簡介
上篇Python爬蟲爬取動態頁面思路+實例(一)提到,爬取動態頁面有兩種方法
- 分析頁面請求
- selenium模擬瀏覽器行為(這篇介紹這個)
理論上來講,這種方法可以應對各種動態加載,因為模擬人的行為嘛,如果人自己用瀏覽器來看網頁都加載不出數據來,這網站吃棗葯丸。但是它的顯著缺點就是——慢。所以一般情況下,這是一種萬不得已的方法。
selenium是一種自動化測試的工具(話說這方面我並不懂),懂自動化測試的同學不用我介紹,不懂的同學,跟着我的思路走就好了,不影響我們寫爬蟲。
如何安裝selenium我就不介紹了,網上很多教程,如果你在調用Firefox的時候出現了問題,那么請留意一下本文參考資料1。
首先說一下整體的思路,我們知道,動態加載的數據都是用戶通過鼠標或鍵盤執行了一定的動作之后加載出來的。所以我們通過selenium提供的webdriver工具調用本地的瀏覽器,讓程序替代人的行為,滾動頁面,點擊按鈕,提交表單等等。從而獲取到想要的數據。所以我認為,使用selenium方法爬取動態頁面的中心思想是模擬人的行為。
這里舉一個例子,爬取自己QQ空間的說說。
說說
在模擬人的行為的中心思想指導下,我們首先來一步一步的分析出,人都需要做什么。這個過程中不放代碼了,代碼最后貼出來,注釋也很詳細,不想看分析的直接看代碼也OK。
百度qq空間,進入qq空間的登陸頁面,發現默認是快速登陸方式,所以得先點擊賬號密碼登陸,檢查元素,發現這個按鈕有id,好辦。得出第一步:點擊賬號密碼登陸。

點過之后,該填寫表單了,填完之后點擊登陸按鈕,這里的帳號密碼框和登陸按鈕通過檢查元素也可以發現都是有id的,獲取很方便。第二步:填寫表單並點擊登陸按鈕。
登陸成功后就進入了最近消息的頁面,然后找到說說按鈕,點進去(你的空間主題可能跟我不一樣,不過點進去的url除了qq號部分不同,其它地方應該是相同的)。

可以看到說說頁的url格式是
http://user.qq.com/qq號/311
- 1
第三步:找到說說所在的url

然后我們就下拉滾動條,可以直觀的看到說說是動態加載的,大概加載個四五次,就出現了分頁的按鈕。而且檢查元素后發現十分感人,“下一頁”按鈕也有id。
不過注意了,經過我的分析,這個id會隨着你的點擊次數逐次加一,不妨試一下,下兩張圖是沒點過和點過一次的id。


得出第四步:拖到頁面末尾,點擊“下一頁”。
當頁面到達最后一頁,”下一頁”按鈕的id就消失了,也就是說程序里就查不到這個元素了,所以停止就好了。

