學渣講爬蟲之Python爬蟲從入門到出門(第三講)

雖為學渣誓為學霸發表於2018-01-13

學渣講爬蟲之Python爬蟲從入門到出門(第三講)

這一講,我將會為大家講解稍微複雜一點的爬蟲,即動態網頁的爬蟲。

動態網頁技術介紹

所謂的動態網頁,是指跟靜態網頁相對的一種網頁程式設計技術。靜態網頁,隨著html程式碼的生成,頁面的內容和顯示效果就基本上不會發生變化了——除非你修改頁面程式碼。而動態網頁則不然,頁面程式碼雖然沒有變,但是顯示的內容卻是可以隨著時間、環境或者資料庫操作的結果而發生改變的。

值得強調的是,不要將動態網頁和頁面內容是否有動感混為一談。這裡說的動態網頁,與網頁上的各種動畫、滾動字幕等視覺上的動態效果沒有直接關係,動態網頁也可以是純文字內容的,也可以是包含各種動畫的內容,這些只是網頁具體內容的表現形式,無論網頁是否具有動態效果,只要是採用了動態網站技術生成的網頁都可以稱為動態網頁。(解釋來源:百度百科 - “動態網頁”,若連結失效請訪問:https://baike.baidu.com/item/%E5%8A%A8%E6%80%81%E7%BD%91%E9%A1%B5/6327050?fr=aladdin)

這裡寫圖片描述

網際網路每天都在蓬勃的發展,數以萬計的線上平臺如雨後春筍般不斷湧現,不同平臺對不同使用者的許可權、喜好推出不同的個性化內容,傳統的靜態網頁似乎早已不能滿足社會的需求。於是,動態網頁技術應運而生,當然,在如今人們對網頁載入速度的要求越來越高的要求下,非同步載入成為了許多大的站點的首選。比如各大電商平臺、知識型網站、社交平臺等,都廣泛採用了非同步載入的動態技術。簡單來說,就是把一些根據時間、請求而變化的內容,比如某寶的商品價格、評論,比如某瓣的熱門電影評論,再比如某訊的視訊等,採用先載入網頁整體框架,後載入動態內容的方式呈現

對於這一類動態頁面,如果我們採用前面所說的對付靜態網頁的爬蟲方式去爬,可能收穫不到任何結果,因為這些非同步載入的內容所在的位置大多是一段請求內容的JS程式碼。在某些觸發操作下,這些JS程式碼開始工作,從資料庫中提取對應的資料,將其放置到網頁框架中相對應的位置,從而最終拼接成我們所能看到的完整的一張頁面。

動態網頁爬蟲技術一之API請求法

看似更加複雜的操作似乎給我們的爬蟲帶來了很大的困擾,但其實也可能給我們帶來極大的便利。我們只需要找到JS請求的API,並按照一定的要求傳送帶有有效引數的請求,便能獲得最為整潔的資料,而不用像以前那樣從層層巢狀的HTML程式碼中慢慢解析出我們想要的資料

這裡我們以上面提到的豆瓣電影(若連結失效請訪問:https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0)為例做一個分析,提取出熱度排名前100的電影名稱和評分以及在豆瓣的地址

這裡寫圖片描述

這是最近熱門電影按熱度排序的一個截圖,每個月都有不同的新電影上映,每部電影會隨著口碑效應每天呈現不同的熱度排序,如果這頁面是個靜態網頁,那麼豆瓣的程式設計師豈不是很辛苦,每天都要上線修改這個頁面。所以,我們可以大膽的猜測,這是個動態頁面。但是光猜不行,我們還得證實。這裡就要用到第二講講到的谷歌開發者工具了。按下F12或者在網頁空白處右鍵選擇檢查(N),或者在鍵盤上按下組合鍵Ctrl + Shift + I,召喚出我們的神器。如下圖所示:

這裡寫圖片描述

今天我們不再使用左上角的滑鼠按鈕了,而是使用紅色框中的Network,這裡顯示的是網頁載入出來的所有的檔案,如下圖所示:

這裡寫圖片描述

如果下方沒有任何結果,需要在開啟谷歌開發者工具的情況下重新整理網頁。

這裡寫圖片描述

如上圖所示,我們點選上方紅色小框中的”XHR“按鈕,就可以將這張網頁中非同步載入的內容篩選出來。至於到底哪一個才是我們所要的,這是個問題,看左邊的地址我們似乎也看不出神馬頭緒,那就一個一個點出來看吧。。。經過列舉,我們發現,第三個是我們要的內容,它的內容如下圖:

這裡寫圖片描述

我們可以看到,這個連結裡包含的內容是以JSON格式展示出來的,這時我們便有了一個大概的思路,那就是將這個連結的內容用requests模組下載後,再用Python的json模組進行解析。

但是,這好像是一頁的內容,數一數也只有20部電影,我們想要的是排名前100的電影,這怎麼辦呢?

不方,畢竟是動態網頁,內容都是可以根據請求改變的,而且這裡也沒有登陸啥的操作,開啟網頁就能看到,那我們是不是可以改變一下URL從而獲取到下一頁甚至下下頁的內容咧?當然可以,不然我就寫不下去了!

我們仔細觀察一下這個URL裡傳遞的引數:

https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0

這裡寫圖片描述

到這裡我們可能還不知道這五個引數是幹嘛的,但我們可以找規律啊,於是現在回到原始的網頁,點選頁面下方的“載入更多”,再返回到開發者工具,哇,多出了一個URL,長的跟剛才說的那個好像,內容也長的好像:

這裡寫圖片描述

這個URL同樣傳遞了五個引數:

這裡寫圖片描述

唯一的不同就是一個叫“page_start”的關鍵字的值改變了,簡單翻譯一下大概是頁面起點的意思,再看上面的“page_limit”,大概就是頁面限制的意思,看右邊的響應內容,這一個頁面傳遞了20個條目,也就是說“page_limit”是一個頁面裡條目數量的限制,也就是20條,這個資料是不變的,而“page_start”是這一頁開始的條目序號,那麼我們要獲取後面的內容,豈不是隻要改變一下這個“page_start”就好了?是的。

老規矩,先寫個程式碼壓壓驚:

# -*- coding: utf-8 -*-

import requests
import json

for i in range(5):
    page_start = str(i * 20)  # 註釋1
    url = 'https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=' + page_start  # 註釋2
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
    }
    response = requests.get(url=url, headers=headers, verify=False)
    content = response.content.decode()
    content_list = json.loads(content)['subjects']  # 註釋3
    for item in content_list:  # 註釋4
        title = item['title']  #註釋5
        rate = item['rate']  # 註釋6
        link = item['url']  # 註釋7
        print(title, rate, link)
  • 註釋1:因為一頁有20條資料,我們要爬取100條,而page_start的起始資料是0,因此我們這裡只需要用一個i,從0到4迴圈5次,每一次乘以20便可以輕鬆的設定page_start的值了,因為後面的URL是個字串型別,這裡進行乘法運算後我們用str()方法進行型別轉換,轉換成str型別,方便後面的呼叫。

  • 註釋2:這裡我們每一次迴圈只要改變page_start的值就好了,所以在最後修改這個值;

  • 註釋3:返回的content經過decode()方法解碼,變成了一個字串型別,根據我們前面的分析,得出這是一個JSON格式的字串,因此,我們用Python內建的標準庫json來進行解析,標準庫就不需要用pip工具進行安裝了。json模組有兩個主要方法——json.loads()json.dumps(),前一個是用來解碼JSON資料的,後一個則是編碼成JSON資料的。這裡我們主要用到loads()方法,將content解析成一個字典格式的內容,儲存在名為content_list的物件裡,最後加上“[‘subjects’]”是用來解析出我們要的最簡潔的部分,這個根據具體的內容來定,不是所有的解析都要這麼寫。比如這裡,如下圖:

    這裡寫圖片描述

    我們要的內容最外面有這麼一個巢狀,這個巢狀的Key為”subjects”,這個Key的值為一個陣列,這個陣列才是我們要的,因此加上了“[‘subjects’]”

  • 註釋4:content_list是一個陣列物件,因此我們也做一個迴圈,分條提取。

  • 註釋5:每一條資料依然是個字典型別的物件,因此我們可以直接寫對應的Key名得到想要的值,這裡得到的電影的名稱;

  • 註釋6:同5,這裡得到的是電影的評分;

  • 註釋7:同5,這裡得到的是電影的豆瓣連結;

