写出你的第一个小爬虫
在上一篇文章中对Python有了一定的基础学习后,我们现在要开始对网页进行爬取啦。
这次我们要爬取的网站是中国移动集团的运维案例文章和评论内容。这篇文章中将会涉及到GET请求和POST请求,以及BeautifulSoup的使用。
扩展模块的安装
要让python可以对网页发起请求,那就需要用到requests之类的包。我们可以用命令行来下载安装,打开cmd,直接输入命令就会自动下载安装,非常的方便。
pip install requests
既然用到了pip,那就顺便解释一下这个东东。
pip 是 Python 著名的包管理工具,在 Python 开发中必不可少。一般来说当你安装完python后,pip也会自动安装完毕,可以直接享用,十分鲜美。
附上一些常用的pip命令
# 查看pip信息
pip –version
#升级pip
pip install -U pip
# 查看已经安装的包
pip list
# 安装包
Pip install PackageName
# 卸载已经安装好的包
Pip uninstall PackageName
以上这四个命令非常的实用,我在做这个爬虫期间有多次使用到。
在安装完requests包后,还需要再安装一个神器BeautifulSoup,配合lxml库,这是最近非常流行的两个库,这个是用来解析网页的,并提供定位内容的便捷接口,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器
语法使用类似于XPath。通过官方的文档的阅读,很容易上手。
安装方式:
- BeautifulSoup的安装
pip install beautifulsoup4
- lxml的安装
pip install lxml
不报错即为安装成功
安装完上面三个包后,就开始制作我们的第一个小爬虫吧
我们先来分析一下我们这次要爬取数据的网站数据。可以使用chrome浏览器自带的工具来抓包,按F12,选择Network,就可以看到所有的请求内容了。当然也可以用Fiddler这个优秀的工具来抓包,还能对请求进行截获并修改参数,大家有时间的话可以去玩一玩。这儿因为方便,我就采用了chrome浏览器自带的来进行分析了。
根据这个请求,我们能看出这个是一个GET请求。我们将headers里的内容绑定到类的属性里,接着绑定一个请求的地址。
import requests #导入requests 模块
from bs4 import BeautifulSoup #导入BeautifulSoup 模块
# 爬取文章案例编号和当前周期有效阅读数
class BeautifulGetCaseSN():
def __init__(self): #类的初始化操作
#头部信息
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language':'zh-CN,zh;q=0.8',
'Connection':'keep-alive',
'Cookie':'JSESSIONID=48A82C60BE65ED14C5978297C03AF776; PHPSESSID=ST-10-QybrBt31XVfPBqKAT2jr'
}
#要访问的网页地址
self.web_url = 'http://net.chinamobile.com/cmccnkms-case/web/casesPortal/getCaseInfor.action?caseId=75058'
接下来我们再来分析一下要爬取的内容,下图中的三个框是我们要爬取的目标内容,分别是标题,当前周期有效阅读数,案例编号。
分别右击审查元素,分析一下HTML的结构,以方便用BeautifulSoup来解析。
看了一遍这个网页的HTML,发现写这个网站的人真的是随意发挥,哈哈哈,都是直接用标签对的,标签class属性或者id属性几乎都没有,还好我们现在有了神器Beautifulsoup再手,根本不用愁无从下手这种的事儿。
通过分析,我们发现标题和当前有效周期的父级标签都是<td width=“78%”>
,而且我搜索了下,发现width=“78%”
属性只有这两个标签有。那么好办了,利用Beautifulsoup能对CSS属性解析的特性,我们就从这个属性下手。接着通过对案例编号的分析,我们发现这个<td class=“txleft”>
标签有一个class属性,那就根据这个属性进行获取。
def get_data(self):
print('开始文章基础信息GET请求')
r = requests.get(self.web_url, headers=self.headers)
all_soup = BeautifulSoup(r.text, 'lxml')
caseSn = all_soup.find_all('td','txleft') #案例编号抓取
print(caseSn[2].text)
all_a = all_soup.find_all('td',width='78%')
title = all_a[0].find('h4').text #标题抓取
print(title)
read_num = all_a[1].find_all('span')
print(read_num[3].text) #有效阅读数抓取
解释一下这段代码:
r = requests.get(self.web_url, headers=self.headers)
all_soup = BeautifulSoup(r.text, 'lxml')
对网站进行GET请求,GET请求需要的参数有请求地址和头部信息,然后将获取的文本放入BeautifulSoup进行解析。这儿顺带说一句,python语言真的是人生苦短啊,请求网址,解析网页2句话就能完成,简洁的不得了,当然这个BeautifulSoup我为了逻辑清楚分开写了,不然也是能用一句代码来完成的。
caseSn = all_soup.find_all('td','txleft') #案例编号抓取
print(caseSn[2].text)
解析获取所有class类名为txleft
的td标签,然后发现我们需要的案例编号是在第三个tag中,获取这个tag的文本内容。
all_a = all_soup.find_all('td',width='78%')
title = all_a[0].find('h4').text #标题抓取
print(title)
read_num = all_a[1].find_all('span')
print(read_num[3].text) #有效阅读数抓取
通过打断点的方式,我们可以看到解析获取所有宽度属性为78%
的td标签,一共有2个tag集,再在第1个标签集中解析获取为h4的标签,这个全文只存在唯一的一个,所以就获取到了我们所需要的文章标题。有效阅读数获取同理,在第2个标签集继续中解析获取叫span的标签,有效阅读数的内容藏在第四个span标签中。
关于BeautifulSoup的find_all()
和find()
的官方使用说明:
find_all(name, attrs, recursive, text, **kwargs)
find_all()方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.
find( name , attrs , recursive , text , **kwargs )
find_all() 方法将返回文档中符合条件的所有tag,尽管有时候我们只想得到一个结果.比如文档中只有一个<body>标签,那么使用 find_all() 方法来查找<body>标签就不太合适, 使用 find_all 方法并设置 limit=1 参数不如直接使用 find() 方法.
现在让我们来实例化一个爬虫,满怀憧憬的按下F5,让他跑起来。
getCaseSN = BeautifulGetCaseSN() #创建类的实例,根据caseId爬取文章基础信息
getCaseSN.get_data()
print('爬取具体信息over')
在调试控制台里查看结果。
哇!爬取数据成功!第一个小爬虫诞生了。
然鹅!现在还不能开始庆祝,毕竟任务才进行到一半。接下来,我们根据需求,还需要爬取文章的评论者的名字做爬取统计。继续对网页进行分析。
需要对上图中的评论者姓名进行爬取,而且还需要做到翻页。
通过对请求的分析,发现这个评论块是个独立的请求,然后加入到文章页面的<frame>标签块中。点进去发现这是个POST请求,带有Form数据。通过对数据分析,发现有3个元素,第一个是评论的排序方式,我们不用动他,第二个是页码,就是翻页的关键参数,第三个是文章的Id。
开始构建POST请求
# 爬取具体信息
class BeautifulGetData():
def __init__(self): #类的初始化操作
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0',
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language':'zh-CN,zh;q=0.8',
'Connection':'keep-alive',
'Cookie':'JSESSIONID=48A82C60BE65ED14C5978297C03AF776; PHPSESSID=ST-10-QybrBt31XVfPBqKAT2jr''
}
#要访问的网页地址
self.web_url = 'http://net.chinamobile.com/cmccnkms-case/web/casesPortal/loadComments.action'
def get_data(self):
print('开始文章评论内容POST请求')
print('具体评论获取\n')
for x in range(1,10):
#post请求的数据
web_data = {
'sort':'desc',
'pageNum':x,
'caseId':75058
}
r = requests.post(self.web_url,data=web_data, headers=self.headers)
这里增加一个for循环就是为了模拟请求的页码,根据pageNum的不同,对该网址进行多次请求获取不同页面信息。
通过分析评论页的HTML数据,我发现每个评论都用<div class=“month”>包含,于是我们可以用find_all
来获取全部的这个class,因为每页都有五个评论,所以可以用for循环来进行分析并输出。
下面是完整的请求代码
def get_data(self):
print('开始文章评论内容POST请求')
print('具体评论获取\n')
get_name = []
get_next_name = []
for x in range(1,10):
web_data = {
'sort':'desc',
'pageNum':x,
'caseId':75058
}
r = requests.post(self.web_url,data=web_data, headers=self.headers)
all_a = BeautifulSoup(r.text, 'lxml').find_all('div','month')
print('第',x,'页')
#将上页获取的评论记录并清空当前页
get_name = get_next_name
get_next_name = []
for a in enumerate(all_a):
str_name = a[1].text.split(':') #对获取的文本内容做切割
get_next_name.append(str_name[0]) #将名字加入到当前获取记录中
if get_name == get_next_name:
print('完成')
break
else:
for a in get_next_name:
print(a)
这里说一下我定义的两个list:get_name
和get_next_name
。之所以定义这两个是因为每篇文章的评论数量我是不知道的,所以我不能直接控制需要爬取的页数,只能尽可能大的写一个数。但是当页数小于我设定的页码值后会发生如下的数据重复显示事件。
于是我加入了这两个参数,来存放前一页的获取的数据,如果单页获取的数据与前一页获取的数据相同,那说明就是到了评论的最后一页,直接跳出循环,结束该篇文章的评论爬取。
好了,把这两个类实例化一下,然后开始run起来吧。
getCaseSN = BeautifulGetCaseSN() #创建类的实例,根据caseId爬取文章基础信息
getData = BeautifulGetData() #创建类的实例,根据caseId爬取文章评论信息
getCaseSN.get_data()
getData.get_data()
成功获取到了我希望得到的目标数据,完美!
后记
但是,只对一篇固定文章的爬取,远远不是我的最终目的,我的目的是,导入一份需要爬取的表格,然后进行自动的爬取,并将获取的数据输出并保存下来。在下一篇文章中,我就来讲一讲,Excel文件的导入读取与文件的导出保存。