般而言,從特點(diǎn)上對(duì)反爬蟲手段進(jìn)行細(xì)分,可以有信息校驗(yàn)型反爬蟲、動(dòng)態(tài)渲染型反爬蟲、文本混淆型反爬蟲、特征識(shí)別型反爬蟲,等等。
其中,文本混淆反爬蟲最為有趣。
知識(shí)點(diǎn)植入:文本混淆可以有效地避免爬蟲獲取Web應(yīng)用中重要的文字?jǐn)?shù)據(jù)。反爬蟲的前提是不能影響用戶正常瀏覽網(wǎng)頁(yè)和閱讀文字內(nèi)容,直接混淆文本很容易被看出來,開發(fā)者通常是利用CSS的特性來實(shí)現(xiàn)混淆。
找?guī)讉€(gè)常見的文本混淆手段給大家康康。
CSS偏移反爬蟲指的是利用CSS樣式將亂序的文字,排版為人類正常閱讀的順序。
比如,去哪兒網(wǎng)的航班查詢……
你以為所爬即所見?Too young too naive!
SVG是用于描述二維矢量圖形的一種圖形格式。
由于SVG中的圖形代表的也是一個(gè)個(gè)文字,所以在使用時(shí)必須在后端或者前端將真實(shí)的文字與對(duì)應(yīng)的SVG圖形進(jìn)行映射和替換,因此,這種反爬蟲手段被稱為SVG映射反爬蟲。
舉個(gè)大眾點(diǎn)評(píng)網(wǎng)的例子。
打開瀏覽器并訪問:https://www.dianping.com/shop/14741057
我們可以看一看商家電話或評(píng)分的 HTML 代碼。
嘿嘿嘿,<d>標(biāo)簽占位警告!Surprise~
大眾點(diǎn)評(píng)中的商家號(hào)碼并不是全部使用<d>標(biāo)簽代替,其中有部分使用了數(shù)字。但是仔細(xì)觀察一下就可以發(fā)現(xiàn)商家號(hào)碼的數(shù)量等于<d>標(biāo)簽數(shù)量加上數(shù)字的數(shù)量,說明<d>標(biāo)簽的class 屬性值與數(shù)字也有可能是一一對(duì)應(yīng)的映射關(guān)系。
繼續(xù)往下看,大眾點(diǎn)評(píng)的商家營(yíng)業(yè)時(shí)間部分的 HTML 代碼。
原來,除了數(shù)字映射之外,還對(duì)中文進(jìn)行了映射!這就給爬蟲造成了很大的難度。
面對(duì)這樣的問題,爬蟲工程師必須找到文字映射規(guī)律,并且能夠?qū)崿F(xiàn)映射算法才能繞過這種反爬手段哦!
————
爬蟲技術(shù)和反爬蟲技術(shù)在不斷斗爭(zhēng)的過程中變得越來越高深與復(fù)雜,從簡(jiǎn)單的 User-Agent 識(shí)別到混淆驗(yàn)證碼加密,“花樣”越來越多,破解難度也越來越大。
想知道這些反爬蟲手段怎么繞過嗎?更多爬蟲與反爬蟲技術(shù)請(qǐng)見《Python3反爬蟲原理與繞過實(shí)戰(zhàn)》。以上文本混淆反爬蟲的案例,都出自這本書~
作者:韋世東
這本書對(duì)爬蟲技術(shù)與反爬蟲技術(shù)的對(duì)抗過程進(jìn)行了深入的研究,并詳細(xì)介紹了其中的原理和具體實(shí)現(xiàn)方法。
最重要的是,作者針對(duì)各類反爬蟲給出了對(duì)應(yīng)的繞過和破解方案!
書中還提到了瀏覽器的基本結(jié)構(gòu)、網(wǎng)頁(yè)渲染原理、加密和混淆規(guī)范,還有很多 RFC 文檔。無論是開發(fā)者還是爬蟲工程師,熟讀常見的 RFC 文檔對(duì)工作會(huì)有很大的幫助。
對(duì)于反爬蟲工程師來說,動(dòng)手實(shí)踐很重要。這本書特別為讀者準(zhǔn)備了一個(gè)練習(xí)平臺(tái),其中包含 21 個(gè)示例,練習(xí)平臺(tái)上的示例均為本書作者編寫,且與本書示例一一對(duì)應(yīng)。因此,示例內(nèi)容不會(huì)改動(dòng),并且無須擔(dān)心相關(guān)的法律問題,這保證了大家的學(xué)習(xí)能順利進(jìn)行。
干貨滿滿,推薦給大家。
讀:本文主要分為兩個(gè)部分:一部分是網(wǎng)絡(luò)爬蟲的概述,幫助大家詳細(xì)了解網(wǎng)絡(luò)爬蟲;另一部分是HTTP請(qǐng)求的Python實(shí)現(xiàn),幫助大家了解Python中實(shí)現(xiàn)HTTP請(qǐng)求的各種方式,以便具備編寫HTTP網(wǎng)絡(luò)程序的能力。
作者:范傳輝
如需轉(zhuǎn)載請(qǐng)聯(lián)系華章科技
接下來從網(wǎng)絡(luò)爬蟲的概念、用處與價(jià)值和結(jié)構(gòu)等三個(gè)方面,讓大家對(duì)網(wǎng)絡(luò)爬蟲有一個(gè)基本的了解。
1. 網(wǎng)絡(luò)爬蟲及其應(yīng)用
隨著網(wǎng)絡(luò)的迅速發(fā)展,萬維網(wǎng)成為大量信息的載體,如何有效地提取并利用這些信息成為一個(gè)巨大的挑戰(zhàn),網(wǎng)絡(luò)爬蟲應(yīng)運(yùn)而生。網(wǎng)絡(luò)爬蟲(又被稱為網(wǎng)頁(yè)蜘蛛、網(wǎng)絡(luò)機(jī)器人),是一種按照一定的規(guī)則,自動(dòng)地抓取萬維網(wǎng)信息的程序或者腳本。下面通過圖3-1展示一下網(wǎng)絡(luò)爬蟲在互聯(lián)網(wǎng)中起到的作用:
▲圖3-1 網(wǎng)絡(luò)爬蟲
網(wǎng)絡(luò)爬蟲按照系統(tǒng)結(jié)構(gòu)和實(shí)現(xiàn)技術(shù),大致可以分為以下幾種類型:通用網(wǎng)絡(luò)爬蟲、聚焦網(wǎng)絡(luò)爬蟲、增量式網(wǎng)絡(luò)爬蟲、深層網(wǎng)絡(luò)爬蟲。實(shí)際的網(wǎng)絡(luò)爬蟲系統(tǒng)通常是幾種爬蟲技術(shù)相結(jié)合實(shí)現(xiàn)的。
搜索引擎(Search Engine),例如傳統(tǒng)的通用搜索引擎baidu、Yahoo和Google等,是一種大型復(fù)雜的網(wǎng)絡(luò)爬蟲,屬于通用性網(wǎng)絡(luò)爬蟲的范疇。但是通用性搜索引擎存在著一定的局限性:
為了解決上述問題,定向抓取相關(guān)網(wǎng)頁(yè)資源的聚焦爬蟲應(yīng)運(yùn)而生。
聚焦爬蟲是一個(gè)自動(dòng)下載網(wǎng)頁(yè)的程序,它根據(jù)既定的抓取目標(biāo),有選擇地訪問萬維網(wǎng)上的網(wǎng)頁(yè)與相關(guān)的鏈接,獲取所需要的信息。與通用爬蟲不同,聚焦爬蟲并不追求大的覆蓋,而將目標(biāo)定為抓取與某一特定主題內(nèi)容相關(guān)的網(wǎng)頁(yè),為面向主題的用戶查詢準(zhǔn)備數(shù)據(jù)資源。
說完了聚焦爬蟲,接下來再說一下增量式網(wǎng)絡(luò)爬蟲。增量式網(wǎng)絡(luò)爬蟲是指對(duì)已下載網(wǎng)頁(yè)采取增量式更新和只爬行新產(chǎn)生的或者已經(jīng)發(fā)生變化網(wǎng)頁(yè)的爬蟲,它能夠在一定程度上保證所爬行的頁(yè)面是盡可能新的頁(yè)面。
和周期性爬行和刷新頁(yè)面的網(wǎng)絡(luò)爬蟲相比,增量式爬蟲只會(huì)在需要的時(shí)候爬行新產(chǎn)生或發(fā)生更新的頁(yè)面,并不重新下載沒有發(fā)生變化的頁(yè)面,可有效減少數(shù)據(jù)下載量,及時(shí)更新已爬行的網(wǎng)頁(yè),減小時(shí)間和空間上的耗費(fèi),但是增加了爬行算法的復(fù)雜度和實(shí)現(xiàn)難度。
例如:想獲取趕集網(wǎng)的招聘信息,以前爬取過的數(shù)據(jù)沒有必要重復(fù)爬取,只需要獲取更新的招聘數(shù)據(jù),這時(shí)候就要用到增量式爬蟲。
最后說一下深層網(wǎng)絡(luò)爬蟲。Web頁(yè)面按存在方式可以分為表層網(wǎng)頁(yè)和深層網(wǎng)頁(yè)。表層網(wǎng)頁(yè)是指?jìng)鹘y(tǒng)搜索引擎可以索引的頁(yè)面,以超鏈接可以到達(dá)的靜態(tài)網(wǎng)頁(yè)為主構(gòu)成的Web頁(yè)面。深層網(wǎng)絡(luò)是那些大部分內(nèi)容不能通過靜態(tài)鏈接獲取的、隱藏在搜索表單后的,只有用戶提交一些關(guān)鍵詞才能獲得的Web頁(yè)面。
例如用戶登錄或者注冊(cè)才能訪問的頁(yè)面。可以想象這樣一個(gè)場(chǎng)景:爬取貼吧或者論壇中的數(shù)據(jù),必須在用戶登錄后,有權(quán)限的情況下才能獲取完整的數(shù)據(jù)。
2. 網(wǎng)絡(luò)爬蟲結(jié)構(gòu)
下面用一個(gè)通用的網(wǎng)絡(luò)爬蟲結(jié)構(gòu)來說明網(wǎng)絡(luò)爬蟲的基本工作流程,如圖3-4所示。
▲圖3-4 網(wǎng)絡(luò)爬蟲結(jié)構(gòu)
網(wǎng)絡(luò)爬蟲的基本工作流程如下:
通過上面的網(wǎng)絡(luò)爬蟲結(jié)構(gòu),我們可以看到讀取URL、下載網(wǎng)頁(yè)是每一個(gè)爬蟲必備而且關(guān)鍵的功能,這就需要和HTTP請(qǐng)求打交道。接下來講解Python中實(shí)現(xiàn)HTTP請(qǐng)求的三種方式:urllib2/urllib、httplib/urllib以及Requests。
1. urllib2/urllib實(shí)現(xiàn)
urllib2和urllib是Python中的兩個(gè)內(nèi)置模塊,要實(shí)現(xiàn)HTTP功能,實(shí)現(xiàn)方式是以u(píng)rllib2為主,urllib為輔。
1.1 首先實(shí)現(xiàn)一個(gè)完整的請(qǐng)求與響應(yīng)模型
urllib2提供一個(gè)基礎(chǔ)函數(shù)urlopen,通過向指定的URL發(fā)出請(qǐng)求來獲取數(shù)據(jù)。最簡(jiǎn)單的形式是:
import urllib2 response=urllib2.urlopen('http://www.zhihu.com') html=response.read() print html
其實(shí)可以將上面對(duì)http://www.zhihu.com的請(qǐng)求響應(yīng)分為兩步,一步是請(qǐng)求,一步是響應(yīng),形式如下:
import urllib2 # 請(qǐng)求 request=urllib2.Request('http://www.zhihu.com') # 響應(yīng) response=urllib2.urlopen(request) html=response.read() print html
上面這兩種形式都是GET請(qǐng)求,接下來演示一下POST請(qǐng)求,其實(shí)大同小異,只是增加了請(qǐng)求數(shù)據(jù),這時(shí)候用到了urllib。示例如下:
import urllib import urllib2 url='http://www.xxxxxx.com/login' postdata={'username' : 'qiye', 'password' : 'qiye_pass'} # info 需要被編碼為urllib2能理解的格式,這里用到的是urllib data=urllib.urlencode(postdata) req=urllib2.Request(url, data) response=urllib2.urlopen(req) html=response.read()
但是有時(shí)會(huì)出現(xiàn)這種情況:即使POST請(qǐng)求的數(shù)據(jù)是對(duì)的,但是服務(wù)器拒絕你的訪問。這是為什么呢?問題出在請(qǐng)求中的頭信息,服務(wù)器會(huì)檢驗(yàn)請(qǐng)求頭,來判斷是否是來自瀏覽器的訪問,這也是反爬蟲的常用手段。
1.2 請(qǐng)求頭headers處理
將上面的例子改寫一下,加上請(qǐng)求頭信息,設(shè)置一下請(qǐng)求頭中的User-Agent域和Referer域信息。
import urllib import urllib2 url='http://www.xxxxxx.com/login' user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' referer='http://www.xxxxxx.com/' postdata={'username' : 'qiye', 'password' : 'qiye_pass'} # 將user_agent,referer寫入頭信息 headers={'User-Agent':user_agent,'Referer':referer} data=urllib.urlencode(postdata) req=urllib2.Request(url, data,headers) response=urllib2.urlopen(req) html=response.read()
也可以這樣寫,使用add_header來添加請(qǐng)求頭信息,修改如下:
import urllib import urllib2 url='http://www.xxxxxx.com/login' user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' referer='http://www.xxxxxx.com/' postdata={'username' : 'qiye', 'password' : 'qiye_pass'} data=urllib.urlencode(postdata) req=urllib2.Request(url) # 將user_agent,referer寫入頭信息 req.add_header('User-Agent',user_agent) req.add_header('Referer',referer) req.add_data(data) response=urllib2.urlopen(req) html=response.read()
對(duì)有些header要特別留意,服務(wù)器會(huì)針對(duì)這些header做檢查,例如:
1.3 Cookie處理
urllib2對(duì)Cookie的處理也是自動(dòng)的,使用CookieJar函數(shù)進(jìn)行Cookie的管理。如果需要得到某個(gè)Cookie項(xiàng)的值,可以這么做:
import urllib2 import cookielib cookie=cookielib.CookieJar() opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) response=opener.open('http://www.zhihu.com') for item in cookie: print item.name+':'+item.value
但是有時(shí)候會(huì)遇到這種情況,我們不想讓urllib2自動(dòng)處理,我們想自己添加Cookie的內(nèi)容,可以通過設(shè)置請(qǐng)求頭中的Cookie域來做:
import urllib2 opener=urllib2.build_opener() opener.addheaders.append( ( 'Cookie', 'email=' + "xxxxxxx@163.com" ) ) req=urllib2.Request( "http://www.zhihu.com/" ) response=opener.open(req) print response.headers retdata=response.read()
1.4 Timeout設(shè)置超時(shí)
在Python2.6之前的版本,urllib2的API并沒有暴露Timeout的設(shè)置,要設(shè)置Timeout值,只能更改Socket的全局Timeout值。示例如下:
import urllib2 import socket socket.setdefaulttimeout(10) # 10 秒鐘后超時(shí) urllib2.socket.setdefaulttimeout(10) # 另一種方式
在Python2.6及新的版本中,urlopen函數(shù)提供了對(duì)Timeout的設(shè)置,示例如下:
import urllib2 request=urllib2.Request('http://www.zhihu.com') response=urllib2.urlopen(request,timeout=2) html=response.read() print html
1.5 獲取HTTP響應(yīng)碼
對(duì)于200 OK來說,只要使用urlopen返回的response對(duì)象的getcode()方法就可以得到HTTP的返回碼。但對(duì)其他返回碼來說,urlopen會(huì)拋出異常。這時(shí)候,就要檢查異常對(duì)象的code屬性了,示例如下:
import urllib2 try: response=urllib2.urlopen('http://www.google.com') print response except urllib2.HTTPError as e: if hasattr(e, 'code'): print 'Error code:',e.code
1.6 重定向
urllib2默認(rèn)情況下會(huì)針對(duì)HTTP 3XX返回碼自動(dòng)進(jìn)行重定向動(dòng)作。要檢測(cè)是否發(fā)生了重定向動(dòng)作,只要檢查一下Response的URL和Request的URL是否一致就可以了,示例如下:
import urllib2 response=urllib2.urlopen('http://www.zhihu.cn') isRedirected=response.geturl()=='http://www.zhihu.cn'
如果不想自動(dòng)重定向,可以自定義HTTPRedirectHandler類,示例如下:
import urllib2 class RedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): result=urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) result.status=code result.newurl=result.geturl() return result opener=urllib2.build_opener(RedirectHandler) opener.open('http://www.zhihu.cn')
1.7 Proxy的設(shè)置
在做爬蟲開發(fā)中,必不可少地會(huì)用到代理。urllib2默認(rèn)會(huì)使用環(huán)境變量http_proxy來設(shè)置HTTP Proxy。但是我們一般不采用這種方式,而是使用ProxyHandler在程序中動(dòng)態(tài)設(shè)置代理,示例代碼如下:
import urllib2 proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8087'}) opener=urllib2.build_opener([proxy,]) urllib2.install_opener(opener) response=urllib2.urlopen('http://www.zhihu.com/') print response.read()
這里要注意的一個(gè)細(xì)節(jié),使用urllib2.install_opener()會(huì)設(shè)置urllib2的全局opener,之后所有的HTTP訪問都會(huì)使用這個(gè)代理。這樣使用會(huì)很方便,但不能做更細(xì)粒度的控制,比如想在程序中使用兩個(gè)不同的Proxy設(shè)置,這種場(chǎng)景在爬蟲中很常見。比較好的做法是不使用install_opener去更改全局的設(shè)置,而只是直接調(diào)用opener的open方法代替全局的urlopen方法,修改如下:
import urllib2 proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8087'}) opener=urllib2.build_opener(proxy,) response=opener.open("http://www.zhihu.com/") print response.read()
2. httplib/urllib實(shí)現(xiàn)
httplib模塊是一個(gè)底層基礎(chǔ)模塊,可以看到建立HTTP請(qǐng)求的每一步,但是實(shí)現(xiàn)的功能比較少,正常情況下比較少用到。在Python爬蟲開發(fā)中基本上用不到,所以在此只是進(jìn)行一下知識(shí)普及。下面介紹一下常用的對(duì)象和函數(shù):
接下來演示一下GET請(qǐng)求和POST請(qǐng)求的發(fā)送,首先是GET請(qǐng)求的示例,如下所示:
import httplib conn=None try: conn=httplib.HTTPConnection("www.zhihu.com") conn.request("GET", "/") response=conn.getresponse() print response.status, response.reason print '-' * 40 headers=response.getheaders() for h in headers: print h print '-' * 40 print response.msg except Exception,e: print e finally: if conn: conn.close()
POST請(qǐng)求的示例如下:
import httplib, urllib conn=None try: params=urllib.urlencode({'name': 'qiye', 'age': 22}) headers={"Content-type": "application/x-www-form-urlencoded" , "Accept": "text/plain"} conn=httplib.HTTPConnection("www.zhihu.com", 80, timeout=3) conn.request("POST", "/login", params, headers) response=conn.getresponse() print response.getheaders() # 獲取頭信息 print response.status print response.read() except Exception, e: print e finally: if conn: conn.close()
3. 更人性化的Requests
Python中Requests實(shí)現(xiàn)HTTP請(qǐng)求的方式,是本人極力推薦的,也是在Python爬蟲開發(fā)中最為常用的方式。Requests實(shí)現(xiàn)HTTP請(qǐng)求非常簡(jiǎn)單,操作更加人性化。
Requests庫(kù)是第三方模塊,需要額外進(jìn)行安裝。Requests是一個(gè)開源庫(kù),源碼位于:
GitHub: https://github.com/kennethreitz/requests
希望大家多多支持作者。
使用Requests庫(kù)需要先進(jìn)行安裝,一般有兩種安裝方式:
如何驗(yàn)證Requests模塊安裝是否成功呢?在Python的shell中輸入import requests,如果不報(bào)錯(cuò),則是安裝成功。如圖3-5所示。
▲圖3-5 驗(yàn)證Requests安裝
3.1 首先還是實(shí)現(xiàn)一個(gè)完整的請(qǐng)求與響應(yīng)模型
以GET請(qǐng)求為例,最簡(jiǎn)單的形式如下:
import requests r=requests.get('http://www.baidu.com') print r.content
大家可以看到比urllib2實(shí)現(xiàn)方式的代碼量少。接下來演示一下POST請(qǐng)求,同樣是非常簡(jiǎn)短,更加具有Python風(fēng)格。示例如下:
import requests postdata={'key':'value'} r=requests.post('http://www.xxxxxx.com/login',data=postdata) print r.content
HTTP中的其他請(qǐng)求方式也可以用Requests來實(shí)現(xiàn),示例如下:
r=requests.put('http://www.xxxxxx.com/put', data={'key':'value'}) r=requests.delete('http://www.xxxxxx.com/delete') r=requests.head('http://www.xxxxxx.com/get') r=requests.options('http://www.xxxxxx.com/get')
接著講解一下稍微復(fù)雜的方式,大家肯定見過類似這樣的URL:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
就是在網(wǎng)址后面緊跟著“?”,“?”后面還有參數(shù)。那么這樣的GET請(qǐng)求該如何發(fā)送呢?肯定有人會(huì)說,直接將完整的URL帶入即可,不過Requests還提供了其他方式,示例如下:
import requests payload={'Keywords': 'blog:qiyeboy','pageindex':1} r=requests.get('http://zzk.cnblogs.com/s/blogpost', params=payload) print r.url
通過打印結(jié)果,我們看到最終的URL變成了:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
3.2 響應(yīng)與編碼
還是從代碼入手,示例如下:
import requests r=requests.get('http://www.baidu.com') print 'content-->'+r.content print 'text-->'+r.text print 'encoding-->'+r.encoding r.encoding='utf-8' print 'new text-->'+r.text
其中r.content返回的是字節(jié)形式,r.text返回的是文本形式,r.encoding返回的是根據(jù)HTTP頭猜測(cè)的網(wǎng)頁(yè)編碼格式。
輸出結(jié)果中:“text-->”之后的內(nèi)容在控制臺(tái)看到的是亂碼,“encoding-->”之后的內(nèi)容是ISO-8859-1(實(shí)際上的編碼格式是UTF-8),由于Requests猜測(cè)編碼錯(cuò)誤,導(dǎo)致解析文本出現(xiàn)了亂碼。Requests提供了解決方案,可以自行設(shè)置編碼格式,r.encoding='utf-8'設(shè)置成UTF-8之后,“new text-->”的內(nèi)容就不會(huì)出現(xiàn)亂碼。
但是這種手動(dòng)的方式略顯笨拙,下面提供一種更加簡(jiǎn)便的方式:chardet,這是一個(gè)非常優(yōu)秀的字符串/文件編碼檢測(cè)模塊。安裝方式如下:
pip install chardet
安裝完成后,使用chardet.detect()返回字典,其中confidence是檢測(cè)精確度,encoding是編碼形式。示例如下:
import requests r=requests.get('http://www.baidu.com') print chardet.detect(r.content) r.encoding=chardet.detect(r.content)['encoding'] print r.text
直接將chardet探測(cè)到的編碼,賦給r.encoding實(shí)現(xiàn)解碼,r.text輸出就不會(huì)有亂碼了。
除了上面那種直接獲取全部響應(yīng)的方式,還有一種流模式,示例如下:
import requests r=requests.get('http://www.baidu.com',stream=True) print r.raw.read(10)
設(shè)置stream=True標(biāo)志位,使響應(yīng)以字節(jié)流方式進(jìn)行讀取,r.raw.read函數(shù)指定讀取的字節(jié)數(shù)。
3.3 請(qǐng)求頭headers處理
Requests對(duì)headers的處理和urllib2非常相似,在Requests的get函數(shù)中添加headers參數(shù)即可。示例如下:
import requests user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} r=requests.get('http://www.baidu.com',headers=headers) print r.content
3.4 響應(yīng)碼code和響應(yīng)頭headers處理
獲取響應(yīng)碼是使用Requests中的status_code字段,獲取響應(yīng)頭使用Requests中的headers字段。示例如下:
import requests r=requests.get('http://www.baidu.com') if r.status_code==requests.codes.ok: print r.status_code# 響應(yīng)碼 print r.headers# 響應(yīng)頭 print r.headers.get('content-type')# 推薦使用這種獲取方式,獲取其中的某個(gè)字段 print r.headers['content-type']# 不推薦使用這種獲取方式 else: r.raise_for_status()
上述程序中,r.headers包含所有的響應(yīng)頭信息,可以通過get函數(shù)獲取其中的某一個(gè)字段,也可以通過字典引用的方式獲取字典值,但是不推薦,因?yàn)槿绻侄沃袥]有這個(gè)字段,第二種方式會(huì)拋出異常,第一種方式會(huì)返回None。
r.raise_for_status()是用來主動(dòng)地產(chǎn)生一個(gè)異常,當(dāng)響應(yīng)碼是4XX或5XX時(shí),raise_for_status()函數(shù)會(huì)拋出異常,而響應(yīng)碼為200時(shí),raise_for_status()函數(shù)返回None。
3.5 Cookie處理
如果響應(yīng)中包含Cookie的值,可以如下方式獲取Cookie字段的值,示例如下:
import requests user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} r=requests.get('http://www.baidu.com',headers=headers) # 遍歷出所有的cookie字段的值 for cookie in r.cookies.keys(): print cookie+':'+r.cookies.get(cookie)
如果想自定義Cookie值發(fā)送出去,可以使用以下方式,示例如下:
import requests user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} cookies=dict(name='qiye',age='10') r=requests.get('http://www.baidu.com',headers=headers,cookies=cookies) print r.text
還有一種更加高級(jí),且能自動(dòng)處理Cookie的方式,有時(shí)候我們不需要關(guān)心Cookie值是多少,只是希望每次訪問的時(shí)候,程序自動(dòng)把Cookie的值帶上,像瀏覽器一樣。Requests提供了一個(gè)session的概念,在連續(xù)訪問網(wǎng)頁(yè),處理登錄跳轉(zhuǎn)時(shí)特別方便,不需要關(guān)注具體細(xì)節(jié)。使用方法示例如下:
import Requests oginUrl='http://www.xxxxxxx.com/login' s=requests.Session() #首先訪問登錄界面,作為游客,服務(wù)器會(huì)先分配一個(gè)cookie r=s.get(loginUrl,allow_redirects=True) datas={'name':'qiye','passwd':'qiye'} #向登錄鏈接發(fā)送post請(qǐng)求,驗(yàn)證成功,游客權(quán)限轉(zhuǎn)為會(huì)員權(quán)限 r=s.post(loginUrl, data=datas,allow_redirects=True) print r.text
上面的這段程序,其實(shí)是正式做Python開發(fā)中遇到的問題,如果沒有第一步訪問登錄的頁(yè)面,而是直接向登錄鏈接發(fā)送Post請(qǐng)求,系統(tǒng)會(huì)把你當(dāng)做非法用戶,因?yàn)樵L問登錄界面時(shí)會(huì)分配一個(gè)Cookie,需要將這個(gè)Cookie在發(fā)送Post請(qǐng)求時(shí)帶上,這種使用Session函數(shù)處理Cookie的方式之后會(huì)很常用。
3.6 重定向與歷史信息
處理重定向只是需要設(shè)置一下allow_redirects字段即可,例如:
r=requests.get('http://www.baidu.com',allow_redirects=True)
將allow_redirects設(shè)置為True,則是允許重定向;設(shè)置為False,則是禁止重定向。如果是允許重定向,可以通過r.history字段查看歷史信息,即訪問成功之前的所有請(qǐng)求跳轉(zhuǎn)信息。示例如下:
import requests r=requests.get('http://github.com') print r.url print r.status_code print r.history
打印結(jié)果如下:
https://github.com/ 200 (<Response [301]>,)
上面的示例代碼顯示的效果是訪問GitHub網(wǎng)址時(shí),會(huì)將所有的HTTP請(qǐng)求全部重定向?yàn)镠TTPS。
3.7 超時(shí)設(shè)置
超時(shí)選項(xiàng)是通過參數(shù)timeout來進(jìn)行設(shè)置的,示例如下:
requests.get('http://github.com', timeout=2)
3.8 代理設(shè)置
使用代理Proxy,你可以為任意請(qǐng)求方法通過設(shè)置proxies參數(shù)來配置單個(gè)請(qǐng)求:
import requests proxies={ "http": "http://0.10.1.10:3128", "https": "http://10.10.1.10:1080", } requests.get("http://example.org", proxies=proxies)
也可以通過環(huán)境變量HTTP_PROXY和HTTPS_PROXY?來配置代理,但是在爬蟲開發(fā)中不常用。你的代理需要使用HTTP Basic Auth,可以使用http://user:password@host/語(yǔ)法:
proxies={ "http": "http://user:pass@10.10.1.10:3128/", }
本文主要講解了網(wǎng)絡(luò)爬蟲的結(jié)構(gòu)和應(yīng)用,以及Python實(shí)現(xiàn)HTTP請(qǐng)求的幾種方法。希望大家對(duì)本文中的網(wǎng)絡(luò)爬蟲工作流程和Requests實(shí)現(xiàn)HTTP請(qǐng)求的方式重點(diǎn)吸收消化。
關(guān)于作者:范傳輝,資深網(wǎng)蟲,Python開發(fā)者,參與開發(fā)了多項(xiàng)網(wǎng)絡(luò)應(yīng)用,在實(shí)際開發(fā)中積累了豐富的實(shí)戰(zhàn)經(jīng)驗(yàn),并善于總結(jié),貢獻(xiàn)了多篇技術(shù)文章廣受好評(píng)。研究興趣是網(wǎng)絡(luò)安全、爬蟲技術(shù)、數(shù)據(jù)分析、驅(qū)動(dòng)開發(fā)等技術(shù)。
本文摘編自《Python爬蟲開發(fā)與項(xiàng)目實(shí)戰(zhàn)》,經(jīng)出版方授權(quán)發(fā)布。
延伸閱讀《Python爬蟲開發(fā)與項(xiàng)目實(shí)戰(zhàn)》
推薦語(yǔ):零基礎(chǔ)學(xué)習(xí)爬蟲技術(shù),從Python和Web前端基礎(chǔ)開始講起,由淺入深,包含大量案例,實(shí)用性強(qiáng)。
讀:本文主要分為兩個(gè)部分:一部分是網(wǎng)絡(luò)爬蟲的概述,幫助大家詳細(xì)了解網(wǎng)絡(luò)爬蟲;另一部分是HTTP請(qǐng)求的Python實(shí)現(xiàn),幫助大家了解Python中實(shí)現(xiàn)HTTP請(qǐng)求的各種方式,以便具備編寫HTTP網(wǎng)絡(luò)程序的能力。
作者:范傳輝
如需轉(zhuǎn)載請(qǐng)聯(lián)系華章科技
接下來從網(wǎng)絡(luò)爬蟲的概念、用處與價(jià)值和結(jié)構(gòu)等三個(gè)方面,讓大家對(duì)網(wǎng)絡(luò)爬蟲有一個(gè)基本的了解。
1. 網(wǎng)絡(luò)爬蟲及其應(yīng)用
隨著網(wǎng)絡(luò)的迅速發(fā)展,萬維網(wǎng)成為大量信息的載體,如何有效地提取并利用這些信息成為一個(gè)巨大的挑戰(zhàn),網(wǎng)絡(luò)爬蟲應(yīng)運(yùn)而生。網(wǎng)絡(luò)爬蟲(又被稱為網(wǎng)頁(yè)蜘蛛、網(wǎng)絡(luò)機(jī)器人),是一種按照一定的規(guī)則,自動(dòng)地抓取萬維網(wǎng)信息的程序或者腳本。下面通過圖3-1展示一下網(wǎng)絡(luò)爬蟲在互聯(lián)網(wǎng)中起到的作用:
▲圖3-1 網(wǎng)絡(luò)爬蟲
網(wǎng)絡(luò)爬蟲按照系統(tǒng)結(jié)構(gòu)和實(shí)現(xiàn)技術(shù),大致可以分為以下幾種類型:通用網(wǎng)絡(luò)爬蟲、聚焦網(wǎng)絡(luò)爬蟲、增量式網(wǎng)絡(luò)爬蟲、深層網(wǎng)絡(luò)爬蟲。實(shí)際的網(wǎng)絡(luò)爬蟲系統(tǒng)通常是幾種爬蟲技術(shù)相結(jié)合實(shí)現(xiàn)的。
搜索引擎(Search Engine),例如傳統(tǒng)的通用搜索引擎baidu、Yahoo和Google等,是一種大型復(fù)雜的網(wǎng)絡(luò)爬蟲,屬于通用性網(wǎng)絡(luò)爬蟲的范疇。但是通用性搜索引擎存在著一定的局限性:
為了解決上述問題,定向抓取相關(guān)網(wǎng)頁(yè)資源的聚焦爬蟲應(yīng)運(yùn)而生。
聚焦爬蟲是一個(gè)自動(dòng)下載網(wǎng)頁(yè)的程序,它根據(jù)既定的抓取目標(biāo),有選擇地訪問萬維網(wǎng)上的網(wǎng)頁(yè)與相關(guān)的鏈接,獲取所需要的信息。與通用爬蟲不同,聚焦爬蟲并不追求大的覆蓋,而將目標(biāo)定為抓取與某一特定主題內(nèi)容相關(guān)的網(wǎng)頁(yè),為面向主題的用戶查詢準(zhǔn)備數(shù)據(jù)資源。
說完了聚焦爬蟲,接下來再說一下增量式網(wǎng)絡(luò)爬蟲。增量式網(wǎng)絡(luò)爬蟲是指對(duì)已下載網(wǎng)頁(yè)采取增量式更新和只爬行新產(chǎn)生的或者已經(jīng)發(fā)生變化網(wǎng)頁(yè)的爬蟲,它能夠在一定程度上保證所爬行的頁(yè)面是盡可能新的頁(yè)面。
和周期性爬行和刷新頁(yè)面的網(wǎng)絡(luò)爬蟲相比,增量式爬蟲只會(huì)在需要的時(shí)候爬行新產(chǎn)生或發(fā)生更新的頁(yè)面,并不重新下載沒有發(fā)生變化的頁(yè)面,可有效減少數(shù)據(jù)下載量,及時(shí)更新已爬行的網(wǎng)頁(yè),減小時(shí)間和空間上的耗費(fèi),但是增加了爬行算法的復(fù)雜度和實(shí)現(xiàn)難度。
例如:想獲取趕集網(wǎng)的招聘信息,以前爬取過的數(shù)據(jù)沒有必要重復(fù)爬取,只需要獲取更新的招聘數(shù)據(jù),這時(shí)候就要用到增量式爬蟲。
最后說一下深層網(wǎng)絡(luò)爬蟲。Web頁(yè)面按存在方式可以分為表層網(wǎng)頁(yè)和深層網(wǎng)頁(yè)。表層網(wǎng)頁(yè)是指?jìng)鹘y(tǒng)搜索引擎可以索引的頁(yè)面,以超鏈接可以到達(dá)的靜態(tài)網(wǎng)頁(yè)為主構(gòu)成的Web頁(yè)面。深層網(wǎng)絡(luò)是那些大部分內(nèi)容不能通過靜態(tài)鏈接獲取的、隱藏在搜索表單后的,只有用戶提交一些關(guān)鍵詞才能獲得的Web頁(yè)面。
例如用戶登錄或者注冊(cè)才能訪問的頁(yè)面。可以想象這樣一個(gè)場(chǎng)景:爬取貼吧或者論壇中的數(shù)據(jù),必須在用戶登錄后,有權(quán)限的情況下才能獲取完整的數(shù)據(jù)。
2. 網(wǎng)絡(luò)爬蟲結(jié)構(gòu)
下面用一個(gè)通用的網(wǎng)絡(luò)爬蟲結(jié)構(gòu)來說明網(wǎng)絡(luò)爬蟲的基本工作流程,如圖3-4所示。
▲圖3-4 網(wǎng)絡(luò)爬蟲結(jié)構(gòu)
網(wǎng)絡(luò)爬蟲的基本工作流程如下:
通過上面的網(wǎng)絡(luò)爬蟲結(jié)構(gòu),我們可以看到讀取URL、下載網(wǎng)頁(yè)是每一個(gè)爬蟲必備而且關(guān)鍵的功能,這就需要和HTTP請(qǐng)求打交道。接下來講解Python中實(shí)現(xiàn)HTTP請(qǐng)求的三種方式:urllib2/urllib、httplib/urllib以及Requests。
1. urllib2/urllib實(shí)現(xiàn)
urllib2和urllib是Python中的兩個(gè)內(nèi)置模塊,要實(shí)現(xiàn)HTTP功能,實(shí)現(xiàn)方式是以u(píng)rllib2為主,urllib為輔。
1.1 首先實(shí)現(xiàn)一個(gè)完整的請(qǐng)求與響應(yīng)模型
urllib2提供一個(gè)基礎(chǔ)函數(shù)urlopen,通過向指定的URL發(fā)出請(qǐng)求來獲取數(shù)據(jù)。最簡(jiǎn)單的形式是:
import urllib2 response=urllib2.urlopen('http://www.zhihu.com') html=response.read() print html
其實(shí)可以將上面對(duì)http://www.zhihu.com的請(qǐng)求響應(yīng)分為兩步,一步是請(qǐng)求,一步是響應(yīng),形式如下:
import urllib2 # 請(qǐng)求 request=urllib2.Request('http://www.zhihu.com') # 響應(yīng) response=urllib2.urlopen(request) html=response.read() print html
上面這兩種形式都是GET請(qǐng)求,接下來演示一下POST請(qǐng)求,其實(shí)大同小異,只是增加了請(qǐng)求數(shù)據(jù),這時(shí)候用到了urllib。示例如下:
import urllib import urllib2 url='http://www.xxxxxx.com/login' postdata={'username' : 'qiye', 'password' : 'qiye_pass'} # info 需要被編碼為urllib2能理解的格式,這里用到的是urllib data=urllib.urlencode(postdata) req=urllib2.Request(url, data) response=urllib2.urlopen(req) html=response.read()
但是有時(shí)會(huì)出現(xiàn)這種情況:即使POST請(qǐng)求的數(shù)據(jù)是對(duì)的,但是服務(wù)器拒絕你的訪問。這是為什么呢?問題出在請(qǐng)求中的頭信息,服務(wù)器會(huì)檢驗(yàn)請(qǐng)求頭,來判斷是否是來自瀏覽器的訪問,這也是反爬蟲的常用手段。
1.2 請(qǐng)求頭headers處理
將上面的例子改寫一下,加上請(qǐng)求頭信息,設(shè)置一下請(qǐng)求頭中的User-Agent域和Referer域信息。
import urllib import urllib2 url='http://www.xxxxxx.com/login' user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' referer='http://www.xxxxxx.com/' postdata={'username' : 'qiye', 'password' : 'qiye_pass'} # 將user_agent,referer寫入頭信息 headers={'User-Agent':user_agent,'Referer':referer} data=urllib.urlencode(postdata) req=urllib2.Request(url, data,headers) response=urllib2.urlopen(req) html=response.read()
也可以這樣寫,使用add_header來添加請(qǐng)求頭信息,修改如下:
import urllib import urllib2 url='http://www.xxxxxx.com/login' user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' referer='http://www.xxxxxx.com/' postdata={'username' : 'qiye', 'password' : 'qiye_pass'} data=urllib.urlencode(postdata) req=urllib2.Request(url) # 將user_agent,referer寫入頭信息 req.add_header('User-Agent',user_agent) req.add_header('Referer',referer) req.add_data(data) response=urllib2.urlopen(req) html=response.read()
對(duì)有些header要特別留意,服務(wù)器會(huì)針對(duì)這些header做檢查,例如:
1.3 Cookie處理
urllib2對(duì)Cookie的處理也是自動(dòng)的,使用CookieJar函數(shù)進(jìn)行Cookie的管理。如果需要得到某個(gè)Cookie項(xiàng)的值,可以這么做:
import urllib2 import cookielib cookie=cookielib.CookieJar() opener=urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) response=opener.open('http://www.zhihu.com') for item in cookie: print item.name+':'+item.value
但是有時(shí)候會(huì)遇到這種情況,我們不想讓urllib2自動(dòng)處理,我們想自己添加Cookie的內(nèi)容,可以通過設(shè)置請(qǐng)求頭中的Cookie域來做:
import urllib2 opener=urllib2.build_opener() opener.addheaders.append( ( 'Cookie', 'email=' + "xxxxxxx@163.com" ) ) req=urllib2.Request( "http://www.zhihu.com/" ) response=opener.open(req) print response.headers retdata=response.read()
1.4 Timeout設(shè)置超時(shí)
在Python2.6之前的版本,urllib2的API并沒有暴露Timeout的設(shè)置,要設(shè)置Timeout值,只能更改Socket的全局Timeout值。示例如下:
import urllib2 import socket socket.setdefaulttimeout(10) # 10 秒鐘后超時(shí) urllib2.socket.setdefaulttimeout(10) # 另一種方式
在Python2.6及新的版本中,urlopen函數(shù)提供了對(duì)Timeout的設(shè)置,示例如下:
import urllib2 request=urllib2.Request('http://www.zhihu.com') response=urllib2.urlopen(request,timeout=2) html=response.read() print html
1.5 獲取HTTP響應(yīng)碼
對(duì)于200 OK來說,只要使用urlopen返回的response對(duì)象的getcode()方法就可以得到HTTP的返回碼。但對(duì)其他返回碼來說,urlopen會(huì)拋出異常。這時(shí)候,就要檢查異常對(duì)象的code屬性了,示例如下:
import urllib2 try: response=urllib2.urlopen('http://www.google.com') print response except urllib2.HTTPError as e: if hasattr(e, 'code'): print 'Error code:',e.code
1.6 重定向
urllib2默認(rèn)情況下會(huì)針對(duì)HTTP 3XX返回碼自動(dòng)進(jìn)行重定向動(dòng)作。要檢測(cè)是否發(fā)生了重定向動(dòng)作,只要檢查一下Response的URL和Request的URL是否一致就可以了,示例如下:
import urllib2 response=urllib2.urlopen('http://www.zhihu.cn') isRedirected=response.geturl()=='http://www.zhihu.cn'
如果不想自動(dòng)重定向,可以自定義HTTPRedirectHandler類,示例如下:
import urllib2 class RedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): result=urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) result.status=code result.newurl=result.geturl() return result opener=urllib2.build_opener(RedirectHandler) opener.open('http://www.zhihu.cn')
1.7 Proxy的設(shè)置
在做爬蟲開發(fā)中,必不可少地會(huì)用到代理。urllib2默認(rèn)會(huì)使用環(huán)境變量http_proxy來設(shè)置HTTP Proxy。但是我們一般不采用這種方式,而是使用ProxyHandler在程序中動(dòng)態(tài)設(shè)置代理,示例代碼如下:
import urllib2 proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8087'}) opener=urllib2.build_opener([proxy,]) urllib2.install_opener(opener) response=urllib2.urlopen('http://www.zhihu.com/') print response.read()
這里要注意的一個(gè)細(xì)節(jié),使用urllib2.install_opener()會(huì)設(shè)置urllib2的全局opener,之后所有的HTTP訪問都會(huì)使用這個(gè)代理。這樣使用會(huì)很方便,但不能做更細(xì)粒度的控制,比如想在程序中使用兩個(gè)不同的Proxy設(shè)置,這種場(chǎng)景在爬蟲中很常見。比較好的做法是不使用install_opener去更改全局的設(shè)置,而只是直接調(diào)用opener的open方法代替全局的urlopen方法,修改如下:
import urllib2 proxy=urllib2.ProxyHandler({'http': '127.0.0.1:8087'}) opener=urllib2.build_opener(proxy,) response=opener.open("http://www.zhihu.com/") print response.read()
2. httplib/urllib實(shí)現(xiàn)
httplib模塊是一個(gè)底層基礎(chǔ)模塊,可以看到建立HTTP請(qǐng)求的每一步,但是實(shí)現(xiàn)的功能比較少,正常情況下比較少用到。在Python爬蟲開發(fā)中基本上用不到,所以在此只是進(jìn)行一下知識(shí)普及。下面介紹一下常用的對(duì)象和函數(shù):
接下來演示一下GET請(qǐng)求和POST請(qǐng)求的發(fā)送,首先是GET請(qǐng)求的示例,如下所示:
import httplib conn=None try: conn=httplib.HTTPConnection("www.zhihu.com") conn.request("GET", "/") response=conn.getresponse() print response.status, response.reason print '-' * 40 headers=response.getheaders() for h in headers: print h print '-' * 40 print response.msg except Exception,e: print e finally: if conn: conn.close()
POST請(qǐng)求的示例如下:
import httplib, urllib conn=None try: params=urllib.urlencode({'name': 'qiye', 'age': 22}) headers={"Content-type": "application/x-www-form-urlencoded" , "Accept": "text/plain"} conn=httplib.HTTPConnection("www.zhihu.com", 80, timeout=3) conn.request("POST", "/login", params, headers) response=conn.getresponse() print response.getheaders() # 獲取頭信息 print response.status print response.read() except Exception, e: print e finally: if conn: conn.close()
3. 更人性化的Requests
Python中Requests實(shí)現(xiàn)HTTP請(qǐng)求的方式,是本人極力推薦的,也是在Python爬蟲開發(fā)中最為常用的方式。Requests實(shí)現(xiàn)HTTP請(qǐng)求非常簡(jiǎn)單,操作更加人性化。
Requests庫(kù)是第三方模塊,需要額外進(jìn)行安裝。Requests是一個(gè)開源庫(kù),源碼位于:
GitHub: https://github.com/kennethreitz/requests
希望大家多多支持作者。
使用Requests庫(kù)需要先進(jìn)行安裝,一般有兩種安裝方式:
如何驗(yàn)證Requests模塊安裝是否成功呢?在Python的shell中輸入import requests,如果不報(bào)錯(cuò),則是安裝成功。如圖3-5所示。
▲圖3-5 驗(yàn)證Requests安裝
3.1 首先還是實(shí)現(xiàn)一個(gè)完整的請(qǐng)求與響應(yīng)模型
以GET請(qǐng)求為例,最簡(jiǎn)單的形式如下:
import requests r=requests.get('http://www.baidu.com') print r.content
大家可以看到比urllib2實(shí)現(xiàn)方式的代碼量少。接下來演示一下POST請(qǐng)求,同樣是非常簡(jiǎn)短,更加具有Python風(fēng)格。示例如下:
import requests postdata={'key':'value'} r=requests.post('http://www.xxxxxx.com/login',data=postdata) print r.content
HTTP中的其他請(qǐng)求方式也可以用Requests來實(shí)現(xiàn),示例如下:
r=requests.put('http://www.xxxxxx.com/put', data={'key':'value'}) r=requests.delete('http://www.xxxxxx.com/delete') r=requests.head('http://www.xxxxxx.com/get') r=requests.options('http://www.xxxxxx.com/get')
接著講解一下稍微復(fù)雜的方式,大家肯定見過類似這樣的URL:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
就是在網(wǎng)址后面緊跟著“?”,“?”后面還有參數(shù)。那么這樣的GET請(qǐng)求該如何發(fā)送呢?肯定有人會(huì)說,直接將完整的URL帶入即可,不過Requests還提供了其他方式,示例如下:
import requests payload={'Keywords': 'blog:qiyeboy','pageindex':1} r=requests.get('http://zzk.cnblogs.com/s/blogpost', params=payload) print r.url
通過打印結(jié)果,我們看到最終的URL變成了:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
3.2 響應(yīng)與編碼
還是從代碼入手,示例如下:
import requests r=requests.get('http://www.baidu.com') print 'content-->'+r.content print 'text-->'+r.text print 'encoding-->'+r.encoding r.encoding='utf-8' print 'new text-->'+r.text
其中r.content返回的是字節(jié)形式,r.text返回的是文本形式,r.encoding返回的是根據(jù)HTTP頭猜測(cè)的網(wǎng)頁(yè)編碼格式。
輸出結(jié)果中:“text-->”之后的內(nèi)容在控制臺(tái)看到的是亂碼,“encoding-->”之后的內(nèi)容是ISO-8859-1(實(shí)際上的編碼格式是UTF-8),由于Requests猜測(cè)編碼錯(cuò)誤,導(dǎo)致解析文本出現(xiàn)了亂碼。Requests提供了解決方案,可以自行設(shè)置編碼格式,r.encoding='utf-8'設(shè)置成UTF-8之后,“new text-->”的內(nèi)容就不會(huì)出現(xiàn)亂碼。
但是這種手動(dòng)的方式略顯笨拙,下面提供一種更加簡(jiǎn)便的方式:chardet,這是一個(gè)非常優(yōu)秀的字符串/文件編碼檢測(cè)模塊。安裝方式如下:
pip install chardet
安裝完成后,使用chardet.detect()返回字典,其中confidence是檢測(cè)精確度,encoding是編碼形式。示例如下:
import requests r=requests.get('http://www.baidu.com') print chardet.detect(r.content) r.encoding=chardet.detect(r.content)['encoding'] print r.text
直接將chardet探測(cè)到的編碼,賦給r.encoding實(shí)現(xiàn)解碼,r.text輸出就不會(huì)有亂碼了。
除了上面那種直接獲取全部響應(yīng)的方式,還有一種流模式,示例如下:
import requests r=requests.get('http://www.baidu.com',stream=True) print r.raw.read(10)
設(shè)置stream=True標(biāo)志位,使響應(yīng)以字節(jié)流方式進(jìn)行讀取,r.raw.read函數(shù)指定讀取的字節(jié)數(shù)。
3.3 請(qǐng)求頭headers處理
Requests對(duì)headers的處理和urllib2非常相似,在Requests的get函數(shù)中添加headers參數(shù)即可。示例如下:
import requests user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} r=requests.get('http://www.baidu.com',headers=headers) print r.content
3.4 響應(yīng)碼code和響應(yīng)頭headers處理
獲取響應(yīng)碼是使用Requests中的status_code字段,獲取響應(yīng)頭使用Requests中的headers字段。示例如下:
import requests r=requests.get('http://www.baidu.com') if r.status_code==requests.codes.ok: print r.status_code# 響應(yīng)碼 print r.headers# 響應(yīng)頭 print r.headers.get('content-type')# 推薦使用這種獲取方式,獲取其中的某個(gè)字段 print r.headers['content-type']# 不推薦使用這種獲取方式 else: r.raise_for_status()
上述程序中,r.headers包含所有的響應(yīng)頭信息,可以通過get函數(shù)獲取其中的某一個(gè)字段,也可以通過字典引用的方式獲取字典值,但是不推薦,因?yàn)槿绻侄沃袥]有這個(gè)字段,第二種方式會(huì)拋出異常,第一種方式會(huì)返回None。
r.raise_for_status()是用來主動(dòng)地產(chǎn)生一個(gè)異常,當(dāng)響應(yīng)碼是4XX或5XX時(shí),raise_for_status()函數(shù)會(huì)拋出異常,而響應(yīng)碼為200時(shí),raise_for_status()函數(shù)返回None。
3.5 Cookie處理
如果響應(yīng)中包含Cookie的值,可以如下方式獲取Cookie字段的值,示例如下:
import requests user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} r=requests.get('http://www.baidu.com',headers=headers) # 遍歷出所有的cookie字段的值 for cookie in r.cookies.keys(): print cookie+':'+r.cookies.get(cookie)
如果想自定義Cookie值發(fā)送出去,可以使用以下方式,示例如下:
import requests user_agent='Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} cookies=dict(name='qiye',age='10') r=requests.get('http://www.baidu.com',headers=headers,cookies=cookies) print r.text
還有一種更加高級(jí),且能自動(dòng)處理Cookie的方式,有時(shí)候我們不需要關(guān)心Cookie值是多少,只是希望每次訪問的時(shí)候,程序自動(dòng)把Cookie的值帶上,像瀏覽器一樣。Requests提供了一個(gè)session的概念,在連續(xù)訪問網(wǎng)頁(yè),處理登錄跳轉(zhuǎn)時(shí)特別方便,不需要關(guān)注具體細(xì)節(jié)。使用方法示例如下:
import Requests oginUrl='http://www.xxxxxxx.com/login' s=requests.Session() #首先訪問登錄界面,作為游客,服務(wù)器會(huì)先分配一個(gè)cookie r=s.get(loginUrl,allow_redirects=True) datas={'name':'qiye','passwd':'qiye'} #向登錄鏈接發(fā)送post請(qǐng)求,驗(yàn)證成功,游客權(quán)限轉(zhuǎn)為會(huì)員權(quán)限 r=s.post(loginUrl, data=datas,allow_redirects=True) print r.text
上面的這段程序,其實(shí)是正式做Python開發(fā)中遇到的問題,如果沒有第一步訪問登錄的頁(yè)面,而是直接向登錄鏈接發(fā)送Post請(qǐng)求,系統(tǒng)會(huì)把你當(dāng)做非法用戶,因?yàn)樵L問登錄界面時(shí)會(huì)分配一個(gè)Cookie,需要將這個(gè)Cookie在發(fā)送Post請(qǐng)求時(shí)帶上,這種使用Session函數(shù)處理Cookie的方式之后會(huì)很常用。
3.6 重定向與歷史信息
處理重定向只是需要設(shè)置一下allow_redirects字段即可,例如:
r=requests.get('http://www.baidu.com',allow_redirects=True)
將allow_redirects設(shè)置為True,則是允許重定向;設(shè)置為False,則是禁止重定向。如果是允許重定向,可以通過r.history字段查看歷史信息,即訪問成功之前的所有請(qǐng)求跳轉(zhuǎn)信息。示例如下:
import requests r=requests.get('http://github.com') print r.url print r.status_code print r.history
打印結(jié)果如下:
https://github.com/ 200 (<Response [301]>,)
上面的示例代碼顯示的效果是訪問GitHub網(wǎng)址時(shí),會(huì)將所有的HTTP請(qǐng)求全部重定向?yàn)镠TTPS。
3.7 超時(shí)設(shè)置
超時(shí)選項(xiàng)是通過參數(shù)timeout來進(jìn)行設(shè)置的,示例如下:
requests.get('http://github.com', timeout=2)
3.8 代理設(shè)置
使用代理Proxy,你可以為任意請(qǐng)求方法通過設(shè)置proxies參數(shù)來配置單個(gè)請(qǐng)求:
import requests proxies={ "http": "http://0.10.1.10:3128", "https": "http://10.10.1.10:1080", } requests.get("http://example.org", proxies=proxies)
也可以通過環(huán)境變量HTTP_PROXY和HTTPS_PROXY?來配置代理,但是在爬蟲開發(fā)中不常用。你的代理需要使用HTTP Basic Auth,可以使用http://user:password@host/語(yǔ)法:
proxies={ "http": "http://user:pass@10.10.1.10:3128/", }
本文主要講解了網(wǎng)絡(luò)爬蟲的結(jié)構(gòu)和應(yīng)用,以及Python實(shí)現(xiàn)HTTP請(qǐng)求的幾種方法。希望大家對(duì)本文中的網(wǎng)絡(luò)爬蟲工作流程和Requests實(shí)現(xiàn)HTTP請(qǐng)求的方式重點(diǎn)吸收消化。
關(guān)于作者:范傳輝,資深網(wǎng)蟲,Python開發(fā)者,參與開發(fā)了多項(xiàng)網(wǎng)絡(luò)應(yīng)用,在實(shí)際開發(fā)中積累了豐富的實(shí)戰(zhàn)經(jīng)驗(yàn),并善于總結(jié),貢獻(xiàn)了多篇技術(shù)文章廣受好評(píng)。研究興趣是網(wǎng)絡(luò)安全、爬蟲技術(shù)、數(shù)據(jù)分析、驅(qū)動(dòng)開發(fā)等技術(shù)。
本文摘編自《Python爬蟲開發(fā)與項(xiàng)目實(shí)戰(zhàn)》,經(jīng)出版方授權(quán)發(fā)布。
延伸閱讀《Python爬蟲開發(fā)與項(xiàng)目實(shí)戰(zhàn)》
推薦語(yǔ):零基礎(chǔ)學(xué)習(xí)爬蟲技術(shù),從Python和Web前端基礎(chǔ)開始講起,由淺入深,包含大量案例,實(shí)用性強(qiáng)。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。