从字面意思上理解,网页的正文提取嘛,无非就是把网页当中对咱最有价值的那部分文章给取出来撒.有点编程经验的朋友肯定都知道,右键网页源文件,看看html代码,取出来有正则匹配一下也就几分钟而已的事情。更好一点的办法,那自然是用上像DOM或者XPath这样专门对付html的利器,多写几次估计一分钟也不要。如果本博也这么干的话,那还怎么体现您的慧眼如矩呢:)
上面说的方法,实际上在垂直搜索引擎的定向抓取中,给一个目标站点利用DOM建立抽取模板是一个很常用也很准确的办法。但是当问题域变得稍微大那么一点点,比方说吧,我觉着谷歌做的不错,我也想搞一个的话,那咋整呢?再利用上面的办法,机械的给每一个页面建立DOM,是会死人的哟XD
那么问题其实就变成了对于任意篇网页,有没有办法"聪明"点的法子,能识别出正文部分呢?
既然我们人是可以做到这一点的,那么就说明存在利用人工智能去解决这个问题的可能性.当然,我们现在不急,先从简单的做起.
网页里面除了可读文本就是链接,图片,视频,以及其他媒体类型.而后面这些东东在HTML里面都是用专有的标签来显示的,而且还是被标签所"夹住"的。那我们来看看一个文本块里,除了标签还剩下来的东西有多少.
通过Python内置的htmllib模块和formatter的配合,我们可以统计出网页中每一行文本中标签和正文的数量.代码如下:
#coding:utf-8
import htmllib,urllib2
import formatter,StringIO
class TrackParser(htmllib.HTMLParser):
def __init__(self, writer, *args):
htmllib.HTMLParser.__init__(self,*args)
self.writer = writer
def parse_starttag(self,i):
index = htmllib.HTMLParser.parse_starttag(self,i)
self.writer.index = index
return index
def parse_endtag(self,i):
self.writer.index = i
return htmllib.HTMLParser.parse_endtag(self,i)
class Para:
def __init__(self):
self.text = ''
self.bytes = 0
self.density = 0.0
class LineWirter(formatter.AbstractWriter):
"""
a Formatter instance to get text in lines
"""
def __init__(self):
self.last_index = 0
self.lines = [Para()]
formatter.AbstractWriter.__init__(self)
def send_flowing_data(self, data):
t = len(data)
self.index += t
b = self.index - self.last_index
self.last_index = self.index
l = self.lines[-1]
l.text += data
l.bytes += b
def send_paragraph(self,blankline):
if self.lines[-1].text == '':
return
self.lines[-1].text += 'n'*(blankline+1)
self.lines[-1].bytes += 2*(blankline+1)
self.lines.append(Para())
def send_literal_data(self,data):
self.send_flowing_data(data)
def send_line_break(self):
self.send_paragraph(0)
def extract_text(html):
writer = LineWirter()
fmt = formatter.AbstractFormatter(writer)
parser = TrackParser(writer,fmt)
parser.feed(html)
parser.close()
return writer.lines
htmls = urllib2.urlopen("http://ent.hunantv.com/d/x/20091128/503722.html")
print map(lambda x:[x.bytes,len(x.text)],extract_text(htmls.read()))
看着飞速跑过的列表,你是不是恨不得把他给全部写入一个文件来看看结果?在文件尾部加入
q = open("e.csv","w+").writelines('\n'.join(["%s,%s"%(x[0],x[1]) for x in s]))
现在结果变成了一个csv文件鸟,来上个图看看:
上图清晰的表达了该网页的文本分布,根据与页面的比对,我们发现文本所在的区域与相对应的行域保持了某种关系.这似乎说明我们的思路是正确的.
实际上行文本字节数与行总字节数的比值被称为行文本密度.有了这个概念,我们就可以对网页全文扫描计算相应的文本密度,这里我们不妨做一个假设,文本密度在0.5以上的就是我们需要的文本部分,也就是说我们认定某行的文本至少和该行的标签一样多的话,他就可能是我们需要的文本区域.
修改上述代码的两个地方,我们来初试下身手,
在原有的LineWriter里加入:
def output(self):
self.compute_density()
output = StringIO.StringIO()
for l in self.lines:
if l.density > 0.5: //这里就是我们设置的行文本密度
output.write(l.text)
return output.getvalue()
self.compute_density()
output = StringIO.StringIO()
for l in self.lines:
if l.density > 0.5: //这里就是我们设置的行文本密度
output.write(l.text)
return output.getvalue()
修改extract_text函数
def extract_text(html):
writer = LineWirter()
fmt = formatter.AbstractFormatter(writer)
parser = TrackParser(writer,fmt)
parser.feed(html)
parser.close()
return writer.output()
writer = LineWirter()
fmt = formatter.AbstractFormatter(writer)
parser = TrackParser(writer,fmt)
parser.feed(html)
parser.close()
return writer.output()
文件末尾改成
htmls = urllib2.urlopen("http://ent.hunantv.com/d/x/20091128/503722.html")
s = open('e.html','w+').write(extract_text(htmls.read()))
s = open('e.html','w+').write(extract_text(htmls.read()))
先透口气,然后平静的点开e.html,喔,你看见了什么!
激动之后,应该会有这么一个疑问,刚才我们设置的文本密度为0.5,这个数字到底是怎么来的?他具有普适性么?
其实这个文本密度是可以计算出来的:
设y为行文本集合,z为行标签集合,则文本密度p为:
设i代表任意行,分别用yi,zi代表任意行文本/标签的长度,设且均符合正态分布.uy,uz分别代表行文本和行标签的平均长度:
计算方差:
最后得到p的估值:
将我们选用的网页实际情况带入以后,我们得到真实的文本密度p大约为0.53,和估计值很相近.学术界有针对行的做了大量实验,得出新闻资讯类网站的文本密度大约在0.4-0.6左右,sina和sohu的这个值大约都是0.6;博客类网站的文本密度大约在0.7-0.8之间.
作为这个话题上半部分的结束,谈谈这个文本密度的实际应用,除了本文所涉及到的文本抽取以外,文本密度现在被广泛应用于搜索引擎网页价值分析的预处理,同时我们也大体可以看出网站内容的大致分布.
下半部分,本博将引入ANN(神经网络)和FDR(错误控制)的相关方法继续探讨这个话题.敬请围观.
光風(gfn)
你好,这个代码报错self.compute_density()没有定义,请问怎么解决
ReplyDelete