寫出你的第一個(gè)小爬蟲
在上一篇文章中對(duì)Python有了一定的基礎(chǔ)學(xué)習(xí)后,我們現(xiàn)在要開始對(duì)網(wǎng)頁進(jìn)行爬取啦。
這次我們要爬取的網(wǎng)站是中國移動(dòng)集團(tuán)的運(yùn)維案例文章和評(píng)論內(nèi)容。這篇文章中將會(huì)涉及到GET請(qǐng)求和POST請(qǐng)求,以及BeautifulSoup的使用。
擴(kuò)展模塊的安裝
要讓python可以對(duì)網(wǎng)頁發(fā)起請(qǐng)求,那就需要用到requests之類的包。我們可以用命令行來下載安裝,打開cmd,直接輸入命令就會(huì)自動(dòng)下載安裝,非常的方便。
pip install requests
既然用到了pip,那就順便解釋一下這個(gè)東東。
pip 是 Python 著名的包管理工具,在 Python 開發(fā)中必不可少。一般來說當(dāng)你安裝完python后,pip也會(huì)自動(dòng)安裝完畢,可以直接享用,十分鮮美。
附上一些常用的pip命令
# 查看pip信息
pip –version
#升級(jí)pip
pip install -U pip
# 查看已經(jīng)安裝的包
pip list
# 安裝包
Pip install PackageName
# 卸載已經(jīng)安裝好的包
Pip uninstall PackageName
以上這四個(gè)命令非常的實(shí)用,我在做這個(gè)爬蟲期間有多次使用到。
在安裝完requests包后,還需要再安裝一個(gè)神器BeautifulSoup,配合lxml庫,這是最近非常流行的兩個(gè)庫,這個(gè)是用來解析網(wǎng)頁的,并提供定位內(nèi)容的便捷接口,API非常人性化,支持CSS選擇器、Python標(biāo)準(zhǔn)庫中的HTML解析器,也支持 lxml 的 XML解析器
語法使用類似于XPath。通過官方的文檔的閱讀,很容易上手。
安裝方式:
- BeautifulSoup的安裝
pip install beautifulsoup4
- lxml的安裝
pip install lxml
不報(bào)錯(cuò)即為安裝成功
安裝完上面三個(gè)包后,就開始制作我們的第一個(gè)小爬蟲吧
我們先來分析一下我們這次要爬取數(shù)據(jù)的網(wǎng)站數(shù)據(jù)。可以使用chrome瀏覽器自帶的工具來抓包,按F12,選擇Network,就可以看到所有的請(qǐng)求內(nèi)容了。當(dāng)然也可以用Fiddler這個(gè)優(yōu)秀的工具來抓包,還能對(duì)請(qǐng)求進(jìn)行截獲并修改參數(shù),大家有時(shí)間的話可以去玩一玩。這兒因?yàn)榉奖悖揖筒捎昧薱hrome瀏覽器自帶的來進(jìn)行分析了。

根據(jù)這個(gè)請(qǐng)求,我們能看出這個(gè)是一個(gè)GET請(qǐng)求。我們將headers里的內(nèi)容綁定到類的屬性里,接著綁定一個(gè)請(qǐng)求的地址。
import requests #導(dǎo)入requests 模塊
from bs4 import BeautifulSoup #導(dǎo)入BeautifulSoup 模塊
# 爬取文章案例編號(hào)和當(dāng)前周期有效閱讀數(shù)
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'
}
#要訪問的網(wǎng)頁地址
self.web_url = 'http://net.chinamobile.com/cmccnkms-case/web/casesPortal/getCaseInfor.action?caseId=75058'
接下來我們?cè)賮矸治鲆幌乱廊〉膬?nèi)容,下圖中的三個(gè)框是我們要爬取的目標(biāo)內(nèi)容,分別是標(biāo)題,當(dāng)前周期有效閱讀數(shù),案例編號(hào)。

分別右擊審查元素,分析一下HTML的結(jié)構(gòu),以方便用BeautifulSoup來解析。


