用python3教你任意Html主内容提取功能


Posted in Python onNovember 05, 2018

本文将和大家分享一些从互联网上爬取语料的经验。

0x1 工具准备

工欲善其事必先利其器,爬取语料的根基便是基于python。

我们基于python3进行开发,主要使用以下几个模块:requests、lxml、json。

简单介绍一个各模块的功能

01|requests

requests是一个Python第三方库,处理URL资源特别方便。它的官方文档上写着大大口号:HTTP for Humans(为人类使用HTTP而生)。相比python自带的urllib使用体验,笔者认为requests的使用体验比urllib高了一个数量级。

我们简单的比较一下:

urllib:

import urllib2
 import urllib
 URL_GET = "https://api.douban.com/v2/event/list"
 #构建请求参数
 params = urllib.urlencode({'loc':'108288','day_type':'weekend','type':'exhibition'})
 #发送请求
 response = urllib2.urlopen('?'.join([URL_GET,'%s'])%params)
#Response Headers
print(response.info())
#Response Code
print(response.getcode())
#Response Body
print(response.read())

requests:

import requests
 URL_GET = "https://api.douban.com/v2/event/list"
 #构建请求参数
 params = {'loc':'108288','day_type':'weekend','type':'exhibition'}

 #发送请求
 response = requests.get(URL_GET,params=params)
 #Response Headers
print(response.headers)
#Response Code
print(response.status_code)
#Response Body
print(response.text)

我们可以发现,这两种库还是有一些区别的:

1. 参数的构建:urllib需要对参数进行urlencode编码处理,比较麻烦;requests无需额外编码处理,十分简洁。

2. 请求发送:urllib需要额外对url参数进行构造,变为符合要求的形式;requests则简明很多,直接get对应链接与参数。

3. 连接方式:看一下返回数据的头信息的“connection”,使用urllib库时,"connection":"close",说明每次请求结束关掉socket通道,而使用requests库使用了urllib3,多次请求重复使用一个socket,"connection":"keep-alive",说明多次请求使用一个连接,消耗更少的资源

4. 编码方式:requests库的编码方式Accept-Encoding更全,在此不做举例

综上所诉,使用requests更为简明、易懂,极大的方便我们开发。

02|lxml

BeautifulSoup是一个库,而XPath是一种技术,python中最常用的XPath库是lxml。

当我们拿到requests返回的页面后,我们怎么拿到想要的数据呢?这个时候祭出lxml这强大的HTML/XML解析工具。python从不缺解析库,那么我们为什么要在众多库里选择lxml呢?我们选择另一款出名的HTML解析库BeautifulSoup来进行对比。

我们简单的比较一下:

BeautifulSoup:

from bs4 import BeautifulSoup #导入库
# 假设html是需要被解析的html
#将html传入BeautifulSoup 的构造方法,得到一个文档的对象
soup = BeautifulSoup(html,'html.parser',from_encoding='utf-8')
#查找所有的h4标签 
links = soup.find_all("h4")

lxml:

from lxml import etree
# 假设html是需要被解析的html
#将html传入etree 的构造方法,得到一个文档的对象
root = etree.HTML(html)
#查找所有的h4标签 
links = root.xpath("//h4")

我们可以发现,这两种库还是有一些区别的:

1. 解析html: BeautifulSoup的解析方式和JQ的写法类似,API非常人性化,支持css选择器;lxml的语法有一定的学习成本

2. 性能:BeautifulSoup是基于DOM的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多;而lxml只会局部遍历,另外lxml是用c写的,而BeautifulSoup是用python写的,明显的性能上lxml>>BeautifulSoup。

综上所诉,使用BeautifulSoup更为简明、易用,lxml虽然有一定学习成本,但总体也很简明易懂,最重要的是它基于C编写,速度快很多,对于笔者这种强迫症,自然而然就选lxml啦。

03|json

python自带json库,对于基础的json的处理,自带库完全足够。但是如果你想更偷懒,可以使用第三方json库,常见的有demjson、simplejson。

这两种库,无论是import模块速度,还是编码、解码速度,都是simplejson更胜一筹,再加上兼容性 simplejson 更好。所以大家如果想使用方库,可以使用simplejson。

0x2 确定语料源

将武器准备好之后,接下来就需要确定爬取方向。

