為了抓取網站,我們首先需要下載包含有感興趣數據的網頁,該過程一般被稱為爬取“crawing”。爬取一個網站有很多種方法,而選用哪種方法更加合適,則取決於目標網站的結構。本章中,首先會探討如何安全地下載網頁,然后會介紹如下3種爬取網站的常見方法:
- 爬取網站地圖
- 遍歷每個網頁的數據庫ID
- 跟蹤網頁鏈接
下載網頁
想要爬取網頁,我們首先需要將其下載下來。下面的示例腳本使用python的urllib2模塊下載URL。
import urllib2 def download(url): return urllib2.urlopen(url).read()
當傳入URL參數時,該函數將會下載網頁並返回其HTML。不過,這個代碼存在一個問題,即當下在網頁時,我們可能會遇到一些無法控制的錯誤,比如請求的頁面可能不存在。此時,urllib2會拋出異常,然后退出腳本。安全起見,下面再給出一個更健壯的版本,可以捕獲這些異常。
import urllib2 def download(url) print("Download: ",url) try: html = urllib2.urlopen(url).read() except urllib2.URLError as e: print("Download error: ",e.reason) html = None return html
現在,當出現下載錯誤時,該函數能夠捕獲到異常,然后返回None。
重新下載
下載時遇到的錯誤經常是臨時性的,比如服務器過載時返回的 503 service unavailable 錯誤。對於此類錯誤,我們可以嘗試重新下載,因為這個服務器問題現在可能已解決。不過,我們不需要對所有錯誤都嘗試重新下載。如果服務器返回的是 404 not found 這種錯誤,則說明該網頁目前並不存在,再次嘗試同樣的請求一般也不會出現不同的結果。
互聯網工程任務組(Inter Engineering Task Force)定義了HTTP錯誤的完整列表,詳情可參考 http://tools.ietf.org/html/rfc7231#section-6 從該文檔中,我們可以了解到 4XX 錯誤發生在請求存在問題時,而 5XX 錯誤發生在服務端存在問題時。所以,我們只需要確保 download 函數在發生 5XX 錯誤時重試下載即可。下面是支持重試下載功能的新版本代碼。
#python2 import urllib2 def download(url, num_retries=2): print 'Download:',url try: html = urllib2.urlopen(url).read() except urllib2.URLError as e: print('Download error: ',e.reason) html = None if num_retries > 0: if hasattr(e, 'code') and 500 <= e.code <600: #hasattr() 函數用於判斷對象是否包含對應的屬性。 # recursively retry 5xx HTTP errors return download(url, num_retries-1) return html
#maybe made in Richard lawson
#python3 import os import urllib.request import urllib.error def download(url, num_retries=2): print ("Download:",url) try: html = urllib.request.urlopen(url).read() except urllib.error.URLError as e: print("Download error: ",e.reason) html = None if num_retries > 0 and 500 <= e.code <600: # recursively retry 5xx HTTP errors return download(url, num_retries-1) return html print(download("http://httpstat.us/500")) os.system("pause") #made in China
現在,當download函數遇到 5XX 錯誤碼時,將會遞歸調用函數自身進行嘗試。此外,該函數還增加了一個參數,用於設定重試下載的次數,其默認值為兩次。我們在這里限制網頁下載的嘗試次數,是因為服務器錯誤可能暫時還沒有解決。想要測試該函數,可以嘗試下載 http://httpstat.us/500 ,該網站會始終返回500錯誤碼。
將會顯示類似如下的文本
Download: http://httpstat.us/500 Download error: Internal Server Error Download: http://httpstat.us/500 Download error: Internal Server Error Download: http://httpstat.us/500 Download error: Internal Server Error None 請按任意鍵繼續. . .
從上面的返回結果可以看出,download 函數的行為和預期一致,先嘗試下載網頁,在接收到500錯誤后,又進行了兩次重試才放棄。
設置用戶代理
默認情況下,urllib2 使用Python-urllib/2.7 作為用戶代理下載網頁內容,其中2.7是Python的版本號。如果能使用可辨識的用戶代理則更好,這樣可以避免我們的網絡爬蟲碰到一些問題。此外,也許是因為曾經經歷過質量不佳的Python網絡爬蟲造成的服務器過載,一些網站還會封禁這個默認的用戶代理。比如,在使用python默認用戶代理的情況下,訪問 http://meetup.com/ ,目前會返回如下訪問拒絕提示。
Access denied The owner of this website(www.meetup.com) has banned your access based on your brower's signature (1754134676cf0ac4-ua48). Ray ID: 1754134676cf0ac4 Timestamp:Mon,06-Oct-14 18:55:48 GMT Your IP address: Requested URL:www.meetup.com/ Error reference number: 1010 Server ID: FL_33F7 User-Agent: Python-urllib/2.7
因此,為了下載更加可靠,我們需要控制用戶代理的設定。下面的代碼對 download 函數進行了修改,設定了一個默認的用戶代理“wswp”(即web scraping with python 的首字母縮寫)。
def download(url, user_agent='wswp', num_retries=2): print 'Download:', url headers = {'User-agent':user_agent} request = urllib2.Request{url, headers=headers} try: html = urllib2.urlopen(request).read() except urllib.URLError as e: print 'Download error:', e.reason html = None if num_retries > 0: if hasattr(e, 'code') and 500 <= e.code <600: # recursively retry 5xx HTTP errors return download(url, num_retries-1) return html
現在,我們擁有了一個靈活的下載函數,可以在后續示例中得到復用。該函數能夠捕獲異常、重試下載並設置用戶代理。
