11.2 网络信息请求、解析与处理
11.2.1 robot.txt协议
网络爬虫这个名词对我们而言其实并不陌生。谷歌、百度、bing等搜索引擎就是大型的网络爬虫,它们无时无刻不在网络世界里抓取信息,但也并非总能畅通无阻。稍加留意就会发现,有时在搜索结果页面中会出现“由于该网站的robots.txt文件存在限制指令(限制搜索引擎抓取),系统无法提供该页面的内容描述”的字样,这是因为不同信息的敏感度不同,网络内容服务商可能并不愿意自己所拥有的高价值的数据内容被抓取,因此不同的网络内容服务商通过设置不同的协议来限制爬虫的信息抓取。此外,可能很多人都有这样的经历——春运抢票。为了能够及时回家,很多人会在同一时间登录12306网页进行抢票,随之而来的就是服务器的延宕。这是因为短时间内涌入服务器的人数(操作)超过了服务器在设计之初时的容量上限。任何一个网络内容服务商都会对自己的平台设置访问容量上限,这不仅能为访问者提供更高质量的服务,而且是非常经济的行为。当使用网络爬虫访问网页时,我们是将一般情况下原本1秒内一个用户的一次访问动作(如请求页面跳转)通过程序变成了1秒内一个用户成百上千次的访问请求,其效果就像在同一时间进行春运抢票一样,它会对服务器的服务质量产生影响,这对一些网络内容服务商而言是无法承受的。
robots.txt协议就是一组针对机器人(如网络爬虫)的指令。这个文件包含在大多数网站的源文件中,主要用于管理机器人(如网络爬虫)在该网站的行为。但该协议不具有技术上的强制性,即“好”机器人会遵守,“坏”机器人则会去破坏。我们可以把一个robots.txt文件想象为一个出现在宣传栏中的“行为公约”,号召大家能遵守规则,却也无法限制不好行为的发生。但严重的违反行为可能会导致法律诉讼,请大家在获取网络信息时做一个“好”机器人。
网站robots协议的查看方法很简单,一般在目标网站的网址后加上“/robots.txt”即可,以下是百度及网易新闻的robots协议的部分截图(图11-5、11-6):
图11-5 百度https://www.baidu.com/robots.txt
图11-6 网易新闻https://news.163.com/robots.txt
当网站允许所有机器人访问时,其协议中会出现如下字样:
User-agent:*
Allow:/
(或创建一个空的robots.txt文件)
当网站禁止所有机器人访问时,其协议中会出现如下字样:
User-agent:*
Disallow:/
当网站禁止指定机器人访问时,其协议中会出现如下字样:
User-agent:指定机器人名称
Disallow:/
更多关于robots.txt的内容可阅读本章“延伸与讨论”版块。
11.2.2 用requests库请求网页信息
在充分了解了网站的robots协议后,我们就可以合乎规范地获取网页信息了。获取所需要的信息可以分为两步:第一步,向目标网站请求网页信息;第二步,解析请求到的信息,获取所需要的部分。在Python中存在着很多请求网页信息的库,比如Python内置的urllib库,但一般认为requests库较为便捷。
在第一节中,我们已经安装好了requests库。接下来我们一起来看看如何使用requets库。
requests库提供了7种主要方法来实现对网页内容的获取请求(表11-1),它“模拟”了我们的浏览器与目标服务器间的所有可能情况。
表11-1 requests库的主要方法
由于本书并非Python学习的专门书籍,因此本节仅就最基础的内容进行讲解。在requests库的所有方法中,获得一个网页最常用的方法就是get()方法,其语法为:
r=requests.get(url)
其中requests.get(url)构造了一个向服务器请求资源的Request对象;变量r被赋值为通过请求所获取到的所有内容(更多时候我们习惯将变量名设为response);url是get()方法的第一个参数,也是必填参数,即所需请求的页面网址。除url参数外,get()方法还有很多参数,通过对这些参数进行不同的设置,可以应对在向服务器请求资源过程中的各种问题,其完整语法为:
r=requests.get(url,params=None,**kwargs)
url:拟获取页面的url链接
params:url中的额外参数,字典或字节流格式,(可选)
**kwargs:12个控制访问的参数(可选)
我们可以通过r.raise_for_status来判断get()是否成功,其中状态码200表示请求成功,404表示未找到,405表示请求被禁止,503表示系统超载或维护等。通过r.encoding=r.aparent_encoding来规避可能出现的乱码问题;通过r.text查看所请求到的内容。由此我们可以得到一个关于网页请求的通用代码框架(图11-7),只需要将其中的URL替换成所需网页的网址即可实现请求。
图11-7 网页请求通用代码框架
11.2.3 用bs4库解析网页信息
对大多数人而言,往往是先学会创建网页,然后才学拆解网页,但此时,我们的工作却恰好相反。在通过requests库请求网页成功的基础上,所谓的解析网页实际就是拆解网页代码,从而寻获所需要的部分。因此我们不需要具备熟练创建网页的的能力,当然如果具备了这一能力将会使你在阅读本节时更加得心应手。在这一小节中,我们假设了大家都没有网页编程基础,然后尝试带领大家一起确定一个网页的内部结构,并通过BeautifulSoup4(bs4)这一第三方库的相关功能实现对所获取网页信息的解析。
(BeautifulSoup4中文文档)
所谓BeautifulSoup,在其中文文档中是这样介绍自己的:BeautifulSoup 是一个可以从HTML或XML文件中提取数据的Python库,它能够实现惯用的文档导航、查找、修改文档,帮你节省数小时甚至数天的工作时间。关于此库更详细的内容可以通过扫码进入文档阅读。bs4是BeautfulSoup4的缩写,在解析网页信息中我们实际使用的是bs4下的BeautifulSoup类。
在计算机看来,每个网页都不过是一些HTML代码,而所谓HTML可以简单理解为以<>为标识的标签(Tag)所封装的一些信息,每一对尖括号形成一组标签。如<title>视听节目市场调查与分析</title>就是一对title标签,中间的文字就是title标签在网页对应位置上会显示的内容。标签与标签间存在上下游关系(通过缩进实现),形成标签树。当我们指定好所需标签类型时,bs4库将通过解析、遍历、维护标签树满足我们的需求。
由于无法确定在实际操作中我们所需要的信息会出现在网页中的什么位置,因此对HTML标签的具体格式有所了解能够有效地帮助我们掌握、使用bs库。这里以p标签为例(网页中定义段落的标签),其标签的具体格式是:
<p class=“title”> …</p>
其中p是标签名,一般成对出现;class=“title”是p标签的属性,以多个或者0个键值对形式出现,class为键,title为值;两个尖括号间的省略部分为NavigableString,指标签内的非属性字符串。
那么如何才能查看到一个网页的HTML代码呢?一种方法是通过上一节所介绍的request库请求获得,另一种则是通过浏览器自带功能获得:浏览器打开相应网页,右键点击“查看源代码”。通过查看源代码,确定所需信息所在的标签及其标签的相同点(规律),就可以利用Python快速将它们提取出来了。
由于bs4也是一个第三方库,所以在使用前依旧需要安装,如何安装第三方库在上面章节内容中已经介绍,此处不再赘述,相关命令为:
pip install beautifulsoup4
这样 bs4 所在的第三方库就下载和安装完成了,通过import关键词就可以在脚本中调用这个库了。对bs4库的使用我们集中在BeautifulSoup类上,因此在编写调用语句时可以使用from…import…来实现。然后通过bs4所提供的一个简单方法,即BeautifulSoup()方法来实现对网页内容的解析(图11-8)。
图11-8 bs4库的基本使用
第一行代码表示导入BeautifulSoup类,第二行代码是将通过BeautifulSoup()方法解析出的内容赋给变量soup,以备进一步使用。这其中有两点需要注意,首先B和S字母需要大写。其次BeautifulSoup()方法包含了两个参数,第一个参数是包含了HTML格式的信息,它可以直接以字符串形式出现,也可以以变量名称形式出现;第二个参数是HTML解析器,它可以帮助bs4正确理解HTML格式信息而不会报错。
接下来我们以BeautifulSoup中文文档中出现的《爱丽丝梦游仙境》的一段内容(图11-9)为例,简单看一下BeautifulSoup是如何通过标签获取相关内容的。
图11-9 爱丽丝梦游仙境内容
使用BeautifulSoup解析这段代码,能够得到一个 BeautifulSoup 的对象,并能按照标准的缩进结构输出(图11-10):
图11-10 爱丽丝梦游仙境HTML代码
此时,我们就可以利用标签的有关知识获取《爱丽丝梦游仙境》HTML代码中指定的信息了:
1.获取标签的基本方法。任何存在于HTML语法中的标签内容都能通过soup.tag’s name的形式获取到。HTML文档中存在多个相同文档时,此方法只能返回第一个符合条件的内容。
输入:soup.title
输出:<title>The Dormouse’s story</title>
2.通过<tag>.name获得标签的名字。对任何一个标签使用.name的方式都能以字符串形式获得它的名字。
输入:soup.title.name
输出:‘title’
3.通过<tag>.string获得Navigablestring,即标签内的非属性字符串,也就是<>…</>中的字符串。
输入:soup.title.string
输出:‘The Dormouse’s story’
4.通过soup.get_text()获得所有文字内容。
输入:print(soup.get_text())
输出:The Dormouse’s story
# The Dormouse’s story
Once upon a time there were three little sisters;and their names were
Elsie,
Lacie and
Tillie;
and they lived at the bottom of a well.
……
然而在实践中,网页的情况远比上面的例子要复杂,这时我们可以借助BeautifulSoup类中所提供的find_all()方法对内容进行查找。
find_all()方法通过参数的设定可以将网页中所有符合条件的信息全部解析出来,并以列表的形式提供出来。其基本语法为:
soup.find_all(name,attrs,recursive,string,**kwargs)
其中soup为通过BeautifulSoup()解析后得到内容的变量名称,可以自拟,但注意要和赋值时的变量一致。name参数的作用是对标签名称进行检索的字符串形式,如我们想查找《爱丽丝梦游仙境》代码中所有的a标签(<a>),我们可以通过soup.find_all(‘a’)实现:
[<a class=”sister” href=”http://example.com/elsie” id=”link1”>Elsie</a>,
<a class=”sister” href=”http://example.com/lacie” id=”link2”>Lacie</a>,
<a class=”sister” href=”http://example.com/tillie” id=”link3”>Tillie</a>]
如果在name参数的位置写入的不是具体标签名称的字符串形式而是布尔值True,则将显示当前所有标签的信息。
attrs是属性参数,用来指定标签的属性,以值的字符串形式出现。如我们想查找《爱丽丝》文档中所有属性是class=”title”的p标签,就可以通过soup.find_all(‘p’,‘title’)来实现:
<b>
The Dormouse's story
</b>
因为在此文档中所有class=“title”的p标签只有一个,所以bs4也只能帮我们找到一个。但如果文档中存在多个,则通过find_all()方法可以将所有符合条件的结果都找到。
recursive参数表示是否对子孙全部检索,默认为True。但假设我们只想对a标签所在位置进行检索,而不进入标签树的下层,则可以通过soup.find_all(‘a’,recursive=False)实现。
string参数为<>…</>中字符串区域的检索字符串。通过约定这一参数,我们可以对文章内容进行直接检索,如soup.find_all(string=‘Once upon a time there were three little sisters;and their names were)’,则可将文档中与此字符串完全匹配的其他字符串找到。并且我们可以通过和另一个模块——re(正则表达式)的方法[complie()方法]组合使用,获得更强大的功能。
find_all()方法是bs4库中最常被使用到的检索方法,请大家务必掌握。以下我们也整理了其他常用的检索方法供大家参考(表11-2)
表11-2 其他常用检索方法
续表
灵活利用requets库和bs4库,就可以实现对复杂网页内容的请求及解析,其基本代码框架如图11-11所示。其中find()方法与find_all()方法的具体使用可以根据实际情况进行灵活选择,对其内部参数的调用也是如此。
图11-11 基本代码框架
此外,在此段代码中还出现了for...in...,这是Python中循环语句的一种。Python提供了两种循环的方式,另一种是通过while来实现(可参考“延伸与讨论”部分)。这里仅就出现的for循环语句做解释。
有时候我们需要遍历一组东西,例如单词列表、文件的每一行、一组名单或者数字等。遍历一组东西,可以用for语句来构造循环。for语句是对已知的数据项集合进行循环,因此数据集内的数据个数控制了循环的结束。
下面是一个for语句和循环体代码示例及运行结果(如图11-12)。
图11-12 for循环
friends变量是一个包含三个字符串的列表。从结果可以看出,for循环遍历整个列表,将列表中的每个元素都提取出来并赋值给了变量friend一次,最终依次打印出这三个字符串。其中friends和friend都是自定义的变量名称,但一般我们习惯把关键词for后跟的变量命名为i。具体来说,friend是for循环的迭代变量。变量friend的值在每次迭代时都会改变,并控制for循环什么时候结束。这个迭代变量的取值是friends中存储的三个字符串。你可以把这个for循环当做是一个名册,那么这段代码的作用是:对friends集合中的每个名字执行for循环体。for循环的循环体也是通过缩进实现的,所以print(‘完毕’)并不会出现三次。请注意在写for循环时不要忘了冒号(当输入冒号再回车换行时,程序其实会自动进行缩进)。
此外,如果你需要抓取的信息不仅仅是一个网页,而是一个网站或更多内容时,scrapy库会是一个更好的选择。scrapy库能够帮助我们快速编写一个中型爬虫,感兴趣的同学可以自行学习。
11.2.4 用jieba库所获得的中文信息
通过requests库和bs4库的配合使用可以实现网页指定信息的提取。当我们取得所需信息后,就可以通过数据分析相关章节所介绍的方法进行分析。但在直接进入分析前,Python还可以对信息文本做一些处理以方便分析的进行。在日常接触的网页内容中,最常见的是中文和英文两种文本形式。由于两者在书写格式上的不同,Python在处理英文文本时可以通过内置函数直接进行,而在处理中文文本时则需要借助第三方中文分词模块进行。本节主要介绍其中一种中文分词模块——jieba。因为jieba的目标是:做最好的 Python 中文分词组件。
(jieba页面)
中文分词(Chinese word segmentation)指的是将一个中文文本(由汉字组成的序列)切分成单独的词。在英文的书写规范里,单词之间通过空格自然分割。而中文只有到句和段时才会出现明显的分割标识符(如句号、换行符等),字与词之间没有清晰分割的标识符。那么像jieba这样的第三方中文分词模块是如何运作的呢?我们可以简单地把jieba想象成一本厚厚的现代中文大词典,Python会捧着它一个个去对照待分析文本中出现的字,并将能在词典中找到的词切割出来。
因为jieba是第三方模块,所以在使用前需要通过pip进行安装。jieba支持三种分词模式:(1)精确模式。试图将句子最精确地切开,以适合文本分析;(2)全模式。把句子中所有的可以成词的词语都扫描出来,速度非常快,但是不能解决歧义;(3)搜索引擎模式。在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。结合官网上的例子,我们一起来看一下jieba是如何实现这三种分词模式的(图11-13)。
图11-13 分词模式及对应结果
其中,jieba.cut()方法接受三个参数:(1)需要分词的字符串;(2)cut_all 参数用来控制是否采用全模式;(3)HMM 参数用来控制是否使用 HMM 模型。jieba.cut_for_search 方法接受两个参数:(1)需要分词的字符串;(2)是否使用 HMM 模型,该方法适合用于搜索引擎构建倒排索引的分词。jieba.cut()以及 jieba.cut_for_search ()返回的结构都是可迭代的,可以使用 for 循环来获得分词后得到的每一个词语。也可以通过jieba.lcut ()以及 jieba.lcut_for_search()方法直接返回所有词语的列表形式。
虽然jieba 有新词识别能力,可以将一部分在“词典”中未载入的词识别出来,但我们依旧鼓励大家制定自己自定义的词典,以便包含 jieba 词库里没有的词,从而保证更高的分词正确率。我们只需要先建立一个txt文件,按照规定的词典格式输入新词。一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开即可(顺序不可颠倒)。然后通过以下语句在Python中来实现:
jieba.load_userdict(file_name)# file_name 为文件类对象或自定义词典的路径
除了基本分词之外,jieba还提供了分词标注功能,它可以帮助我们标注句子分词后每个词的词性,如图11-14所示。
图11-14 词性标注及对应结果
此外,jieba还提供了很多其他功能以便于对文本进行分析,如基于 TF-IDF 算法、TextRank 算法的关键词抽取,并展示其共现关系等,这里就不再一一介绍。需要注意的是,虽然jieba可以支持GBK字符串,但一般建议使用UTF-8字符串,因为直接输入 GBK 字符串,存在会被程序错误解码成UTF-8的风险,导致乱码的出现。
以下是一个实例。某高校人事处在疫情期间开展针对教师的线上教学培训,事后发放问卷以便得到教师反馈。其中有开放题型一题,得到文字反馈。通过jieba相关功能,我们可以快速地对文本进行处理并得到初步结果(图11-16)。
图11-16 中文处理及结果
从结果来看,出现频率多的词不见得是同等重要的关键词。这告诉我们对文本的分析不应仅局限在对词频的统计上。掌握更多的方法能够更好地帮助我们进行研究,比如在结果中出现了“可以”“一些“这样的词,我们可以通过停用词加以约定去除。更多中文文本的分析功能,请参看jieba官网解释。