c 抓取网页数据(0x1工具准备工欲善其事必先利其器,爬取语料的根基基于python)
优采云 发布时间: 2021-12-26 17:05c 抓取网页数据(0x1工具准备工欲善其事必先利其器,爬取语料的根基基于python)
0x1 工具准备
欲做好事,必先利其器。爬取语料库的基础是基于python。
我们基于python3开发,主要使用以下模块:requests, lxml, json。
简单介绍各个模块的功能
01|要求
requests是一个Python第三方库,特别方便处理URL资源。它的官方文档上写着一个大大的标语:HTTP for Humans(为人类使用HTTP而生)。相比使用python自带的urllib的体验,笔者认为使用requests的体验比urllib高一个数量级。
我们来简单对比一下:
网址:
1import urllib2
2import urllib
3
4URL_GET = "https://api.douban.com/v2/event/list"
5#构建请求参数
6params = urllib.urlencode({'loc':'108288','day_type':'weekend','type':'exhibition'})
7
8#发送请求
9response = urllib2.urlopen('?'.join([URL_GET,'%s'])%params)
10#Response Headers
11print(response.info())
12#Response Code
13print(response.getcode())
14#Response Body
15print(response.read())
复制代码复制代码
要求:
1import requests
2
3URL_GET = "https://api.douban.com/v2/event/list"
4#构建请求参数
5params = {'loc':'108288','day_type':'weekend','type':'exhibition'}
6
7#发送请求
8response = requests.get(URL_GET,params=params)
9#Response Headers
10print(response.headers)
11#Response Code
12print(response.status_code)
13#Response Body
14print(response.text)复制代码复制代码
我们可以发现这两个库还是有一些区别的:
1. 参数构建:urllib需要对参数进行urlencode,比较麻烦;requests 不需要额外的编码,非常简洁。
2. 请求发送:urllib需要构造额外的url参数,成为符合要求的表单;requests 简洁很多,直接获取对应的链接和参数。
3. 连接方式:查看返回数据的头信息的“连接”。使用urllib库时,"connection":"close"表示每次请求结束时关闭socket通道,requests库使用urllib3,多次请求复用一个socket,"connection":"keep-alive" ,表示多个请求使用一个连接,消耗更少的资源
4. 编码方式:requests库的Accept-Encoding编码方式比较完善,这里就不举例了
综上所述,使用requests更加简洁易懂,极大的方便了我们的开发。
02|lxml
BeautifulSoup 是一个库,而 XPath 是一种技术。python中最常用的XPath库是lxml。
当我们获取到请求返回的页面时,如何获取我们想要的数据呢?此时,lxml 是一个强大的 HTML/XML 解析工具。Python从不缺少解析库,那么为什么要在众多库中选择lxml呢?我们选择另一个知名的 HTML 解析库 BeautifulSoup 进行比较。
我们来简单对比一下:
美汤:
1from bs4 import BeautifulSoup #导入库
2# 假设html是需要被解析的html
3
4#将html传入BeautifulSoup 的构造方法,得到一个文档的对象
5soup = BeautifulSoup(html,'html.parser',from_encoding='utf-8')
6#查找所有的h4标签
7links = soup.find_all("h4")
复制代码复制代码
lxml:
1from lxml import etree
2# 假设html是需要被解析的html
3
4#将html传入etree 的构造方法,得到一个文档的对象
5root = etree.HTML(html)
6#查找所有的h4标签
7links = 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。
这两个库,无论是模块导入速度,还是编解码速度,simplejson都比较好,加上simplejson兼容性更好。所以如果要使用square库,可以使用simplejson。
0x2 确定语料来源
准备好武器后,下一步就是确定爬升的方向。
以电子竞技语料库为例,现在我们要爬取电子竞技相关语料库。知名的电竞平台有企鹅电竞、企鹅电竞和企鹅电竞(眯眼),所以我们使用企鹅电竞的直播游戏作为爬取的数据源。
我们登录企鹅电竞官网,进入游戏列表页面。我们可以发现页面上有很多游戏。手动编写这些游戏名称的收益显然不高,于是我们开始了我们爬虫的第一步:游戏列表爬取。
1import requests
2from lxml import etree
3
4# 更新游戏列表
5def _updateGameList():
6 # 发送HTTP请求时的HEAD信息,用于伪装为浏览器
7 heads = {
8 'Connection': 'Keep-Alive',
9 'Accept': 'text/html, application/xhtml+xml, */*',
10 'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3',
11 'Accept-Encoding': 'gzip, deflate',
12 'User-Agent': 'Mozilla/6.1 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko'
13 }
14 # 需要爬取的游戏列表页
15 url = 'https://egame.qq.com/gamelist'
16
17 # 不压缩html,最大链接时间为10妙
18 res = requests.get(url, headers=heads, verify=False, timeout=10)
19 # 为防止出错,编码utf-8
20 res.encoding = 'utf-8'
21 # 将html构建为Xpath模式
22 root = etree.HTML(res.content)
23 # 使用Xpath语法,获取游戏名
24 gameList = root.xpath("//ul[@class='livelist-mod']//li//p//text()")
25 # 输出爬到的游戏名
26 print(gameList)
复制代码复制代码
在我们得到这几十款游戏之后,下一步就是爬取这几十款游戏的语料库。这时候,问题来了。我们想从哪个网站抓取这几十个游戏指南,点击?玩更多?17173?对这些网站进行分析后发现,这些网站只有一些热门游戏的文章语料库,以及一些冷门或不受欢迎的游戏,如《灵魂讨价还价》、《奇迹:觉醒》、《死神降临》等很难在这些网站上找到大量的文章语料,如图:
我们可以发现,《奇迹:觉醒》和《灵魂讨价还价》的语料非常少,数量不符合我们的要求。那么有没有比较通用的资源站,拥有极其丰富的文章语料,可以满足我们的需求。
其实,静下心来想一想。我们每天都在使用这个资源网站,那就是百度。我们在百度新闻中搜索相关游戏,得到了一个搜索结果列表。几乎所有链接到这些列表的网页都与搜索结果密切相关。这样就可以轻松解决数据源不足的问题。但是此时出现了一个新的问题,也是一个比较难解决的问题——如何抓取任意网页文章的内容?
由于不同的网站页面结构不同,我们无法预测会爬取哪个网站的数据,也无法为每个网站编写一个爬虫。这样的工作量简直难以想象!但是我们不能简单粗暴地抓取页面上的所有文字,使用这样的语料进行训练无疑是一场噩梦!
和各种网站斗智斗勇,打听资料,思考,终于找到了一个比较大体的方案。给大家说说作者的想法吧。
0x3 从任意网站爬取文章语料库 01|提取方法
1) 基于Dom树体提取
2)基于分页查找文本块
3) 基于标记窗口的文本提取
4)基于数据挖掘或机器学习
5) 基于行块分布函数的文本提取
02|萃取原理
大家看到这些类型都有些糊涂了,怎么提取出来的?作者慢慢说吧。
1) 基于Dom树的文本提取:
这种方法主要是通过相对规范的HTML构建一个Dom树,然后基柜遍历Dom,对比识别各种非文本信息,包括广告、链接和非重要节点信息,提取非文本信息. 本质是短信。
但是这个方法有两个问题
① 它特别依赖于 HTML 的良好结构。如果我们爬到一个不是按照W3c规范编写的网页,这种方法不是很合适。
②树的建立和遍历的时间复杂度和空间复杂度都很高,而且由于HTML标签不同,树的遍历方法也不同。
2) 基于分页查找文本块:
这种方法是利用HTML标签中的分割线和一些视觉信息(如文字颜色、字体大小、文字信息等)。
这种方法有一个问题:
① 不同网站的HTML样式不同,无法统一切分,不能保证通用性。
3) 基于标记窗口的文本提取:
首先是科普概念-标记窗口,我们将两个标签和其中收录
的文字组合成一个标记窗口(例如h1中的“我是h1”就是标记窗口的内容),取出标记窗口。
这种方法首先取文章的标题和HTML中的所有标记窗口,然后对它们进行分词。然后计算标题的序列和标签窗口的文本序列之间的词距L。如果 L 小于阈值,则将标签窗口中的文本视为主要文本。
这种方法虽然看起来不错,但实际上是有问题的:
① 需要对页面上的所有文字进行分段,效率不高。
②词距的阈值很难确定,不同的文章有不同的阈值。
4)基于数据挖掘或机器学习
使用大数据进行训练,让机器提取正文。
这个方法绝对是优秀的,但是需要训练前的html和text数据。我们不会在这里讨论它。
5) 基于行块分布函数的文本提取
对于任何网页,它的正文和标签总是混合在一起的。这种方法的核心有一个亮点:①文本区域的密度;②线块的长度;一个网页的文本区域一定是文本信息分布最密集的区域之一,这个区域可能是最大的(长评论信息和短文本)。因此,同时判断块长度。
实现思路:
① 我们先把标签中的HTML去掉,只留下所有的文字,去掉标签后留下所有空白的位置信息,我们称之为Ctext;
② 取周围的 k 行(k
③从Cblock中去除所有空白字符,文本的总长度称为Clen;
④ 以Ctext为横轴,每行的Clen为纵轴,建立坐标系。
以这个网页为例:网页的文本区域从第145行到第182行。
从上图可以看出,正确的文本区域都是分布函数图上值最高的连续区域。这个区域通常收录
一个膨胀点和一个下垂点。因此,提取网页正文的问题转化为在线块分布函数上寻找凸点和凹点两个边界点。这两个边界点所收录
的区域收录
当前网页的最大行块长度并且是连续的。
经过大量实验证明,该方法对中文网页文本的提取具有较高的准确率。该算法的优点是行块功能不依赖于HTML代码,与HTML标签无关,实现简单,准确率高。
主要逻辑代码如下:
1# 假设content为已经拿到的html
2
3# Ctext取周围k行(k max_text_len and (not boolstart)):
38 # Cblock下面3个都不为0,认为是正文
39 if (Ctext_len[i + 1] != 0 or Ctext_len[i + 2] != 0 or Ctext_len[i + 3] != 0):
40 boolstart = True
41 start = i
42 continue
43 if (boolstart):
44 # Cblock下面3个中有0,则结束
45 if (Ctext_len[i] == 0 or Ctext_len[i + 1] == 0):
46 end = i
47 boolend = True
48 tmp = []
49
50 # 判断下面还有没有正文
51 if(boolend):
52 for ii in range(start, end + 1):
53 if(len(lines[ii]) < 5):
54 continue
55 tmp.append(lines[ii] + "n")
56 str = "".join(list(tmp))
57 # 去掉版权信息
58 if ("Copyright" in str or "版权所有" in str):
59 continue
60 main_text.append(str)
61 boolstart = boolend = False
62# 返回主内容
63result = "".join(list(main_text))
复制代码复制代码
0x4 结论
此时,我们可以获取任何内容的语料库,但这只是开始。得到这些语料后,我们还需要对词性进行清洗、切分、标注等,才能得到真正可用的语料。