3.3 阶段案例——半自动爬虫开发
所谓半自动爬虫,顾名思义就是一半手动一半自动地进行爬虫,手动的部分是把网页的源代码复制下来,自动的部分是通过正则表达式把其中的有效信息提取出来。
3.3.1 需求分析
在百度贴吧中任意寻找一个贴吧并打开一个热门帖子,将帖子的源代码复制下来,并保存为source.txt。Python读入这个source.txt文件,通过正则表达式获取用户名、发帖内容和发帖时间,并保存为result.csv。
涉及的知识点如下。
(1)在浏览器中查看网站的源代码。
(2)使用Python读文本文件。
(3)正则表达式的应用。
(4)先抓大再抓小的匹配技巧。
(5)使用Python写CSV文件。
3.3.2 核心代码构建
1.在浏览器中获取网页的源代码
以Chrome浏览器为例来说明如何查看网页的源代码。在网页上单击鼠标右键,选择“显示网页源代码”命令,如图3-25所示。
图3-25 选择“显示网页源代码”命令
网页源代码如图3-26所示。这里可以复制全部的源代码,并粘贴到记事本中。
图3-26 网页源代码
2.获取关键信息
要获取网站的关键信息,就需要观察网页源代码的规律。通过对比每一层楼的帖子,可以发现规律,即每一层楼都是从“user name”开头的,如图3-27所示。
图3-27 用户名的规律
先来看用户名,从图3-27可以看出,用户名符合这样的规律:
username="(.*? )"
再来看发帖内容,从图3-28可以看出,发帖内容符合如下规律:
图3-28 发帖内容的规律
d_post_content j_d_post_content ">(.*? )<
最后来看发帖时间。请注意,从图3-29中可以看出,发帖时间需要应用3.1.3小节所提到的“括号内和括号外”技巧。由于方框框住的两段内容有着相同的开头字符串,如何把“2017-03-18 19:39”提取出来,但不要把“15楼”提取出来?这种情况下,对于正则表达式,应该在括号里面包含一些其他的要素,才能只把时间提取出来。
由于发帖时间总是以年开头的,因此可以在括号里面包含“2017”,这样就可以得到正确的年份信息。匹配年份的正则表达式如下:
tail-info">(2017.*? )<
3.3.3 调试与运行
完整的半自动爬虫代码如图3-30所示,生成的CSV文件如图3-31所示。
图3-30 完整的半自动爬虫代码
图3-31 爬虫生成的CSV文件
之所以会有一些人的回帖内容是空白的,是因为他的回帖是图片,而代码里面的正则表达式只能提取文字,因此回帖内容为空。这是正常情况。
那这么说来,是不是半自动爬虫已经轻轻松松被写出来了呢?先别着急,虽然这一次代码的结果没有问题,但是它的逻辑有问题。
代码的逻辑是,首先分别获取所有的用户名,接着分别获取所有的帖子内容,然后分别获取所有的回帖时间,最后“按顺序”拼在一起。爬虫认为,用户名列表(username_list)里面的第1个人就是帖子列表(content_list)里面的第1个帖子的发帖人,发帖时间刚好也是时间列表(reply_time_list)里面的第1个时间。这里看起来一一对应,但实际上,这仅仅是表象。
一个帖子一页是30楼,这3个列表理论上都应该有30个元素。那如果帖子里面,有一个人的帖子是在2016年回的呢?这种情况可使用如下的正则表达式:
reply_time_list = re.findall('class="tail-info">(2017.*? )<', source, re.S)
该正则表达式只能得到所有的2017年的回帖时间,所以reply_time_list的元素比其他两个列表都要少。源代码使用用户名列表的长度来作为循环的计数器,必然会导致reply_time_list超出范围而报错。
为了避免这个问题,就需要使用先抓大再抓小的技巧。把每一层楼看作一个“块”,先把每一层楼都抓取下来,再在此基础上从每一层楼里面分别获取用户名、帖子内容和发帖时间,如图3-32所示。
图3-32 把帖子的每一层楼看作一个块
通过分析网站的源代码可以发现,每一层楼在源代码里面是从如下的代码开始的:
<div class="l_post l_post_bright j_l_post clearfix "
而在一层楼结束的地方,在靠近下一层楼的前面可以找一个比较特殊的字符串来作为标志。比如,下一层稍微靠前一点的地方可以看到:
<ul class="p_props_tail props_appraise_wrap">
就可以将其作为结束标志,开始和结束标志如图3-33所示。
图3-33 一层楼的开始和结束标志
对原来的代码进行修改,可以得到逻辑更加合理的新代码,如图3-34所示。
图3-34 更合乎逻辑的半自动爬虫代码