以电竞类语料为例,现在我们要爬电竞类相关语料。大家熟悉的电竞平台有企鹅电竞、企鹅电竞和企鹅电竞(斜眼),所以我们以企鹅电竞上直播的游戏作为数据源进行爬取。

我们登陆企鹅电竞官网,进入游戏列表页,可以发现页面上有很多游戏,通过人工去写这些游戏名收益明显不高,于是我们就开始我们爬虫的第一步:游戏列表爬取。

用python3教你任意Html主内容提取功能

import requests
 from lxml import etree
 # 更新游戏列表
 def _updateGameList():
   # 发送HTTP请求时的HEAD信息,用于伪装为浏览器
   heads = { 
    'Connection': 'Keep-Alive',
    'Accept': 'text/html, application/xhtml+xml, */*',
    'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3',
    'Accept-Encoding': 'gzip, deflate',
    'User-Agent': 'Mozilla/6.1 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko'
  }
  # 需要爬取的游戏列表页
  url = 'https://egame.qq.com/gamelist'
  # 不压缩html,最大链接时间为10妙
  res = requests.get(url, headers=heads, verify=False, timeout=10)
  # 为防止出错,编码utf-8
  res.encoding = 'utf-8'
  # 将html构建为Xpath模式
  root = etree.HTML(res.content)
  # 使用Xpath语法,获取游戏名
  gameList = root.xpath("//ul[@class='livelist-mod']//li//p//text()")
  # 输出爬到的游戏名
  print(gameList)

当我们拿到这几十个游戏名后,下一步就是对这几十款游戏进行语料爬取,这时候问题就来了,我们要从哪个网站来爬这几十个游戏的攻略呢,taptap?多玩?17173?在对这几个网站进行分析后,发现这些网站仅有一些热门游戏的文章语料,一些冷门或者低热度的游戏,例如“灵魂筹码”、“奇迹:觉醒”、“死神来了”等,很难在这些网站上找到大量文章语料,如图所示:

用python3教你任意Html主内容提取功能

我们可以发现,“ 奇迹:觉醒”、“灵魂筹码”的文章语料特别少,数量上不符合我们的要求。 那么有没有一个比较通用的资源站,它拥有着无比丰富的文章语料,可以满足我们的需求。

其实静下心来想想,这个资源站我们天天都有用到,那就是百度。我们在百度新闻搜索相关游戏,拿到搜索结果列表,这些列表的链接的网页内容几乎都与搜索结果强相关,这样我们数据源不够丰富的问题便轻松解决了。但是此时出现了一个新的问题,并且是一个比较难解决的问题——如何抓取到任意网页的文章内容?

因为不同的网站都有不同的页面结构,我们无法与预知将会爬到哪个网站的数据,并且我们也不可能针对每一个网站都去写一套爬虫,那样的工作量简直难以想象!但是我们也不能简单粗暴的将页面中的所有文字都爬下来,用那样的语料来进行训练无疑是噩梦!

经过与各个网站斗智斗勇、查询资料与思索之后,终于找到一条比较通用的方案,下面为大家讲一讲笔者的思路。

0x3 任意网站的文章语料爬取

01|提取方法

1)基于Dom树正文提取

2)基于网页分割找正文块

3)基于标记窗的正文提取

4)基于数据挖掘或机器学习

5)基于行块分布函数正文提取

02|提取原理

大家看到这几种是不是都有点疑惑了,它们到底是怎么提取的呢?让笔者慢慢道来。

1)基于Dom树的正文提取:

这一种方法主要是通过比较规范的HTML建立Dom树,然后地柜遍历Dom,比较并识别各种非正文信息,包括广告、链接和非重要节点信息,将非正文信息抽离之后,余下来的自然就是正文信息。

但是这种方法有两个问题

① 特别依赖于HTML的良好结构,如果我们爬取到一个不按W3c规范的编写的网页时,这种方法便不是很适用。

② 树的建立和遍历时间复杂度、空间复杂度都较高,树的遍历方法也因HTML标签会有不同的差异。

2) 基于网页分割找正文块 :

这一种方法是利用HTML标签中的分割线以及一些视觉信息(如文字颜色、字体大小、文字信息等)。

这种方法存在一个问题:

① 不同的网站HTML风格迥异,分割没有办法统一,无法保证通用性。