最後的話,大家可以採用標準輸入流寫入txt檔案,也可以採用xlwt模組寫入EXCEL,還可以使用比如pymysql模組寫入Mysql資料庫,具體的方式大家隨意,使用方法請自行百度。

到這裡,這種採用尋找API並傳遞有效引數重放API的方法便為大家介紹完了,這是個很通用的方法,在很多網站都可以這樣使用,並且速度很快,結果最精簡。

動態網頁爬蟲技術二之模擬瀏覽器法

上面我們所講的API請求法雖然好用且快,但是並不是所有的網站都會採用這種非同步載入的方式來實現網站,同時還有部分網站會針對爬蟲採取反爬蟲措施,比如常見的驗證碼,雖然驗證碼主要是用來防止CSRF攻擊的,但也有網站用來處理爬蟲,比如某寶。這時候,就要為大家介紹另一個神器了,Python的Selenium模組。

Selenium是一個用於Web應用程式測試的工具。Selenium測試直接執行在瀏覽器中,就像真正的使用者在操作一樣。支援的瀏覽器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。這個工具的主要功能包括:測試與瀏覽器的相容性——測試你的應用程式看是否能夠很好得工作在不同瀏覽器和作業系統之上。測試系統功能——建立迴歸測試檢驗軟體功能和使用者需求。支援自動錄製動作和自動生成 .Net、Java、Perl等不同語言的測試指令碼。(解釋來自:百度百科 - “Selenium”,若連結失效請點選https://baike.baidu.com/item/Selenium/18266?fr=aladdin)

簡單的說,Selenium是一個主要用來進行自動化測試的工具,它可以配合瀏覽器驅動在各個瀏覽器中執行,依照程式碼自動地模擬人的操作,去獲取網頁元素或對網頁元素進行控制。當然,Selenium並不是Python的產物,而是一個獨立的專案,Python對Selenium提供支援。(大家可以自行訪問Selenium的主頁進行訪問,若連結失效請點選http://www.seleniumhq.org/)

安裝selenium模組

要使用Selenium這種第三方的工具,我們首先要進行安裝,這裡依然用到pip工具。在管理員許可權下執行命令列,輸入pip install selenium,稍等片刻後便可以完成安裝,如果覺得網路連線官方pypi映象速度較慢,可以使用國內豆瓣的映象源,pip install selenium -i https://pypi.douban.com/simple/,加上這個-i引數和豆瓣pypi映象的地址就可以了,如果想要預設使用豆瓣映象源,請自行百度修改方法。

下載Google Chrome Driver

在安裝成功後,我們就需要安裝下一個必要的東西了,瀏覽器驅動,前面說過,selenium需要配合瀏覽器驅動執行,因此我們以安裝Google Chrome Driver為例。

首先,我們需要檢視自己的谷歌瀏覽器版本,這個在谷歌的”幫助”中可以檢視,具體方法是,開啟Chrome,點選右上角的三個點狀的按鈕,接著在彈出的選單中依次選擇幫助(E) -> 關於 Google Chrome(G)如下圖所示:

這裡寫圖片描述

作者的瀏覽器是更新到當前最新的版本63的,舊版本的操作方法大致一致。

點開關於資訊後,我們可以看到當前的Chrome版本,以下圖為例:

這裡寫圖片描述

Chrome一直在升級,因此對應的驅動也得不斷升級,並且與Chrome版本相適應。這裡我們需要查詢相應的ChromeDriver版本對映,給大家推薦一個持續更新的CSDN部落格(若連結失效請點選:http://blog.csdn.net/huilan_same/article/details/51896672),根據版本對映表,下載對應版本的ChromeDriver,下載地址1(若連結失效請訪問:http://chromedriver.storage.googleapis.com/index.html),下載地址2(若連結失效請訪問:http://npm.taobao.org/mirrors/chromedriver/)。

安裝ChromeDriver

這裡需要進行環境變數的配置,如第一講所說,為”Path”新增一行值。

首先,我們需要找到Chrome的安裝位置,最為簡單的辦法是,在桌面找到Google Chrome的快捷方式,右鍵選擇”開啟檔案所在的位置“,就能開啟了。比如我這裡開啟的路徑為C:\Program Files (x86)\Google\Chrome\Application,那麼我就將這個路徑新增到Path裡。然後,需要我們將下載的ChromeDriver解壓到exe程式,將單獨的exe程式複製到剛才這個路徑裡,如下圖所示:

這裡寫圖片描述

到這裡,ChromeDriver便完成了安裝,我們可以在命令列輸入命令python,進入到python互動環境進行測試,如下圖所示:

這裡寫圖片描述

如果你的谷歌瀏覽器自動開啟,並且跳轉到百度首頁,那麼Congratulations~

以某寶某隻松鼠店鋪為例爬取”堅果炒貨”的商品名稱、價格、銷量以及評論數量

該頁面的URL為:https://sanzhisongshu.tmall.com/category-1124487841.htm?spm=a1z10.1-b-s.w5003-17763072511.42.6995d6732XB8Of&tsearch=y&scene=taobao_shop#TmshopSrchNav

老規矩,先放一段程式碼:

# -*- coding: utf-8 -*-

from selenium import webdriver

driver = webdriver.Chrome()  # 註釋1
url = 'https://sanzhisongshu.tmall.com/category-1124487841.htm?spm=a1z10.1-b-s.w5003-17763072511.42.6995d6732XB8Of&tsearch=y&scene=taobao_shop#TmshopSrchNav'
driver.maximize_window()  # 註釋2
driver.get(url)  # 註釋3
dl_list = driver.find_elements_by_class_name('item')  # 註釋4
for dl in dl_list:
    name = dl.find_element_by_css_selector("[class='item-name J_TGoldData']").text  # 註釋5
    price = dl.find_element_by_class_name('cprice-area').text  # 註釋6
    sale = dl.find_element_by_class_name('sale-area').text  # 註釋7
    comment = dl.find_element_by_xpath('//*[@id="J_ShopSearchResult"]/div/div[3]/div[1]/dl[1]/dd[2]/div/h4/a/span').text  # 註釋8
    print(name, price, sale, comment)
driver.close()  # 註釋9
  • 註釋1:例項化了一個webdriver的Chrome物件,命名為driver,這時會有一個Chrome視窗自動開啟。

  • 註釋2:呼叫了driver的maximize_window()方法,直接翻譯就是最大化視窗,也就是這個功能,這句寫不寫不重要,作者寫只是覺得看的清楚點。

  • 註釋3:呼叫了driver的get()方法,以get方式請求URL。

  • 註釋4:這裡開始是重點,webdriver主要有八種查詢元素的方式,這一行是採用class_name的形式進行查詢,並且注意到elements這裡的複數,這個方法用來查詢頁面上所有的符合條件的元素,如果是沒有s的方法就只能找到首個符合條件的元素,這一行是使用谷歌開發者工具的左上角小箭頭工具對元素進行稽核,並找出所有的商品條目,其中一個條目範圍如下圖所示:

    這裡寫圖片描述

  • 註釋5:同註釋4,但這裡採用的是css_selector,即css選擇器的方式進行查詢,因為這裡的類名”item-name J_TGoldData”是個複合結構,而find_element_by_class_name()方法不支援複合結構的查詢,所以只能採用css_selector這種方式。

  • 註釋6:同註釋4,這裡是單數,即在一個商品條目的範圍內查詢一次。

  • 註釋7:同註釋6。

  • 註釋8:同註釋4,但這裡採用的是xpath的方式查詢。

    XPath即為XML路徑語言,它是一種用來確定XML標準通用標記語言的子集)文件中某部分位置的語言。XPath基於XML的樹狀結構,有不同型別的節點,包括元素節點,屬性節點和文字節點,提供在資料結構樹中找尋節點的能力。起初 XPath 的提出的初衷是將其作為一個通用的、介於XPointer與XSLT間的語法模型。但是 XPath 很快的被開發者採用來當作小型查詢語言。(解釋來自:百度百科 - “XPath”,若連結失效請訪問:https://baike.baidu.com/item/XPath/5574064?fr=aladdin)

    獲得元素xpath的方法有幾種,最為簡單的一種,即在谷歌開發者工具皮膚上選擇要查詢的元素,右鍵選擇Copy -> Copy XPath,如下圖所示:

    這裡寫圖片描述

    當然這種方式可能存在缺陷,即獲得的XPath可能過於繁瑣,也可能獲取的XPath無法正確查詢到相應的元素,這都需要手動的依據XPath的語法去修改。

  • 註釋9:最後一定要記得關閉例項化的物件,這時候由程式開啟的瀏覽器也會隨之關閉。

這個例子最後的結果如下圖:

這裡寫圖片描述

大家依然可以自由的選擇資料儲存方式。

這裡要注意的是:使用selenium進行資料爬取相比前面的API請求法可能會慢的多,在開啟到對應的視窗後,也可能視窗很長時間沒有任何動作,但這不一定是出錯或者是程式卡死的表現,也可能是程式在瘋狂的查詢網頁元素,在這個過程中,如果不確定是否出錯,請最好不要進行其他操作,避免有些時候造成元素失去焦點,導致莫名的錯誤。

當然了,selenium的功能遠不止如此,幾乎人能在網頁上做出的行為,它都能模擬,包括點選、輸入等各種行為,這個比較適用於某些網站要填寫驗證碼的情況,更多有趣的內容大家可以自行發現。本講就寫到這裡。感謝大家的耐心閱讀。

課後作業

這裡給大家留兩個小作業,感興趣的可以自行測試。

  1. 請大家使用API請求法自行在蝦米音樂上找一首收費下載的歌曲,在不登入賬號的情況下對這首歌曲進行下載操作。
  2. 請大家使用selenium爬取知乎首頁的熱門話題或話題回答100條。

關於作者

作者是一名即將畢業的大四學生,自學爬蟲並作為數個爬蟲專案的主要開發者,對各種爬蟲有一定的瞭解和專案經驗,目前正在自學分散式爬蟲的內容,也將在後面陸續為大家更新。同時作者也是一個狂熱的資訊保安愛好者,如果有想要和作者交流經驗或者想要提出意見的朋友,歡迎在下方留言,我會及時回覆大家的。感謝大家的支援。

相關文章