网页正文及内容图片提取算法

备份自:http://blog.rainy.im/2015/09/02/web-content-and-main-image-extractor/

问题:如何提取任意(尤其是新闻、资讯类)网页的正文内容,提取与文章内容相关的图片,源码可见:extractor.py

抓取单个网站网页内容时通常采用正则匹配的方式,但不同网站之间结构千奇百怪,很难用统一的正则表达式进行匹配。《基于行块分布函数的通用网页正文抽取算法》的作者总结了一般从网页中提取文章正文的方法,提出基于行块分布的正文抽取算法,并给出了 PHP 、Java 等实现。这一算法的主要原理基于两点:

  1. 正文区密度:在去除HTML中所有tag之后,正文区字符密度更高,较少出现多行空白;
  2. 行块长度:非正文区域的内容一般单独标签(行块)中较短。

算法步骤如下:

  • 去除所有tag,包括样式、Js脚本内容等,但保留原有的换行符\n
reCOMM = r'<!--.*?-->'
reTRIM = r'<{0}.*?>([\s\S]*?)<\/{0}>'
reTAG  = r'<[\s\S]*?>|[ \t\r\f\v]'

def processTags(body=""):
  body = re.sub(reCOMM, "", body)
  body = re.sub(reTRIM.format("script"), "" ,re.sub(reTRIM.format("style"), "", body))
  body = re.sub(reTAG, "", body)
  return body
  • 将网页内容按行分割,定义行块 $block_i$ 为第 $[i, i + blockSize]$ 行文本之和并给出行块长度基于行号的分布函数:
def processBlocks(body=""):
  ctexts = body.split("\n")
  textLens = [len(text) for text in ctexts]
  cblocks  = [0] * (len(ctexts) - blockSize)
  
  lines = len(ctexts)
  for i in range(blockSize):
    cblocks = list(map(lambda x,y: x+y, textLens[i : lines-1-blockSize+i], cblocks))
  return cblocks
  • 正文出现在最长的行块,截取两边至行块长度为 0 的范围:
def getContext(ctexts, cblocks):
  maxTextLen = max(cblocks)
  
  start = end = cblocks.index(maxTextLen)
  while start > 0 and cblocks[start] > min(textLens):
    start -= 1
  while end < lines - blockSize and cblocks[end] > min(textLens):
    self.end += 1
  
  return "".join(ctexts[start:end])
  • 如果需要提取正文区域出现的图片,只需要在第一步去除tag时保留<img>标签的内容:
reIMG  = re.compile(r'<img[\s\S]*?src=[\'|"]([\s\S]*?)[\'|"][\s\S]*?>')
def processImages(body):
  return reIMG.sub(r'{{\1}}', body)

总结

以上算法基本可以应对大部分(中文)网页正文的提取,针对有些网站正文图片多于文字的情况,可以采用保留<img> 标签中图片链接的方法,增加正文密度。目前少量测试发现的问题有:1)文章分页或动态加载的网页;2)评论长度过长喧宾夺主的网页。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 最近做一个项目,其中涉及到网页信息采集,随后对相关的技术进行了学习与研发,网页正文提取技术常用的有joyhtml、...
    泊牧阅读 3,208评论 0 2
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,973评论 19 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,466评论 25 708
  • 由于最近在做一个项目,给了36个安全网站相关的博客网站,需要将其中的博客正文都抽取出来,而且需要满足以后添加一个博...
    泊牧阅读 7,271评论 4 11
  • 梦幻实似真, 我心满欢喜。 许久人未见, 再度重相逢。 当我不停着敲打着键盘,回首往日的情意绵绵。 是该哭,还是该...
    曲沙南风阅读 220评论 0 20