上一次我自學爬蟲的時候, 寫了一個簡陋的勉強能執行的爬蟲alpha. alpha版有很多問題. 比如一個網站上不了, 爬蟲卻一直在等待連線返回response, 不知道超時跳過; 或者有的網站專門攔截爬蟲程式, 我們的爬蟲也不會偽裝自己成為瀏覽器正規部隊; 並且抓取的內容沒有儲存到本地, 沒有什麼作用. 這次我們一個個解決這些小問題.
此外, 在我寫這系列文章的第二篇的時候, 我還是一個對http的get和post以及response這些名詞一無所知的人, 但是我覺得這樣是寫不好爬蟲的. 於是我參考了 <<計算機網路–自頂向下方法>> 這本書的第二章的大部分內容. 如果你也一樣對http的機制一無所知, 我也推薦你找一找這方面的資料來看. 在看的過程中, 安裝一個叫做Fiddler的軟體, 邊學邊實踐, 觀察瀏覽器是如何訪問一個網站的, 如何發出請求, 如何處理響應, 如何進行跳轉, 甚至如何通過登入認證. 有句老話說得好, 越會用Fiddler, 就對理論理解更深刻; 越對理論理解深刻, Fiddler就用得越順手. 最後我們在用爬蟲去做各種各樣的事情的時候, Fiddler總是最得力的助手之一.
新增超時跳過功能
首先, 我簡單地將
1 |
urlop = urllib.request.urlopen(url) |
改為
1 |
urlop = urllib.request.urlopen(url, timeout = 2) |
執行後發現, 當發生超時, 程式因為exception中斷. 於是我把這一句也放在try .. except 結構裡, 問題解決.
支援自動跳轉
在爬 http://baidu.com 的時候, 爬回來一個沒有什麼內容的東西, 這個東西告訴我們應該跳轉到 http://www.baidu.com . 但是我們的爬蟲並不支援自動跳轉, 現在我們來加上這個功能, 讓爬蟲在爬 baidu.com 的時候能夠抓取 www.baidu.com 的內容.
首先我們要知道爬 http://baidu.com 的時候他返回的頁面是怎麼樣的, 這個我們既可以用 Fiddler 看, 也可以寫一個小爬蟲來抓取. 這裡我抓到的內容如下, 你也應該嘗試一下寫幾行 python 來抓一抓.
1 2 3 |
<html> <meta http-equiv=”refresh” content=”0;url=http://www.baidu.com/”> </html> |
看程式碼我們知道這是一個利用 html 的 meta 來重新整理與重定向的程式碼, 其中的0是等待0秒後跳轉, 也就是立即跳轉. 這樣我們再像上一次說的那樣用一個正規表示式把這個url提取出來就可以爬到正確的地方去了. 其實我們上一次寫的爬蟲已經可以具有這個功能, 這裡只是單獨拿出來說明一下 http 的 meta 跳轉.
偽裝瀏覽器正規軍
前面幾個小內容都寫的比較少. 現在詳細研究一下如何讓網站們把我們的Python爬蟲當成正規的瀏覽器來訪. 因為如果不這麼偽裝自己, 有的網站就爬不回來了. 如果看過理論方面的知識, 就知道我們是要在 GET 的時候將 User-Agent 新增到header裡.
如果沒有看過理論知識, 按照以下關鍵字搜尋學習吧 :D
- HTTP 報文分兩種: 請求報文和響應報文
- 請求報文的請求行與首部行
- GET, POST, HEAD, PUT, DELETE 方法
我用 IE 瀏覽器訪問百度首頁的時候, 瀏覽器發出去的請求報文如下:
1 2 3 4 5 6 7 8 9 |
GET http://www.baidu.com/ HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko Accept-Encoding: gzip, deflate Host: www.baidu.com DNT: 1 Connection: Keep-Alive Cookie: BAIDUID=57F4D171573A6B88A68789EF5DDFE87:FG=1; uc_login_unique=ccba6e8d978872d57c7654130e714abd; BD_UPN=11263145; BD |
然後百度收到這個訊息後, 返回給我的的響應報文如下(有刪節):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
HTTP/1.1 200 OK Date: Mon, 29 Sep 2014 13:07:01 GMT Content-Type: text/html; charset=utf-8 Connection: Keep-Alive Vary: Accept-Encoding Cache-Control: private Cxy_all: baidu+8b13ba5a7289a37fb380e0324ad688e7 Expires: Mon, 29 Sep 2014 13:06:21 GMT X-Powered-By: HPHP Server: BWS/1.1 BDPAGETYPE: 1 BDQID: 0x8d15bb610001fe79 BDUSERID: 0 Set-Cookie: BDSVRTM=0; path=/ Set-Cookie: BD_HOME=0; path=/ Content-Length: 80137 <!DOCTYPE html><!–STATUS OK–><html><head><meta http-equiv=”content-type” content=”text/html;charset=utf-8″><meta http-equiv=”X-UA-Compatible” content=”IE=Edge”><link rel=”dns-prefetch” href=”//s1.bdstatic.com”/><link rel=”dns-prefetch” href=”//t1.baidu.com”/><link rel=”dns-prefetch” href=”//t2.baidu.com”/><link rel=”dns-prefetch” href=”//t3.baidu.com”/><link rel=”dns-prefetch” href=”//t10.baidu.com”/><link rel=”dns-prefetch” href=”//t11.baidu.com”/><link rel=”dns-prefetch” href=”//t12.baidu.com”/><link rel=”dns-prefetch” href=”//b1.bdstatic.com”/><title>百度一下,你就知道</title><style index=”index” > ……….這裡省略兩萬字……………. </script></body></html> |
如果能夠看懂這段話的第一句就OK了, 別的可以以後再配合 Fiddler 慢慢研究. 所以我們要做的就是在 Python 爬蟲向百度發起請求的時候, 順便在請求裡面寫上 User-Agent, 表明自己是瀏覽器君.
在 GET 的時候新增 header 有很多方法, 下面介紹兩種方法.
第一種方法比較簡便直接, 但是不好擴充套件功能, 程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
import urllib.request url = 'http://www.baidu.com/' req = urllib.request.Request(url, headers = { 'Connection': 'Keep-Alive', 'Accept': 'text/html, application/xhtml+xml, */*', 'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' }) oper = urllib.request.urlopen(req) data = oper.read() print(data.decode()) |
第二種方法使用了 build_opener 這個方法, 用來自定義 opener, 這種方法的好處是可以方便的擴充功能, 例如下面的程式碼就擴充了自動處理 Cookies 的功能.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import urllib.request import http.cookiejar # head: dict of header def makeMyOpener(head = { 'Connection': 'Keep-Alive', 'Accept': 'text/html, application/xhtml+xml, */*', 'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko' }): cj = http.cookiejar.CookieJar() opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj)) header = [] for key, value in head.items(): elem = (key, value) header.append(elem) opener.addheaders = header return opener oper = makeMyOpener() uop = oper.open('http://www.baidu.com/', timeout = 1000) data = uop.read() print(data.decode()) |
上述程式碼執行後通過 Fiddler 抓到的 GET 報文如下所示:
1 2 3 4 5 6 7 |
GET http://www.baidu.com/ HTTP/1.1 Accept-Encoding: identity Connection: close Host: www.baidu.com User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko Accept: text/html, application/xhtml+xml, */* Accept-Language: en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3 |
可見我們在程式碼裡寫的東西都新增到請求報文裡面了.
儲存抓回來的報文
順便說說檔案操作. Python 的檔案操作還是相當方便的. 我們可以講抓回來的資料 data 以二進位制形式儲存, 也可以經過 decode() 處理成為字串後以文字形式儲存. 改動一下開啟檔案的方式就能用不同的姿勢儲存檔案了. 下面是參考程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
def saveFile(data): save_path = 'D:\\temp.out' f_obj = open(save_path, 'wb') # wb 表示開啟方式 f_obj.write(data) f_obj.close() # 這裡省略爬蟲程式碼 # ... # 爬到的資料放到 dat 變數裡 # 將 dat 變數儲存到 D 盤下 saveFile(dat) |
下回我們會用 Python 來爬那些需要登入之後才能看到的資訊. 在那之前, 我已經對 Fiddler 稍微熟悉了. 希望一起學習的也提前安裝個 Fiddler 玩一下.