看了一遍這個(gè)網(wǎng)頁的HTML,發(fā)現(xiàn)寫這個(gè)網(wǎng)站的人真的是隨意發(fā)揮,哈哈哈,都是直接用標(biāo)簽對(duì)的,標(biāo)簽class屬性或者id屬性幾乎都沒有,還好我們現(xiàn)在有了神器Beautifulsoup再手,根本不用愁無從下手這種的事兒。
通過分析,我們發(fā)現(xiàn)標(biāo)題和當(dāng)前有效周期的父級(jí)標(biāo)簽都是<td width=“78%”>,而且我搜索了下,發(fā)現(xiàn)width=“78%”屬性只有這兩個(gè)標(biāo)簽有。那么好辦了,利用Beautifulsoup能對(duì)CSS屬性解析的特性,我們就從這個(gè)屬性下手。接著通過對(duì)案例編號(hào)的分析,我們發(fā)現(xiàn)這個(gè)<td class=“txleft”>標(biāo)簽有一個(gè)class屬性,那就根據(jù)這個(gè)屬性進(jìn)行獲取。
def get_data(self):
print('開始文章基礎(chǔ)信息GET請(qǐng)求')
r = requests.get(self.web_url, headers=self.headers)
all_soup = BeautifulSoup(r.text, 'lxml')
caseSn = all_soup.find_all('td','txleft') #案例編號(hào)抓取
print(caseSn[2].text)
all_a = all_soup.find_all('td',width='78%')
title = all_a[0].find('h4').text #標(biāo)題抓取
print(title)
read_num = all_a[1].find_all('span')
print(read_num[3].text) #有效閱讀數(shù)抓取
解釋一下這段代碼:
r = requests.get(self.web_url, headers=self.headers)
all_soup = BeautifulSoup(r.text, 'lxml')
對(duì)網(wǎng)站進(jìn)行GET請(qǐng)求,GET請(qǐng)求需要的參數(shù)有請(qǐng)求地址和頭部信息,然后將獲取的文本放入BeautifulSoup進(jìn)行解析。這兒順帶說一句,python語言真的是人生苦短啊,請(qǐng)求網(wǎng)址,解析網(wǎng)頁2句話就能完成,簡潔的不得了,當(dāng)然這個(gè)BeautifulSoup我為了邏輯清楚分開寫了,不然也是能用一句代碼來完成的。
caseSn = all_soup.find_all('td','txleft') #案例編號(hào)抓取
print(caseSn[2].text)
解析獲取所有class類名為txleft的td標(biāo)簽,然后發(fā)現(xiàn)我們需要的案例編號(hào)是在第三個(gè)tag中,獲取這個(gè)tag的文本內(nèi)容。
all_a = all_soup.find_all('td',width='78%')
title = all_a[0].find('h4').text #標(biāo)題抓取
print(title)
read_num = all_a[1].find_all('span')
print(read_num[3].text) #有效閱讀數(shù)抓取
通過打斷點(diǎn)的方式,我們可以看到解析獲取所有寬度屬性為78%的td標(biāo)簽,一共有2個(gè)tag集,再在第1個(gè)標(biāo)簽集中解析獲取為h4的標(biāo)簽,這個(gè)全文只存在唯一的一個(gè),所以就獲取到了我們所需要的文章標(biāo)題。有效閱讀數(shù)獲取同理,在第2個(gè)標(biāo)簽集繼續(xù)中解析獲取叫span的標(biāo)簽,有效閱讀數(shù)的內(nèi)容藏在第四個(gè)span標(biāo)簽中。
關(guān)于BeautifulSoup的find_all()和find()的官方使用說明:
find_all(name, attrs, recursive, text, **kwargs)
find_all()方法搜索當(dāng)前tag的所有tag子節(jié)點(diǎn),并判斷是否符合過濾器的條件.
find( name , attrs , recursive , text , **kwargs )
find_all() 方法將返回文檔中符合條件的所有tag,盡管有時(shí)候我們只想得到一個(gè)結(jié)果.比如文檔中只有一個(gè)<body>標(biāo)簽,那么使用 find_all() 方法來查找<body>標(biāo)簽就不太合適, 使用 find_all 方法并設(shè)置 limit=1 參數(shù)不如直接使用 find() 方法.
現(xiàn)在讓我們來實(shí)例化一個(gè)爬蟲,滿懷憧憬的按下F5,讓他跑起來。
getCaseSN = BeautifulGetCaseSN() #創(chuàng)建類的實(shí)例,根據(jù)caseId爬取文章基礎(chǔ)信息
getCaseSN.get_data()
print('爬取具體信息over')

在調(diào)試控制臺(tái)里查看結(jié)果。
哇!爬取數(shù)據(jù)成功!第一個(gè)小爬蟲誕生了。
然鵝!現(xiàn)在還不能開始慶祝,畢竟任務(wù)才進(jìn)行到一半。接下來,我們根據(jù)需求,還需要爬取文章的評(píng)論者的名字做爬取統(tǒng)計(jì)。繼續(xù)對(duì)網(wǎng)頁進(jìn)行分析。