第五步:重復第四步,直到“下一頁”按鈕不可點
根據以上得出的五步,再結合一下selenium的簡單語法,就能實現獲取動態加載的說說的功能。從下面的代碼里可以看出,程序的流程就是再模擬我們上面分析的五步。
代碼:
幾點說明:
- 注意注釋中解釋的frame的選擇問題
- 注意注釋中登陸處的手動處理
- 因為QQ空間里的id實在太好用了,我都是用的通過id查找,元素,其實selenium還可以用其它的方式,比如xpath,css等等
- unittest模塊是python的一個內置測試模塊,詳情參考下面的資料5
- 目前我只實現了孤零零的爬說說(而且還沒爬回復),如果你實現了爬別人的說說或者自動留言,自動評論等有趣的功能(有空我會搞搞這些),歡迎pull request
- github地址kongtianyi/QQZoneSpider
#coding:utf-8 import unittest import time from selenium import webdriver from bs4 import BeautifulSoup class seleniumTest(unittest.TestCase): user = 'xxxxxxxx' # 你的QQ號 pw = 'xxxxxx' # 你的QQ密碼 def setUp(self): # 調試的時候用firefox比較直觀 # self.driver = webdriver.PhantomJS() self.driver = webdriver.Firefox() def testEle(self): driver = self.driver # 瀏覽器窗口最大化 driver.maximize_window() # 瀏覽器地址定向為qq登陸頁面 driver.get("http://i.qq.com") # 很多時候網頁由多個<frame>或<iframe>組成,webdriver默認定位的是最外層的frame, # 所以這里需要選中一下frame,否則找不到下面需要的網頁元素 driver.switch_to.frame("login_frame") # 自動點擊賬號登陸方式 driver.find_element_by_id("switcher_plogin").click() # 賬號輸入框輸入已知qq賬號 driver.find_element_by_id("u").send_keys(self.user) # 密碼框輸入已知密碼 driver.find_element_by_id("p").send_keys(self.pw) # 自動點擊登陸按鈕 driver.find_element_by_id("login_button").click() # 如果登錄比較頻繁或者服務器繁忙的時候,一次模擬點擊可能失敗,所以想到可以嘗試多次, # 但是像QQ空間這種比較知名的社區在多次登錄后都會出現驗證碼,驗證碼自動處理又是一個 # 大問題,本例不贅述。本例采用手動確認的方式。即如果觀察到自動登陸失敗,手動登錄后 # 再執行下列操作。 r = '' while r != 'y': print "Login seccessful?[y]" r = raw_input() # 讓webdriver操縱當前頁 driver.switch_to.default_content() # 跳到說說的url driver.get("http://user.qzone.qq.com/" + self.user + "/311") # 訪問全部說說需要訪問所有分頁,需要獲取本頁數據后點擊“下一頁”按鈕,經分析,“下一頁” # 按鈕的id會隨着點擊的次數發生變化,規律就是每點一下加一,所以需要在程序中動態的構造 # 它的id。(或者不用id改用xpath) next_num = 0 # 初始“下一頁”的id while True: # 下拉滾動條,使瀏覽器加載出動態加載的內容,可能像這樣要拉很多次,中間要適當的延時(跟網速也有關系)。 # 如果說說內容都很長,就增大下拉的長度。 driver.execute_script("window.scrollBy(0,10000)") time.sleep(3) driver.execute_script("window.scrollBy(0,20000)") time.sleep(3) driver.execute_script("window.scrollBy(0,30000)") time.sleep(3) driver.execute_script("window.scrollBy(0,40000)") time.sleep(5) # 很多時候網頁由多個<frame>或<iframe>組成,webdriver默認定位的是最外層的frame, # 所以這里需要選中一下說說所在的frame,否則找不到下面需要的網頁元素 driver.switch_to.frame("app_canvas_frame") soup = BeautifulSoup(driver.page_source, 'xml') contents = soup.find_all('pre', {'class': 'content'}) # 內容 times = soup.find_all('a', {'class': 'c_tx c_tx3 goDetail'}) # 發表時間 for content, _time in zip(contents, times): # 這里_time的下划線是為了與time模塊區分開 print content.get_text(), _time.get_text() # 當已經到了尾頁,“下一頁”這個按鈕就沒有id了,可以結束了 if driver.page_source.find('pager_next_' + str(next_num)) == -1: break # 找到“下一頁”的按鈕 elem = driver.find_element_by_id('pager_next_' + str(next_num)) # 點擊“下一頁” elem.click() # 下一次的“下一頁”的id next_num += 1 # 因為在下一個循環里首先還要把頁面下拉,所以要跳到外層的frame上 driver.switch_to.parent_frame() def tearDown(self): print 'down' if __name__ == "__main__": unittest.main()
結語
在測試的時候使用selenium+Firefox,在寫好了代碼實際運行的時候不妨換成selenium+PhantomJS,后者是一個無界面的瀏覽器,效率會高些。
本人於selenium也是初窺門徑,如有不正確或不清晰之處,請各位大俠不吝批評指正!
參考資料
