寫出你的第一個小爬蟲
在上一篇文章中對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文件的導入讀取與文件的導出保存。