Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

【CSDN 編者按】程序員刷豆瓣也要刷出技術感,本文爬取豆瓣 TOP250 告訴你這些書“熱門”在哪裡!案例分析之外,重在梳理編寫爬蟲的邏輯和鏈路關鍵點,手把手教你形成自己編寫爬蟲的方法論。

Are you ready? Let's go!

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

作者 | 周志鵬

本文以爬取豆瓣TOP250圖書為例,但重點並不在案例本身,而在於梳理編寫爬蟲的邏輯和鏈路關鍵點,幫助感興趣的童鞋逐漸形成自己編寫爬蟲的方法論(這個是最最最關鍵的),並在實戰中體驗requests+xpath絕妙的感覺。沒錯,這一招requests+xpath(json)真的可以吃遍天。

馬克思曾經沒有說過——當你瀏覽網頁時,看到的只是瀏覽器想讓你看到的。這句話道出了一個真諦——我們看到的網頁只一個結果,而這個網頁總是由眾多小的“網頁(或者說部分)”構成。

就像拼圖,我們看到的網頁其實是最終拼好的成品,當我們輸入一個網址,瀏覽器會"自動"幫我們向遠方的服務器發送構成這個大拼圖的一眾請求,服務器驗證身份後,返回相關的小拼圖(請求結果),而瀏覽器很智能的幫助我們把小拼圖拼成大拼圖。

我們需要的,就是找到網頁中存儲我們數據的那個小拼圖的網址,並進一步解析相關內容。以豆瓣TOP250圖書為例,爬蟲的第一步,就是“審查元素”,找到我們想要爬取的目標數據及其所在網址。

豆瓣TOP250圖書網址:https://book.douban.com/top250

審查元素,找到目標數據所在的URL

審查元素,就是看構成網頁的小拼圖具體是怎麼拼的,操作起來很簡單,進入網頁後,右鍵——審查元素,默認會自動定位到我們所需要的元素位置。

比如我們鼠標移動到“追風箏的人”標題上,右鍵後審查元素:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

其實,我們需要的數據(圖書名稱、評價人數、評分等等)都在一個個網頁標籤中。爬蟲這玩意兒就是剛開始看著繞但用起來很簡單,極簡主義友情提示,不要被複雜的結構迷惑。

在徹底定位目標數據之前,我們先簡單明確兩個概念。

網頁分為靜態和動態,我們可以粗暴理解為,直接右鍵——查看源代碼,這裡顯示的是靜態網址的內容,如果裡面包含我們需要的數據,那直接訪問最初的網址(瀏覽器網址欄的網址)即可。

而有的數據是在我們瀏覽的過程中默默新加載(瀏覽過程中向服務器發送的請求)的,這部分網頁的數據一般藏在JS/XHR模塊裡面。

是這樣操作的:審查元素——刷新網址——NETWORK——JS或者XHR。

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

概念講解到此為止,下面是操作詳解,即我們如何找到我們需要的網址。

一般來說,靜態網頁中的數據比較容易獲取,爬取之前我們養成先查看源代碼的方式檢查所需要的數據是否在靜態網頁中的習慣。

右鍵——查看源代碼:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

驗證一下需要的數據在靜態網址中是否出現,CTRL+F快捷查找,然後輸入我們剛才看到的圖書榜第一的“追風箏的人”。

成功找到!得來全不費工夫,朝下拉,發現需要的所有數據都在靜態網址中,完全不需要去動態網頁中一步步排查了。

嘗試請求,報錯中前行

我們剛才發現目標數據是在靜態網址中,直接訪問https://book.douban.com/top250就可以拿到。下一步,藉助PYTHON中requests的get方法很高效的實現網頁訪問。

import requests
html = requests.get('https://book.douban.com/top250')

requests.get函數第一個參數默認是請求的網址,後面還有headers,cookeis等關鍵字參數,稍後會講。

OK,一個訪問請求完成了,是不是超級簡單?訪問完成後,我們可以看一下返回的內容:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

一般都會講返回的狀態碼以及對應的意義,但個人覺得直接暴力抽看返回內容也很方便。

很尷尬,沒偽裝都訪問成功了,之前驗證headers的豆瓣現在連headers都不驗證了。小豆,你墮落了......但關於爬蟲模擬用戶行為必須敲黑板講重點!!!

當我們用PYTHON對目標網址進行訪問,本質上是用PYTHON對目標服務器發送了一個請求,而這種機器(Python)並不是真實的用戶(通過瀏覽器請求的),這種“虛假”訪問是服務器深惡痛絕的(爬蟲會對網站服務器造成負荷,而且絕大多數網站並不希望數據被批量獲取)。所以,如果想要萬無一失,訪問行為必須模擬,要儘可能的讓你的爬蟲像正常的用戶。最常規的方法就是通過偽裝headers和cookies來讓我們的爬蟲看起來更像人。