3) 基于标记窗的正文提取:

先科普一个概念——标记窗,我们将两个标签以及其内部包含的文本合在一起成为一个标记窗(比如 <h1>我是h1</h1> 中的“我是h1”就是标记窗内容),取出标记窗的文字。

这种方法先取文章标题、HTML中所有的标记窗,在对其进行分词。然后计算标题的序列与标记窗文本序列的词语距离L,如果L小于一个阈值,则认为此标记窗内的文本是正文。

这种方法虽然看上去挺好,但其实也是存在问题的:

① 需要对页面中的所有文本进行分词,效率不高。

② 词语距离的阈值难以确定,不同的文章拥有不同的阈值。

4)基于数据挖掘或机器学习

使用大数据进行训练,让机器提取主文本。

这种方法肯定是极好的,但是它需要先有html与正文数据,然后进行训练。我们在此不进行探讨。

5)基于行块分布函数正文提取

对于任意一个网页,它的正文和标签总是杂糅在一起。此方法的核心有亮点:① 正文区的密度;② 行块的长度;一个网页的正文区域肯定是文字信息分布最密集的区域之一,这个区域可能最大(评论信息长、正文较短),所以同时引进行块长度进行判断。

实现思路:

① 我们先将HTML去标签,只留所有正文,同时留下标签取出后的所有空白位置信息,我们称其为Ctext;

② 对每一个Ctext取周围k行(k<5),合起来称为Cblock;

③ 对Cblock去掉所有空白符,其文字总长度称为Clen;

④ 以Ctext为横坐标轴,以各行的Clen为纵轴,建立坐标系。

以这个网页为例: http://www.gov.cn/ldhd/2009-11/08/content_1459564.htm   该网页的正文区域为145行至182行。

用python3教你任意Html主内容提取功能

由上图可知,正确的文本区域全都是分布函数图上含有最值且连续的一个区域,这个区域往往含有一个骤升点和一个骤降点。因此,网页正文抽取问题转化为了求行块分布函数上的骤升点和骤降点两个边界点,这两个边界点所含的区域包含了当前网页的行块长度最大值并且是连续的。

经过大量实验,证明此方法对于中文网页的正文提取有较高的准确度,此算法的优点在于,行块函数不依赖与HTML代码,与HTML标签无关,实现简单,准确率较高。

主要逻辑代码如下:

 

# 假设content为已经拿到的html
 # Ctext取周围k行(k<5),定为3
 blocksWidth = 3
 # 每一个Cblock的长度
 Ctext_len = []
 # Ctext
 lines = content.split('n')
 # 去空格
for i in range(len(lines)):
  if lines[i] == ' ' or lines[i] == 'n':
    lines[i] = ''
# 计算纵坐标,每一个Ctext的长度
for i in range(0, len(lines) - blocksWidth):
  wordsNum = 0
  for j in range(i, i + blocksWidth):
    lines[j] = lines[j].replace("\s", "")
    wordsNum += len(lines[j])
  Ctext_len.append(wordsNum)
# 开始标识
start = -1
# 结束标识
end = -1
# 是否开始标识
boolstart = False
# 是否结束标识
boolend = False
# 行块的长度阈值
max_text_len = 88
# 文章主内容
main_text = []
# 没有分割出Ctext
if len(Ctext_len) < 3:
  return '没有正文'
for i in range(len(Ctext_len) - 3):
  # 如果高于这个阈值
  if(Ctext_len[i] > max_text_len and (not boolstart)):
    # Cblock下面3个都不为0,认为是正文
    if (Ctext_len[i + 1] != 0 or Ctext_len[i + 2] != 0 or Ctext_len[i + 3] != 0):
      boolstart = True
      start = i
      continue
  if (boolstart):
    # Cblock下面3个中有0,则结束
    if (Ctext_len[i] == 0 or Ctext_len[i + 1] == 0):
      end = i
      boolend = True
  tmp = []
  # 判断下面还有没有正文
  if(boolend):
    for ii in range(start, end + 1):
      if(len(lines[ii]) < 5):
        continue
      tmp.append(lines[ii] + "n")
    str = "".join(list(tmp))
    # 去掉版权信息
    if ("Copyright" in str or "版权所有" in str):
      continue
    main_text.append(str)
    boolstart = boolend = False
# 返回主内容
result = "".join(list(main_text))

