[目次]
最近の仕事には、HTML ページからニュース テキストを抽出する問題が含まれています。この問題を解決できるサードパーティのライブラリは数多くありますが、満足できない機能や中国語に適用できない機能が常に存在します。したがって、この記事では、Newspaper、python-goose、python-readability のこれらのパッケージを使用して、次のニュース抽出の詳細を解釈します。ソース コードを読んでいるので、そこには他のプログラミング スキルが記録されているため、混乱を招くかもしれません。ニュース テキストの解析に興味がある場合のみ、無視してかまいません。
`from newspaper import Articleurl = 'http://www.cankaoxiaoxi.com/roll10/20160619/1197379.shtml'article = Article(url)article.download()article.parse()print(article.text)
。ニュースレターの主な機能はこのパッケージから借用しているため、具体的な内容についてはニュースレターでのみ紹介します。このパッケージは Python3 をサポートしていません。 使用法:
`from readability.readability import Documentimport urllibhtml = urllib.urlopen(url).read()readable_article = Document(html).summary()readable_title = Document(html).short_title()
Newspaper parse() はデフォルトで写真とビデオを解析するため、一部のビデオと写真が欠落している場合はエラーが報告されます。効率を考慮すると、写真とビデオは必要ありません。デフォルトですべての関数を解析するのではなく、関数を分離した方がよいでしょう。 Python の可読性は比較的優れています。必要な場合にのみ解析されます。そして、Python の可読性は、まさに私が必要としているページをダウンロードするのに役立ちません。もちろん、Newspaper は HTML の受け渡しもサポートしていますが、その実装は非常に醜く感じられます。 自分で実装したくない場合は、Python 可読性を使用することをお勧めします。
コード構造
`├── api.py├── article.py 所有的功能封装在这个里面├── cleaners.py 清洗HTML页面├── configuration.py 配置├── extractors.py 提取正文等核心功能实现├── images.py 图片相关,这个我忽略不关心├── __init__.py├── mthreading.py 多线程模块,作者自己实现的线程尺用于HTML下载├── network.py 封装requests 下载HTML├── nlp.py 简单自然语言处理功能比如关键词提取├── outputformatters.py 格式化输出├── parsers.py 封装lxml,提供一些方便的方法├── resources 存放数据文件<br />├── settings.py<br />├── source.py<br />├── text.py 对词的处理,算分的时候会用到├── urls.py 一些urls 的方法├── utils.py<br />├── version.py└── videos
この部分はリクエストを使用しており、マルチスレッド関数をカプセル化してスレッドプールを取り出して Python3 で実装し、テストを作成しました。 ```
class Worker(Thread): def
init(self, Tasks, timeout minutes): Thread.init (self, ) print(self.getName()) self .tasks = タスク self.timeout = タイムアウト秒 self.daemon = True self.start()
def run(self): while True: try: func, args, kargs = self.tasks.get(timeout=self.timeout) print(":".join((self.getName(), args[0]))) except queue.Empty: # Extra thread allocated, no job, exit gracefully break try: func(*args, **kargs) except Exception: traceback.print_exc() self.tasks.task_done()
class ThreadPool: def init(self, スレッド数, タイムアウト秒数): self.tasks = queue.Queue(スレッド数) _ 範囲 (スレッド数) 内: ワーカー (self.tasks, timeout_秒)
def add_task(self, func, *args, **kargs): self.tasks.put((func, args, kargs))def wait_completion(self): self.tasks.join()
urls = [ 'http://www.baidu.com', 'http://midday.me', 'http://94fzb. com', 'http://jd.com', 'http://tianmao.com', ] インポートリクエストのインポート時間
def task(url): returnrequests.get(url)
def test threadpool (): pool = ThreadPool(2, 10)
start = time.time()for url in urls: pool.add_task(task, url)pool.wait_completion()print("threadpool spant: ", time.time() - start)
def test singlethread(): start = time.time() for urls: task(url) print("singlethread used: ", time.time() - start)
if
name== '
main': rrree ``` テキスト抽出
タイトルと著者の抽出は、著者部分がサポートするだけであることは言うまでもなく、私にとってあまり役に立ちません英語を読みました。 以下もルールに基づいています。 一般に、すべての解析はルールに基づいています。いくつかの統計手法が使われていますが、基本的には法則であり、当てはまらない場合も必ずあります。 テキストの抽出は主に次のステップに分かれています:
コアはステップ 2 にあります。特定のコードは、extractors.py の ContentExtractor の Calculate bestnode メソッドに実装されています。ステップ 2 は詳細に分割できます。
1.选取所有p,pre,td 标签 2.清除连接密集型标签,这里会用到is highlinkdensity方法,如果满足下面这个公式: 所有a标签的词数/所有候选标签次数> 1/a标签总数 就认为是连接密集型,会被扔掉。 3.计算节点得分,分为两部分,一部分是包含的stopword的数量,在resource 文件夹下面有对应语言的词表,其实这个词表不是黑名单,更像是白名单。还有一部分叫boost score。 boostscore 这个分数是对文章开头和结尾部分的标签的不同处理,开头的段会获得较多的加分,当候选节点多余15个,最后4分之一的节点都会得到更少的分数(作者解释是可能会是评论) 。
`boost_score = float((1.0 / starting_boost) * 50)
` 上面是根据段的顺序的加分公式,starting_boost 会不断递增,当然还有一个判断节点是不是boost 。逻辑就是判断是否为p标签,包含的stopwords 大于5个(这个是很费解的)。中文的, stopwords, 存在stopwords-zh.txt中我看了下都是些常用词,只有125个(这个怎么来的,并没有找到相关介绍)。下面是作者对这个判断的解释,
Alot of times the first paragraph might be the caption under an image so we'll want to make sure if we're going to boost a parent node that it should be connected to other paragraphs, at least for the first n paragraphs so we'll want to make sure that the next sibling is a paragraph and has at least some substantial weight to it.
本节点的得分都会加到父节点,和父节点的父节点。最终从这些父节点中选出得分最高的解释最终的结果。
相对于Newspaper python-readability 的代码会更加清晰明了,组织的也较好。 例子中是使用summary()这个方法获得结果,
`readable_article = Document(html).summary()
summary方法还有一个参数``
html_partial```指定返回结果是否需要html 标签。 这个实现会有些区别:
1.移除js, 和css 等不需要的标签 2.把所有div 标签都转成了p标签 3.根据标签的class 属性, 名称对所有p标签打分.(打分规则详见后面的代码)。 4.选择得分最高格式化并返回
打分规则有两部分第一部分是根据class 属性,第二部分是根据tag名称。 下面对不同标签赋予不同权重
`def score_node(self, elem):content_score = self.class_weight(elem)name = elem.tag.lower()if name == "div":content_score += 5elif name in ["pre", "td", "blockquote"]:content_score += 3elif name in ["address", "ol", "ul", "dl", "dd", "dt", "li", "form"]:content_score -= 3elif name in ["h1", "h2", "h3", "h4", "h5", "h6", "th"]:content_score -= 5return {'content_score': content_score,'elem': elem}
class_weight 方法是根据class 的值来打分,打分规则如下``
def class_weight(self, e): weight = 0 for feature in [e.get('class', None), e.get('id', None)]: if feature: if REGEXES['negativeRe'].search(feature): weight -= 25 if REGEXES['positiveRe'].search(feature): weight += 25 if self.positive_keywords and self.positive_keywords.search(feature): weight += 25 if self.negative_keywords and self.negative_keywords.search(feature): weight -= 25 if self.positive_keywords and self.positive_keywords.match('tag-'+e.tag): weight += 25 if self.negative_keywords and self.negative_keywords.match('tag-'+e.tag): weight -= 25 return weight
`其中用到预先定义的正则:
`
REGEXES = { 'unlikelyCandidatesRe':re.compile('combx|comment|community|disqus|extra|foot|header|menu|remark|rss|shoutbox|sidebar|sponsor|adbreak|agegate|pagination|pager|popup|tweet|twitter', re.I), 'okMaybeItsACandidateRe': re.compile('and|article|body|column|main|shadow', re.I), 'positiveRe': re.compile('article|body|content|entry|hentry|main|page|pagination|post|text|blog|story', re.I), 'negativeRe': re.compile('combx|comment|com|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|shoutbox|sidebar|sponsor|shopping|tags|tool|widget', re.I), 'divToPElementsRe': re.compile('<(a|blockquote|dl|div|img|ol|p|pre|table|ul)', re.I), 'videoRe': re.compile('https?:\/\/(www.)?(youtube|vimeo).com', re.I), }
```
看了两种实现,其实都是基于一些规则,能写出这样的规则,对html 该要有所熟悉才能完成?但是这些规则终归是死的,当然我有见到其他方式实现,使用了简单的机器学习算法,但是可能效果并不会比这里介绍的好多少。
看了很多新闻,发现大部分新闻的摘要都是第一段,看新闻只看第一段大概能知道这个新闻在说什么。这里的第一段并不是严格意义上的第一段。而是新闻前面一部分。自动摘要的结果并不会比第一段的好。所以为了满足自己需求,在python-readability 的结果返回后使用一些类似的规则取第一段作为摘要,还有对中文环境下的新闻可以做些适当调整,这都是在使用过程中能改进的地方