用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 相关文章推荐
Python二维码生成库qrcode安装和使用示例
Dec 16 Python
Python中暂存上传图片的方法
Feb 18 Python
Python编写生成验证码的脚本的教程
May 04 Python
简单谈谈Python中函数的可变参数
Sep 02 Python
如何使用 Pylint 来规范 Python 代码风格(来自IBM)
Apr 06 Python
python广度优先搜索得到两点间最短路径
Jan 17 Python
基于python生成器封装的协程类
Mar 20 Python
深入浅析python3中的unicode和bytes问题
Jul 03 Python
Python使用Beautiful Soup爬取豆瓣音乐排行榜过程解析
Aug 15 Python
pytorch自定义二值化网络层方式
Jan 07 Python
Python换行与不换行的输出实例
Feb 19 Python
解决Jupyter Notebook开始菜单栏Anaconda下消失的问题
Apr 13 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
PHP截取汉字乱码问题解决方法mb_substr函数的应用
2008/03/30 PHP
php中根据变量的类型 选择echo或dump
2012/07/05 PHP
在WordPress中使用wp-cron插件来设置定时任务
2015/12/10 PHP
ThinkPHP中create()方法自动验证实例
2017/04/26 PHP
thinkphp5 + ajax 使用formdata提交数据(包括文件上传) 后台返回json完整实例
2020/03/02 PHP
PHP设计模式之 策略模式Strategy详解【对象行为型】
2020/05/01 PHP
你真的了解JavaScript吗?
2007/02/24 Javascript
JavaScript 学习笔记(十二) dom
2010/01/21 Javascript
JS getStyle获取最终样式函数代码
2010/04/01 Javascript
基于jquery1.4.2的仿flash超炫焦点图播放效果
2010/04/20 Javascript
HTML中的setCapture和releaseCapture使用介绍
2012/03/21 Javascript
从面试题学习Javascript 面向对象(创建对象)
2012/03/30 Javascript
jquery中html、val与text三者属性取值的联系与区别介绍
2013/12/29 Javascript
提高jQuery性能优化的技巧
2015/08/03 Javascript
基于JS代码实现实时显示系统时间
2016/06/16 Javascript
JavaScript中${pageContext.request.contextPath}取值问题及解决方案
2016/12/08 Javascript
移动web开发之touch事件实例详解
2018/01/17 Javascript
webpack里使用jquery.mCustomScrollbar插件的方法
2018/05/30 jQuery
基于vue.js中关于下拉框的值默认及绑定问题
2018/08/22 Javascript
JS如何监听div的resize事件详解
2020/12/03 Javascript
python访问纯真IP数据库的代码
2011/05/19 Python
[原创]教女朋友学Python3(二)简单的输入输出及内置函数查看
2017/11/30 Python
Python 面试中 8 个必考问题
2018/11/16 Python
python中删除某个元素的方法解析
2019/11/05 Python
python实现ip地址的包含关系判断
2020/02/07 Python
使用canvas一步步实现图片打码功能的方法
2019/06/17 HTML / CSS
使用canvas来完成线性渐变和径向渐变的功能的方法示例
2019/07/25 HTML / CSS
施华洛世奇英国官网:SWAROVSKI英国
2017/03/13 全球购物
英语专业学子个人的自我评价
2013/10/02 职场文书
转党组织关系介绍信
2014/01/08 职场文书
怎么写工作检讨书
2014/11/16 职场文书
行政文员岗位职责
2015/02/04 职场文书
法律意见书范文
2015/06/04 职场文书
2015年征兵工作总结
2015/07/23 职场文书
《珍珠鸟》教学反思
2016/02/16 职场文书
MySQL 百万级数据的4种查询优化方式
2021/06/07 MySQL