上一次我自學(xué)爬蟲(chóng)的時(shí)候, 寫(xiě)了一個(gè)簡(jiǎn)陋的勉強(qiáng)能運(yùn)行的爬蟲(chóng)alpha. alpha版有很多問(wèn)題. 比如一個(gè)網(wǎng)站上不了, 爬蟲(chóng)卻一直在等待連接返回response, 不知道超時(shí)跳過(guò); 或者有的網(wǎng)站專(zhuān)門(mén)攔截爬蟲(chóng)程序, 我們的爬蟲(chóng)也不會(huì)偽裝自己成為瀏覽器正規(guī)部隊(duì); 并且抓取的內(nèi)容沒(méi)有保存到本地, 沒(méi)有什么作用. 這次我們一個(gè)個(gè)解決這些小問(wèn)題.
此外, 在我寫(xiě)這系列文章的第二篇的時(shí)候, 我還是一個(gè)對(duì)http的get和post以及response這些名詞一無(wú)所知的人, 但是我覺(jué)得這樣是寫(xiě)不好爬蟲(chóng)的. 于是我參考了 <<計(jì)算機(jī)網(wǎng)絡(luò)–自頂向下方法>> 這本書(shū)的第二章的大部分內(nèi)容. 如果你也一樣對(duì)http的機(jī)制一無(wú)所知, 我也推薦你找一找這方面的資料來(lái)看. 在看的過(guò)程中, 安裝一個(gè)叫做Fiddler的軟件, 邊學(xué)邊實(shí)踐, 觀察瀏覽器是如何訪問(wèn)一個(gè)網(wǎng)站的, 如何發(fā)出請(qǐng)求, 如何處理響應(yīng), 如何進(jìn)行跳轉(zhuǎn), 甚至如何通過(guò)登錄認(rèn)證. 有句老話說(shuō)得好, 越會(huì)用Fiddler, 就對(duì)理論理解更深刻; 越對(duì)理論理解深刻, Fiddler就用得越順手. 最后我們?cè)谟门老x(chóng)去做各種各樣的事情的時(shí)候, Fiddler總是最得力的助手之一.
添加超時(shí)跳過(guò)功能
首先, 我簡(jiǎn)單地將
urlop = urllib.request.urlopen(url)
改為
urlop = urllib.request.urlopen(url, timeout = 2)
運(yùn)行后發(fā)現(xiàn), 當(dāng)發(fā)生超時(shí), 程序因?yàn)閑xception中斷. 于是我把這一句也放在try .. except 結(jié)構(gòu)里, 問(wèn)題解決.
支持自動(dòng)跳轉(zhuǎn)
在爬 http://baidu.com 的時(shí)候, 爬回來(lái)一個(gè)沒(méi)有什么內(nèi)容的東西, 這個(gè)東西告訴我們應(yīng)該跳轉(zhuǎn)到 http://www.baidu.com . 但是我們的爬蟲(chóng)并不支持自動(dòng)跳轉(zhuǎn), 現(xiàn)在我們來(lái)加上這個(gè)功能, 讓爬蟲(chóng)在爬 baidu.com 的時(shí)候能夠抓取 www.baidu.com 的內(nèi)容.
首先我們要知道爬 http://baidu.com 的時(shí)候他返回的頁(yè)面是怎么樣的, 這個(gè)我們既可以用 Fiddler 看, 也可以寫(xiě)一個(gè)小爬蟲(chóng)來(lái)抓取. 這里我抓到的內(nèi)容如下, 你也應(yīng)該嘗試一下寫(xiě)幾行 python 來(lái)抓一抓.
<html>
<meta http-equiv=”refresh” content=”0;url=http://www.baidu.com/”>
</html>
看代碼我們知道這是一個(gè)利用 html 的 meta 來(lái)刷新與重定向的代碼, 其中的0是等待0秒后跳轉(zhuǎn), 也就是立即跳轉(zhuǎn). 這樣我們?cè)傧裆弦淮握f(shuō)的那樣用一個(gè)正則表達(dá)式把這個(gè)url提取出來(lái)就可以爬到正確的地方去了. 其實(shí)我們上一次寫(xiě)的爬蟲(chóng)已經(jīng)可以具有這個(gè)功能, 這里只是單獨(dú)拿出來(lái)說(shuō)明一下 http 的 meta 跳轉(zhuǎn).
偽裝瀏覽器正規(guī)軍
前面幾個(gè)小內(nèi)容都寫(xiě)的比較少. 現(xiàn)在詳細(xì)研究一下如何讓網(wǎng)站們把我們的Python爬蟲(chóng)當(dāng)成正規(guī)的瀏覽器來(lái)訪. 因?yàn)槿绻贿@么偽裝自己, 有的網(wǎng)站就爬不回來(lái)了. 如果看過(guò)理論方面的知識(shí), 就知道我們是要在 GET 的時(shí)候?qū)?User-Agent 添加到header里.
如果沒(méi)有看過(guò)理論知識(shí), 按照以下關(guān)鍵字搜索學(xué)習(xí)吧 :D
- HTTP 報(bào)文分兩種: 請(qǐng)求報(bào)文和響應(yīng)報(bào)文
- 請(qǐng)求報(bào)文的請(qǐng)求行與首部行
- GET, POST, HEAD, PUT, DELETE 方法
我用 IE 瀏覽器訪問(wèn)百度首頁(yè)的時(shí)候, 瀏覽器發(fā)出去的請(qǐng)求報(bào)文如下:
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
然后百度收到這個(gè)消息后, 返回給我的的響應(yīng)報(bào)文如下(有刪節(jié)):
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” > ……….這里省略兩萬(wàn)字……………. </script></body></html>
如果能夠看懂這段話的第一句就OK了, 別的可以以后再配合 Fiddler 慢慢研究. 所以我們要做的就是在 Python 爬蟲(chóng)向百度發(fā)起請(qǐng)求的時(shí)候, 順便在請(qǐng)求里面寫(xiě)上 User-Agent, 表明自己是瀏覽器君.
在 GET 的時(shí)候添加 header 有很多方法, 下面介紹兩種方法.
第一種方法比較簡(jiǎn)便直接, 但是不好擴(kuò)展功能, 代碼如下:
| 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 這個(gè)方法, 用來(lái)自定義 opener, 這種方法的好處是可以方便的拓展功能, 例如下面的代碼就拓展了自動(dòng)處理 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()) |
上述代碼運(yùn)行后通過(guò) Fiddler 抓到的 GET 報(bào)文如下所示:
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
可見(jiàn)我們?cè)诖a里寫(xiě)的東西都添加到請(qǐng)求報(bào)文里面了.
保存抓回來(lái)的報(bào)文
順便說(shuō)說(shuō)文件操作. Python 的文件操作還是相當(dāng)方便的. 我們可以講抓回來(lái)的數(shù)據(jù) data 以二進(jìn)制形式保存, 也可以經(jīng)過(guò) decode() 處理成為字符串后以文本形式保存. 改動(dòng)一下打開(kāi)文件的方式就能用不同的姿勢(shì)保存文件了. 下面是參考代碼:
| def saveFile(data): save_path = 'D:\\temp.out' f_obj = open(save_path, 'wb') # wb 表示打開(kāi)方式 f_obj.write(data) f_obj.close() # 這里省略爬蟲(chóng)代碼 # ... # 爬到的數(shù)據(jù)放到 dat 變量里 # 將 dat 變量保存到 D 盤(pán)下 saveFile(dat) |
下回我們會(huì)用 Python 來(lái)爬那些需要登錄之后才能看到的信息. 在那之前, 我已經(jīng)對(duì) Fiddler 稍微熟悉了. 希望一起學(xué)習(xí)的也提前安裝個(gè) Fiddler 玩一下.
(轉(zhuǎn)載請(qǐng)注明: Jecvay Notes)