繼續舉個例子:

你住在高檔小區,小P這個壞小夥想偽裝你進小區做不可描述的事情。

他知道,門衛會根據身份象徵來模糊判斷是否是小區業主,所以小P先租了一套上檔次的衣服和一輛稱得上身份的豪車(可以理解為偽裝headers),果然混過了門衛。但是呢,小P進進出出太頻繁,而且每次停車區域都不一樣,引起了門衛的嚴重懷疑,在一個星期後,門衛升級檢驗系統,通過人臉識別來驗證,小P被拒絕在外,但很快,小P就通過毀容級別的化妝術(偽裝cookies),完全偽裝成你,竟然混過了人臉識別系統,隨意出入,為所欲為。

此處主要是形象化科普,其實還有IP等更復雜的偽裝方式,感興趣的同學可以自己研究。

所以,為了保險起見,我們還是構造偽裝成正常瀏覽器的請求頭。

找到靜態網址和其關鍵參數(請求頭headers相關參數都在Request Headers下):

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

requests庫會有默認的headers,關鍵參數都是字典格式,這裡我們把瀏覽器的參數賦值給headers,從請求頭的角度偽裝成正常瀏覽器。

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
#再來請求一次
html = requests.get('https://book.douban.com/top250',headers = headers)
html.text[:200]
#成功返回結果

請求成功,解析並定位到我們需要的數據

剛才我們拿到了正確的返回結果html,所有的內容都包在html.text裡,我們需要對這一大包內容進行解析和定位,xpath就登場了。

操作如下:

from lxml import etree
bs = etree.xpath(html.text)

#這一步把HTML的內容解析成xpath方法可以很容易定位的對象,其實就是為定位數據做好準備的必要一步,記住這步必要的操作。

先舉個例子講解用法:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

注:xpath的索引都是從1開始的,謹記謹記!

這裡縮進代表從屬關係,我們想要的作者信息被包裹在

的標籤內,從下到上又從屬於上一級的div標籤——td標籤——tr標籤。

怎麼用xpath獲取?

bs.xpath('//tr[@class = "item"]/td[2]/p')[0].text

//代表從根目錄(從上到下)開始定位(初始定位都需要這樣),tr[@class = "item"]表示找到class等於item標籤,因為可能會有很多個tr標籤,我們通過class屬性來區分,tr後面的/代表下一級,td[2]表示定位到第二個td標籤,然後再下一級的p標籤,並獲取他的內容text。

如果是要獲取p裡面class的值:

bs.xpath('//tr[@class = "item"]/td[2]/p/@class'))[0]

還沒完全理解?不要緊,用一下就會了,Let's do it!

鼠標移動到"追風箏的人”作者名上,右鍵——審查元素:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

關於書籍作者相關的信息都被包裹在p標籤中,而p標籤又從屬於td-tr-tbody-table。

再進一步點擊,書名、評價數、評分等相關信息也都是在類似的鏈路,所有的信息都被包裹在tr[@class = "item"]這一級,雖然再往上一級的tbody和table也可以,但只要定位到tr就夠了。

我們來定位一下:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

返回了一系列對象,數一數(這個地方沒截全),正好25個,對應著我們在瀏覽器中看到的25本書的相關信息。我們先用第一個對象,來嘗試定位需要的關鍵信息。

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

注:我們定位了第一個元素,書籍相關信息都被包裹著(從屬於他),我們想要進一步定位,很簡單,直接調用xpath方法,而且開頭不需要加//:

書籍名:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

作者、出版社信息:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

細心的同學一定發現了,定位返回的值是一個列表,我們通過索引[0]來提取裡面的內容,至於定位元素,我們既可以通過序號(td[2]/div[1]這樣),也可以通過對屬性進行賦值(p[@class = "pl"])來判斷。

Emmm,有一個疑惑點非常容易對人造成困擾,就是定位了之後,我們怎麼提取我們需要的信息,舉個例子:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

大家會發現,作者相關信息是被包裹在

之間的,具體的HTML語法不用管,我們只要明白,標籤中間的內容是文本,而標籤內的內容(class或者其他名字)叫屬性,xpath提取文本是通過 .xpath('p[@class = "pl"]')[0].text方法。而想要拿到標籤內的屬性(這裡是pl) 是用 ('p[@class = "pl"]/@class') 方法提取。