需要對(duì)上圖中的評(píng)論者姓名進(jìn)行爬取,而且還需要做到翻頁。

通過對(duì)請(qǐng)求的分析,發(fā)現(xiàn)這個(gè)評(píng)論塊是個(gè)獨(dú)立的請(qǐng)求,然后加入到文章頁面的<frame>標(biāo)簽塊中。點(diǎn)進(jìn)去發(fā)現(xiàn)這是個(gè)POST請(qǐng)求,帶有Form數(shù)據(jù)。通過對(duì)數(shù)據(jù)分析,發(fā)現(xiàn)有3個(gè)元素,第一個(gè)是評(píng)論的排序方式,我們不用動(dòng)他,第二個(gè)是頁碼,就是翻頁的關(guān)鍵參數(shù),第三個(gè)是文章的Id。
開始構(gòu)建POST請(qǐng)求
# 爬取具體信息
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''
}
#要訪問的網(wǎng)頁地址
self.web_url = 'http://net.chinamobile.com/cmccnkms-case/web/casesPortal/loadComments.action'
def get_data(self):
print('開始文章評(píng)論內(nèi)容POST請(qǐng)求')
print('具體評(píng)論獲取\n')
for x in range(1,10):
#post請(qǐng)求的數(shù)據(jù)
web_data = {
'sort':'desc',
'pageNum':x,
'caseId':75058
}
r = requests.post(self.web_url,data=web_data, headers=self.headers)
這里增加一個(gè)for循環(huán)就是為了模擬請(qǐng)求的頁碼,根據(jù)pageNum的不同,對(duì)該網(wǎng)址進(jìn)行多次請(qǐng)求獲取不同頁面信息。

通過分析評(píng)論頁的HTML數(shù)據(jù),我發(fā)現(xiàn)每個(gè)評(píng)論都用<div class=“month”>包含,于是我們可以用find_all來獲取全部的這個(gè)class,因?yàn)槊宽摱加形鍌€(gè)評(píng)論,所以可以用for循環(huán)來進(jìn)行分析并輸出。
下面是完整的請(qǐng)求代碼
def get_data(self):
print('開始文章評(píng)論內(nèi)容POST請(qǐng)求')
print('具體評(píng)論獲取\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,'頁')
#將上頁獲取的評(píng)論記錄并清空當(dāng)前頁
get_name = get_next_name
get_next_name = []
for a in enumerate(all_a):
str_name = a[1].text.split(':') #對(duì)獲取的文本內(nèi)容做切割
get_next_name.append(str_name[0]) #將名字加入到當(dāng)前獲取記錄中
if get_name == get_next_name:
print('完成')
break
else:
for a in get_next_name:
print(a)
這里說一下我定義的兩個(gè)list:get_name和get_next_name。之所以定義這兩個(gè)是因?yàn)槊科恼碌脑u(píng)論數(shù)量我是不知道的,所以我不能直接控制需要爬取的頁數(shù),只能盡可能大的寫一個(gè)數(shù)。但是當(dāng)頁數(shù)小于我設(shè)定的頁碼值后會(huì)發(fā)生如下的數(shù)據(jù)重復(fù)顯示事件。

于是我加入了這兩個(gè)參數(shù),來存放前一頁的獲取的數(shù)據(jù),如果單頁獲取的數(shù)據(jù)與前一頁獲取的數(shù)據(jù)相同,那說明就是到了評(píng)論的最后一頁,直接跳出循環(huán),結(jié)束該篇文章的評(píng)論爬取。
好了,把這兩個(gè)類實(shí)例化一下,然后開始run起來吧。
getCaseSN = BeautifulGetCaseSN() #創(chuàng)建類的實(shí)例,根據(jù)caseId爬取文章基礎(chǔ)信息
getData = BeautifulGetData() #創(chuàng)建類的實(shí)例,根據(jù)caseId爬取文章評(píng)論信息
getCaseSN.get_data()
getData.get_data()

成功獲取到了我希望得到的目標(biāo)數(shù)據(jù),完美!
后記
但是,只對(duì)一篇固定文章的爬取,遠(yuǎn)遠(yuǎn)不是我的最終目的,我的目的是,導(dǎo)入一份需要爬取的表格,然后進(jìn)行自動(dòng)的爬取,并將獲取的數(shù)據(jù)輸出并保存下來。在下一篇文章中,我就來講一講,Excel文件的導(dǎo)入讀取與文件的導(dǎo)出保存。