0x4 结语

至此我们就可以获取任意内容的文章语料了,但这仅仅是开始,获取到了这些语料后我们还需要在一次进行清洗、分词、词性标注等,才能获得真正可以使用的语料。

总结

以上所述是小编给大家介绍的用python3教你任意Html主内容提取功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
TF-IDF与余弦相似性的应用(二) 找出相似文章
Dec 21 Python
Python堆排序原理与实现方法详解
May 11 Python
Python中 map()函数的用法详解
Jul 10 Python
python程序快速缩进多行代码方法总结
Jun 23 Python
pandas DataFrame的修改方法(值、列、索引)
Aug 02 Python
Python字符串处理的8招秘籍(小结)
Aug 13 Python
pytorch判断是否cuda 判断变量类型方式
Jun 23 Python
Python pytesseract验证码识别库用法解析
Jun 29 Python
基于python实现坦克大战游戏
Oct 27 Python
使用python如何删除同一文件夹下相似的图片
May 07 Python
Python-typing: 类型标注与支持 Any类型详解
May 10 Python
Python django中如何使用restful框架
Jun 23 Python
用Python实现读写锁的示例代码
Nov 05 #Python
详解如何为eclipse安装合适版本的python插件pydev
Nov 04 #Python
详解Python下Flask-ApScheduler快速指南
Nov 04 #Python
Python中修改字符串的四种方法
Nov 02 #Python
Python中flatten( )函数及函数用法详解
Nov 02 #Python
[原创]Python入门教程5. 字典基本操作【定义、运算、常用函数】
Nov 01 #Python
Python拼接字符串的7种方法总结
Nov 01 #Python
You might like
ThinkPHP分页类使用详解
2014/03/05 PHP
php使用pdo连接sqlite3的配置示例
2016/05/27 PHP
PHP简单计算两个时间差的方法示例
2017/06/20 PHP
PHP实现找出链表中环的入口节点
2018/01/16 PHP
写入cookie的JavaScript代码库 cookieLibrary.js
2009/10/24 Javascript
javascript多种数据类型表格排序代码分析
2010/09/11 Javascript
JS维吉尼亚密码算法实现代码
2010/11/09 Javascript
深入理解javascript中return的作用
2013/12/30 Javascript
jQuery基础知识小结
2014/12/22 Javascript
浅谈javascript中的DOM方法
2015/07/16 Javascript
drag-and-drop实现图片浏览器预览
2015/08/06 Javascript
js图片翻书效果代码分享
2015/08/20 Javascript
jQuery判断元素是否显示 是否隐藏的简单实现代码
2016/05/19 Javascript
基于JS实现类似支付宝支付密码输入框
2016/09/02 Javascript
js 性能优化之算法和流程控制
2017/02/15 Javascript
jQuery 导航自动跟随滚动的实现代码
2018/05/30 jQuery
Angular动画实现的2种方式以及添加购物车动画实例代码
2018/08/09 Javascript
微信小程序实现签到功能
2018/10/31 Javascript
解决vue-cli 打包后自定义动画未执行的问题
2019/11/12 Javascript
Python 代码性能优化技巧分享
2012/08/07 Python
用Python制作检测Linux运行信息的工具的教程
2015/04/01 Python
使用Python抓取豆瓣影评数据的方法
2018/10/17 Python
对python:print打印时加u的含义详解
2018/12/15 Python
Python3 max()函数基础用法
2019/02/19 Python
多版本python的pip 升级后, pip2 pip3 与python版本失配解决方法
2019/09/11 Python
使用opencv将视频帧转成图片输出
2019/12/10 Python
python 字典套字典或列表的示例
2019/12/16 Python
Python如何基于Tesseract实现识别文字功能
2020/06/05 Python
Myprotein台湾官方网站:全球领先的运动营养品牌
2018/12/10 全球购物
建筑工程技术应届生自荐信
2013/09/27 职场文书
运动会方阵口号
2014/06/07 职场文书
2014年居委会工作总结
2014/12/09 职场文书
有关水浒传的读书笔记
2015/06/25 职场文书
会计手工模拟做账心得体会
2016/01/22 职场文书
Flask response响应的具体使用
2021/07/15 Python
PYTHON 使用 Pandas 删除某列指定值所在的行
2022/04/28 Python