下面,繼續提取關鍵信息:

評分:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

評價人數:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

一句話概括:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

OKAY,我們把定位提取操作彙總一下:

#這裡創建一個DATAFRAME來存儲最終結果
result = pd.DataFrame()
for i in bs.xpath('//tr[@class = "item"]'):
#書籍中文名
book_ch_name = i.xpath('td[2]/div[1]/a[1]/@title')[0]
#評分
score = i.xpath('td[2]/div[2]/span[2]')[0].text
#書籍信息
book_info = i.xpath('td[2]/p[@class = "pl"]')[0].text

#評價數量由於數據不規整,這裡用PYTHON字符串方法對數據進行了處理
comment_num = i.xpath('td[2]/div[2]/span[3]')[0].text.replace(' ','').strip('(\n').strip('\n)')
#一句話概括
brief = i.xpath('td[2]/p[@class = "quote"]/span')[0].text
#這裡的cache是存儲每一次循環的結果,然後通過下一步操作循環更新result裡面的數據
cache = pd.DataFrame({'中文名':[book_ch_name],'評分':[score],\
'書籍信息':[book_info],'評價數量':[comment_num],'一句話概括':[brief]})
#把新循環中的cache添加到result下面
result = pd.concat([result,cache])

結果像醬紫(截取了部分):

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

呼~最關鍵的數據定位,數據獲取已經完成了。

翻頁爬取,盡收囊中

俗話說的好,拿一血者拿天下。我們已經拿到了第一頁的數據,其他頁面數據都是一樣的結構,最後只需要我們找到網頁變化的規律,實現翻頁爬取。

我們在瀏覽器中翻到下一頁(第二頁)。噹噹噹當!瀏覽器網址欄的網址的變化你一定發現了!

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

再下一頁(第三頁):

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

我們有理由進行推斷,網址前面的結構都是不變的,決定頁數的關鍵參數是start後面的數字,25代表第二頁,50代表第三頁,那一頁就是0,第四頁就是75,以此類推。

來來回回翻頁,不出所料,翻頁的關鍵就是start參數的變化。所以,我們只需要設置一個循環,就能夠構造出所有的網址。

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

至此,各個模塊的代碼可謂大功告成!

最後,先來一個TOP50詞雲版書單,再附上完整代碼來拋磚引玉:

Python 爬蟲分析豆瓣 TOP250 告訴你程序員業餘該看什麼書?

import pandas as pd
import requests
from lxml import etree
import time
#循環構造網址
def format_url(base_url,pages = 10):
urls = []
for num in range(0,pages * 25,25):
urls.append(base_url.format(num))
return urls
#解析單個頁面
def parse_page(url,headers):
#創建一個存儲結果的容器
result = pd.DataFrame()
html = requests.get(url,headers = headers)
bs = etree.HTML(html.text)
for i in bs.xpath('//tr[@class = "item"]'):
#書籍中文名
book_ch_name = i.xpath('td[2]/div[1]/a[1]/@title')[0]
#評分
score = i.xpath('td[2]/div[2]/span[2]')[0].text
#書籍信息
book_info = i.xpath('td[2]/p[@class = "pl"]')[0].text
#評價數量由於數據不規整,這裡用PYTHON字符串方法對數據進行了處理
comment_num = i.xpath('td[2]/div[2]/span[3]')[0].text.replace(' ','').strip('(\n').strip('\n)')
try:
#後面有許多書籍沒有一句話概括
#一句話概括
brief = i.xpath('td[2]/p[@class = "quote"]/span')[0].text
except:
brief = None
#這裡的cache是存儲每一次循環的結果,然後通過下一步操作循環更新result裡面的數據
cache = pd.DataFrame({'中文名':[book_ch_name],'評分':[score],\
'書籍信息':[book_info],'評價數量':[comment_num],'一句話概括':[brief]})
#把新循環中的cache添加到result下面

result = pd.concat([result,cache])
return result
def main():
final_result = pd.DataFrame()
base_url = 'https://book.douban.com/top250?start={}'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
urls = format_url(base_url,pages = 10)
for url in urls:
res = parse_page(url,headers = headers)
final_result = pd.concat([final_result,res])
#爬蟲要文明,這裡設置了一個愛心520時間
time.sleep(5.2)
return final_result
if __name__ == "__main__":
final_result = main()

作者:周志鵬,2年數據分析,深切感受到數據分析的有趣和學習過程中缺少案例的無奈,遂新開公眾號「數據不吹牛」,定期更新數據分析相關技巧和有趣案例(含實戰數據集),歡迎大家關注交流。


分享到:


相關文章: