摘要
jusText
算法是一种优秀的网页正文提取算法,作者是Jan Pomikálek。该算法能够删除 HTML 页面中模板内容(如导航链接、页眉和页脚),自动保留正文句子,准确率很高,适合用于创建Web语料库。这篇文章描述了jusText算法的基本原理和实现步骤,并分析了算法在提取中文网页时的问题,给出了改进建议。
全文3900字,阅读时间大约15分钟。
建议了解该算法的同学直接跳到算法改进部分。
文章目录
先看算法效果
该算法的作者提供了可以直接测试算法效果的在Web页面,使用了算法最新版本3.0。
这个网页设计得简洁明了,把分块、分类结果、分类解释都展现在界面上。
在线测试地址:http://nlp.fi.muni.cz/projects/justext/
因为算法基本是基于英文网页进行设计的,所以对英文网页的抽取结果很好。
其中绿色部分是正文块,鼠标悬浮在特定正文块上,可以看到算法将该块划分为正文的解释,包括链接密度、长度、词数量、停用词数量等等,后续将介绍这些术语的含义。
算法基本原理
在本系列的第一篇文章中,介绍过网页抽取算法都要对网页进行分块,jusText算法使用简单的分块方式。
某些 HTML标签被 Web 浏览器渲染为块(Block),所以,该算法直接用这些标签把HTML页面拆分成块。
块级标签包括:BLOCKQUOTE,CAPTION,CENTER,COL,COLGROUP,DD,DIV,DL,DT,FIELDSET,FORM,H1,H2,H3,H4,H5,H6,LEGEND,LI,OPTGROUP,OPTION,P,PRE,TABLE,TD,TEXTAREA,TFOOT,TH,THEAD,TR,UL。
两个或多个 BR 标记的序列也会分隔块。
对网页完成分块之后,算法的主要目标就是把所有的块分类为模板块和正文块。所谓模板块,就是一些导航、页眉、页脚、广告等内容。有时候一个块可能同时包含正文和模板,但这是相当少见的。
通过观察这些块,可以得到这样一些结论:
1.包含链接的短块几乎总是模板。
2.任何包含许多链接的块几乎总是模板。
3.包含文本的长块几乎总是好的,而所有其他长块几乎总是样板。
4.好的(主要内容)和模板块都倾向于聚集在一起,即模板块通常被其他模板块包围,反之亦然。
5.确定文本是否符合语法可能很棘手,但可以根据停用词的数量使用简单的启发式方法。
6.虽然文本通常包含一定比例的停用词,但模板内容(如列表)中会存在很少的停用词。
通过分析这些观察结果,原作者发现可以用两个步骤解决块(block)
的分类问题:
- 首先,长块和一些短块可以以非常高的置信度进行分类。
- 然后,通过查看周围的块来对所有其他短块进行分类。
算法步骤
第一步:预处理
删除<header>
、<style>
和 <script>
标签及内容。直接把<select>
标签内容设置为bad
,即模板。 包含版权符号(©)的块也设置为bad(模板)
。
第二步:上下文无关的分类
完成分块和预处理之后,开始上下文无关的分类,所谓上下文无关,就是每个块独立分类,不考虑相邻块的分类结果。最终把每个块分到如下四个类别之一:
bad
– 模板块good
– 主要内容(正文)块short
– 太短,无法确切分类的块near-good
– 在short
和good
之间的块
分类的具体代码如下,代码中有一些概念需要提前说明。
link_density
:链接密度,定义为<a>
标签中的字符比例stopwords_density
:停用词密度,停用词比例,英文按照空格分词length
:块内文本长度
算法参数说明
LENGHT_LOW
和LENGTH_HIGH
:根据这两个值,把文本块分为short
,medium-size
和long
MAX_LINK_DENSITY
:最大链接密度STOPWORDS_LOW
和STOPWORDS_HIGH
:停用词密度
块大小 (word count) | 停用词密度 | 类别 |
---|---|---|
medium-size | low | bad |
long | low | bad |
medium-size | medium | near-good |
long | medium | near-good |
medium-size | high | near-good |
long | high | good |
算法默认参数
MAX_LINK_DENSITY = 0.2
LENGTH_LOW = 70
LENGTH_HIGH = 200
STOPWORDS_LOW = 0.30
STOPWORDS_HIGH = 0.32
算法核心代码(官方网站版本)
if link_density > MAX_LINK_DENSITY:
return 'bad'
# short blocks
if length < LENGTH_LOW:
if link_density > 0:
return 'bad'
else:
return 'short'
# medium and long blocks
if stopwords_density > STOPWORDS_HIGH:
if length > LENGTH_HIGH:
return 'good'
else:
return 'near-good'
if stopwords_density > STOPWORDS_LOW:
return 'near-good'
else:
return 'bad'
第三步:上下文相关的分类
上下文相关分类的目标是根据周围块的类将短块和接近好的块重新分类为好或坏。已经被归类为好或坏的块不再改变。
这种分类的思想是聚类:
正文块和正文块在一起,模板块则和模板块靠在一起
。
分类原则
具体策略请参考源代码,这里简要说明。
- 两个
good
块之间的short
被分类为good
。 - 两个
bad
块之间的short
被分为bad
。 bad
和near-good
之间被分为bad
。- 其他情形分为
good
。
如下图所示。
算法改进
问题——中文网页无法适配
如果用一个中文新闻网页测试,就会发现识别结果基本都是错误的。这是由于对原算法中没有考虑中文分词,导致计算词数量的时候发生错误。
算法改进——适应中文网页
为了让jusText
算法适应中文网页,就需要改进算法的分词模块。
方案一
如果只希望处理中文网页,就直接加一个中文分词,比如jieba
分词,然后用分词的结果计算。
这种方式简单,但是需要手动调整一下算法参数,因为英文的一句话包含的单词数量和中文一句话的用词数量差距比较大,会导致有些中文句子被分类到short
类型,影响算法召回率。
方案二
还有一种更加通用的改进方案,就是使用lucene
的标准分词。这种分词方式会把中文按照字进行分词,这样计算块长度的时候,就更加接近英文的结果,但为了获得更好的结果,还是需要稍微调整一下参数。
官方的Python代码示例
官方给出的简单例子如下,比较简洁。
import requests
import justext
response = requests.get("http://planet.python.org/")
paragraphs = justext.justext(response.content, justext.get_stoplist("English"))
for paragraph in paragraphs:
if not paragraph.is_boilerplate:
print paragraph.text
小结
这篇文章简单介绍了jusText
算法的原理和步骤,并指出了算法在中文网页抽取过程中遇到的问题,给出了解决方案。