Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
西第十二賽段,菲利普-吉爾伯特(快步)憑借自己的天賦和經(jīng)驗(yàn),單飛奪冠。此次是他今年自巴黎-魯貝后的第一次勝利,也是他職業(yè)生涯中的第十個(gè)大環(huán)賽冠軍。亞歷克斯-阿蘭布魯(西班牙農(nóng)業(yè)銀行)獲第二,費(fèi)爾南多-巴塞洛(巴斯克-穆里亞斯)第三。
賽段
總成績(jī)方面仍舊沒有發(fā)生改變。
GC
盡管最初不斷有車手嘗試進(jìn)攻,但在前110公里的比賽中,高速的節(jié)奏讓他們始終保持在一起。終點(diǎn)62公里處,維利-斯米特(喀秋莎)發(fā)起決定性的進(jìn)攻,隨后帶出了一個(gè)18人的突圍集團(tuán)。包括吉爾伯特、蒂姆-德克勒爾(快步)、瓦萊里奧-孔蒂和馬可-馬爾卡托(阿聯(lián)酋航空)、朗西斯科-何塞-本托索(CCC)、何塞-華金-羅哈斯(移動(dòng)之星)、尼基亞斯-阿恩特(太陽(yáng)網(wǎng))、約翰-德根科爾布(崔克)、海因里希-豪斯勒(巴林美利達(dá))、曼努埃萊-博阿羅(阿斯塔納)等。
最后40公里,進(jìn)入Alto de Urruztamendi爬坡(全長(zhǎng)2.5公里,坡度9.2%),突圍集團(tuán)中德根科爾布、本托索和豪斯勒掉隊(duì)。格羅斯沙特納進(jìn)攻,格爾邁加入。兩人結(jié)伴,并領(lǐng)先40秒,但這一舉動(dòng)并沒堅(jiān)持多久。
來(lái)到Alto de Arraiz,隨著吉爾伯特和孔蒂一起來(lái)到前方,突圍集團(tuán)的人數(shù)不斷減少。不久,吉爾伯特進(jìn)攻,阿蘭布魯和巴塞洛迅速做出反應(yīng)。吉爾伯特率先下坡后,他們繼續(xù)追趕。
雖然兩人追擊集團(tuán)在最后兩公里與吉爾伯特時(shí)間差只有10秒,但吉爾伯特并沒有給他們機(jī)會(huì),仍加速前進(jìn)。當(dāng)他在終點(diǎn)500米處繞過最后一個(gè)彎道時(shí),環(huán)顧四周,知道自己勝券在握。阿蘭布魯和巴塞洛在吉爾伯特越過終點(diǎn)線的三秒后,也相繼撞線。
而主集團(tuán)進(jìn)入最后一段陡峭的爬坡,不斷有車手掉隊(duì)。紅衫羅格利奇一直頂在前方,旁邊是洛佩斯、金塔納和巴爾維德。三分鐘后,主集團(tuán)輕松地越過了終點(diǎn)線。
圖片來(lái)源:ASO
編輯:夏春花
原文鏈接:http://www.wildto.com/news/48775.html
先我們介紹一個(gè) Python 庫(kù),叫做 urllib,利用它我們可以實(shí)現(xiàn) HTTP 請(qǐng)求的發(fā)送,而不用去關(guān)心 HTTP 協(xié)議本身甚至更低層的實(shí)現(xiàn)。我們只需要指定請(qǐng)求的 URL、請(qǐng)求頭、請(qǐng)求體等信息即可實(shí)現(xiàn) HTTP 請(qǐng)求的發(fā)送,同時(shí) urllib 還可以把服務(wù)器返回的響應(yīng)轉(zhuǎn)化為 Python 對(duì)象,通過該對(duì)象我們便可以方便地獲取響應(yīng)的相關(guān)信息了,如響應(yīng)狀態(tài)碼、響應(yīng)頭、響應(yīng)體等等。
注意:在 Python 2 中,有 urllib 和 urllib2 兩個(gè)庫(kù)來(lái)實(shí)現(xiàn)請(qǐng)求的發(fā)送。而在 Python 3 中,已經(jīng)不存在 urllib2 這個(gè)庫(kù)了,統(tǒng)一為 urllib,其官方文檔鏈接為:https://docs.python.org/3/library/urllib.html。
首先,我們來(lái)了解一下 urllib 庫(kù)的使用方法,它是 Python 內(nèi)置的 HTTP 請(qǐng)求庫(kù),也就是說(shuō)不需要額外安裝即可使用。它包含如下 4 個(gè)模塊。
使用 urllib 的 request 模塊,我們可以方便地實(shí)現(xiàn)請(qǐng)求的發(fā)送并得到響應(yīng)。我們先來(lái)看下它的具體用法。
urllib.request 模塊提供了最基本的構(gòu)造 HTTP 請(qǐng)求的方法,利用它可以模擬瀏覽器的一個(gè)請(qǐng)求發(fā)起過程,同時(shí)它還帶有處理授權(quán)驗(yàn)證(Authentication)、重定向(Redirection)、瀏覽器 Cookie 以及其他內(nèi)容。
下面我們來(lái)看一下它的強(qiáng)大之處。這里以 Python 官網(wǎng)為例,我們來(lái)把這個(gè)網(wǎng)頁(yè)抓下來(lái):
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
運(yùn)行結(jié)果如圖所示。
圖 運(yùn)行結(jié)果
這里我們只用了兩行代碼,便完成了 Python 官網(wǎng)的抓取,輸出了網(wǎng)頁(yè)的源代碼。得到源代碼之后呢?我們想要的鏈接、圖片地址、文本信息不就都可以提取出來(lái)了嗎?
接下來(lái),看看它返回的到底是什么。利用 type 方法輸出響應(yīng)的類型:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(type(response))
輸出結(jié)果如下:
<class 'http.client.HTTPResponse'>
可以發(fā)現(xiàn),它是一個(gè) HTTPResposne 類型的對(duì)象,主要包含 read、readinto、getheader、getheaders、fileno 等方法,以及 msg、version、status、reason、debuglevel、closed 等屬性。
得到這個(gè)對(duì)象之后,我們把它賦值為 response 變量,然后就可以調(diào)用這些方法和屬性,得到返回結(jié)果的一系列信息了。
例如,調(diào)用 read 方法可以得到返回的網(wǎng)頁(yè)內(nèi)容,調(diào)用 status 屬性可以得到返回結(jié)果的狀態(tài)碼,如 200 代表請(qǐng)求成功,404 代表網(wǎng)頁(yè)未找到等。
下面再通過一個(gè)實(shí)例來(lái)看看:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))
運(yùn)行結(jié)果如下:
200
[('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'DENY'), ('Via', '1.1 vegur'), ('Via', '1.1 varnish'), ('Content-Length', '48775'), ('Accept-Ranges', 'bytes'), ('Date', 'Sun, 15 Mar 2020 13:29:01 GMT'), ('Via', '1.1 varnish'), ('Age', '708'), ('Connection', 'close'), ('X-Served-By', 'cache-bwi5120-BWI, cache-tyo19943-TYO'), ('X-Cache', 'HIT, HIT'), ('X-Cache-Hits', '2, 518'), ('X-Timer', 'S1584278942.717942,VS0,VE0'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
nginx
可見,前兩個(gè)輸出分別輸出了響應(yīng)的狀態(tài)碼和響應(yīng)的頭信息,最后一個(gè)輸出通過調(diào)用 getheader 方法并傳遞一個(gè)參數(shù) Server 獲取了響應(yīng)頭中的 Server 值,結(jié)果是 nginx,意思是服務(wù)器是用 Nginx 搭建的。
利用最基本的 urlopen 方法,可以完成最基本的簡(jiǎn)單網(wǎng)頁(yè)的 GET 請(qǐng)求抓取。
如果想給鏈接傳遞一些參數(shù),該怎么實(shí)現(xiàn)呢?首先看一下 urlopen 方法的 API:
urllib.request.urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None)
可以發(fā)現(xiàn),除了第一個(gè)參數(shù)可以傳遞 URL 之外,我們還可以傳遞其他內(nèi)容,比如 data(附加數(shù)據(jù))、timeout(超時(shí)時(shí)間)等。
下面我們?cè)敿?xì)說(shuō)明這幾個(gè)參數(shù)的用法。
data 參數(shù)是可選的。如果要添加該參數(shù),需要使用 bytes 方法將參數(shù)轉(zhuǎn)化為字節(jié)流編碼格式的內(nèi)容,即 bytes 類型。另外,如果傳遞了這個(gè)參數(shù),則它的請(qǐng)求方式就不再是 GET 方式,而是 POST 方式。
下面用實(shí)例來(lái)看一下:
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'name': 'germey'}), encoding='utf-8')
response = urllib.request.urlopen('https://httpbin.org/post', data=data)
print(response.read().decode('utf-8'))
這里我們傳遞了一個(gè)參數(shù) word,值是 hello。它需要被轉(zhuǎn)碼成 bytes(字節(jié)流)類型。其中轉(zhuǎn)字節(jié)流采用了 bytes 方法,該方法的第一個(gè)參數(shù)需要是 str(字符串)類型,需要用 urllib.parse 模塊里的 urlencode 方法來(lái)將參數(shù)字典轉(zhuǎn)化為字符串;第二個(gè)參數(shù)指定編碼格式,這里指定為 utf-8。
這里請(qǐng)求的站點(diǎn)是 httpbin.org,它可以提供 HTTP 請(qǐng)求測(cè)試。本次我們請(qǐng)求的 URL 為 https://httpbin.org/post,這個(gè)鏈接可以用來(lái)測(cè)試 POST 請(qǐng)求,它可以輸出 Request 的一些信息,其中就包含我們傳遞的 data 參數(shù)。
運(yùn)行結(jié)果如下:
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "germey"
},
"headers": {
"Accept-Encoding": "identity",
"Content-Length": "11",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Python-urllib/3.7",
"X-Amzn-Trace-Id": "Root=1-5ed27e43-9eee361fec88b7d3ce9be9db"
},
"json": null,
"origin": "17.220.233.154",
"url": "https://httpbin.org/post"
}
我們傳遞的參數(shù)出現(xiàn)在了 form 字段中,這表明是模擬了表單提交的方式,以 POST 方式傳輸數(shù)據(jù)。
timeout 參數(shù)用于設(shè)置超時(shí)時(shí)間,單位為秒,意思就是如果請(qǐng)求超出了設(shè)置的這個(gè)時(shí)間,還沒有得到響應(yīng),就會(huì)拋出異常。如果不指定該參數(shù),就會(huì)使用全局默認(rèn)時(shí)間。它支持 HTTP、HTTPS、FTP 請(qǐng)求。
下面用實(shí)例來(lái)看一下:
import urllib.request
response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
print(response.read())
運(yùn)行結(jié)果可能如下:
During handling of the above exception, another exception occurred:
Traceback (most recent call last): File "/var/py/python/urllibtest.py", line 4, in <module> response =
urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
...
urllib.error.URLError: <urlopen error _ssl.c:1059: The handshake operation timed out>
這里我們?cè)O(shè)置的超時(shí)時(shí)間是 1 秒。程序運(yùn)行 1 秒過后,服務(wù)器依然沒有響應(yīng),于是拋出了 URLError 異常。該異常屬于 urllib.error 模塊,錯(cuò)誤原因是超時(shí)。
因此,可以通過設(shè)置這個(gè)超時(shí)時(shí)間來(lái)控制一個(gè)網(wǎng)頁(yè)如果長(zhǎng)時(shí)間未響應(yīng),就跳過它的抓取。這可以利用 try…except 語(yǔ)句來(lái)實(shí)現(xiàn),相關(guān)代碼如下:
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
這里我們請(qǐng)求了 https://httpbin.org/get 這個(gè)測(cè)試鏈接,設(shè)置的超時(shí)時(shí)間是 0.1 秒,然后捕獲了 URLError 這個(gè)異常,然后判斷異常類型是 socket.timeout,意思就是超時(shí)異常。因此,得出它確實(shí)是因?yàn)槌瑫r(shí)而報(bào)錯(cuò),打印輸出了 TIME OUT。
運(yùn)行結(jié)果如下:
TIME OUT
按照常理來(lái)說(shuō),0.1 秒內(nèi)基本不可能得到服務(wù)器響應(yīng),因此輸出了 TIME OUT 的提示。
通過設(shè)置 timeout 這個(gè)參數(shù)來(lái)實(shí)現(xiàn)超時(shí)處理,有時(shí)還是很有用的。
除了 data 參數(shù)和 timeout 參數(shù)外,還有 context 參數(shù),它必須是 ssl.SSLContext 類型,用來(lái)指定 SSL 設(shè)置。
此外,cafile 和 capath 這兩個(gè)參數(shù)分別指定 CA 證書和它的路徑,這個(gè)在請(qǐng)求 HTTPS 鏈接時(shí)會(huì)有用。
cadefault 參數(shù)現(xiàn)在已經(jīng)棄用了,其默認(rèn)值為 False。
前面講解了 urlopen 方法的用法,通過這個(gè)最基本的方法,我們可以完成簡(jiǎn)單的請(qǐng)求和網(wǎng)頁(yè)抓取。若需更加詳細(xì)的信息,可以參見官方文檔:https://docs.python.org/3/library/urllib.request.html。
我們知道利用 urlopen 方法可以實(shí)現(xiàn)最基本請(qǐng)求的發(fā)起,但這幾個(gè)簡(jiǎn)單的參數(shù)并不足以構(gòu)建一個(gè)完整的請(qǐng)求。如果請(qǐng)求中需要加入 Headers 等信息,就可以利用更強(qiáng)大的 Request 類來(lái)構(gòu)建。
首先,我們用實(shí)例來(lái)感受一下 Request 類的用法:
import urllib.request
request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
可以發(fā)現(xiàn),我們依然用 urlopen 方法來(lái)發(fā)送這個(gè)請(qǐng)求,只不過這次該方法的參數(shù)不再是 URL,而是一個(gè) Request 類型的對(duì)象。通過構(gòu)造這個(gè)數(shù)據(jù)結(jié)構(gòu),一方面我們可以將請(qǐng)求獨(dú)立成一個(gè)對(duì)象,另一方面可更加豐富和靈活地配置參數(shù)。
下面我們看一下 Request 可以通過怎樣的參數(shù)來(lái)構(gòu)造,它的構(gòu)造方法如下:
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
其中,第一個(gè)參數(shù) url 用于請(qǐng)求 URL,這是必傳參數(shù),其他都是可選參數(shù)。
第二個(gè)參數(shù) data 如果要傳,必須傳 bytes(字節(jié)流)類型的。如果它是字典,可以先用 urllib.parse 模塊里的 urlencode() 編碼。
第三個(gè)參數(shù) headers 是一個(gè)字典,它就是請(qǐng)求頭。我們?cè)跇?gòu)造請(qǐng)求時(shí),既可以通過 headers 參數(shù)直接構(gòu)造,也可以通過調(diào)用請(qǐng)求實(shí)例的 add_header() 方法添加。
添加請(qǐng)求頭最常用的方法就是通過修改 User-Agent 來(lái)偽裝瀏覽器。默認(rèn)的 User-Agent 是 Python-urllib,我們可以通過修改它來(lái)偽裝瀏覽器。比如要偽裝火狐瀏覽器,你可以把它設(shè)置為:
Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11
第四個(gè)參數(shù) origin_req_host 指的是請(qǐng)求方的 host 名稱或者 IP 地址。
第五個(gè)參數(shù) unverifiable 表示這個(gè)請(qǐng)求是否是無(wú)法驗(yàn)證的,默認(rèn)是 False,意思就是說(shuō)用戶沒有足夠權(quán)限來(lái)選擇接收這個(gè)請(qǐng)求的結(jié)果。例如,我們請(qǐng)求一個(gè) HTML 文檔中的圖片,但是我們沒有自動(dòng)抓取圖像的權(quán)限,這時(shí) unverifiable 的值就是 True。
第六個(gè)參數(shù) method 是一個(gè)字符串,用來(lái)指示請(qǐng)求使用的方法,比如 GET、POST 和 PUT 等。
下面我們傳入多個(gè)參數(shù)來(lái)構(gòu)建請(qǐng)求:
from urllib import request, parse
url = 'https://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'Host': 'httpbin.org'
}
dict = {'name': 'germey'}
data = bytes(parse.urlencode(dict), encoding='utf-8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
這里我們通過 4 個(gè)參數(shù)構(gòu)造了一個(gè)請(qǐng)求,其中 url 即請(qǐng)求 URL,headers 中指定了 User-Agent 和 Host,參數(shù) data 用 urlencode 和 bytes 方法轉(zhuǎn)成字節(jié)流。另外,指定了請(qǐng)求方式為 POST。
運(yùn)行結(jié)果如下:
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "germey"
},
"headers": {
"Accept-Encoding": "identity",
"Content-Length": "11",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)",
"X-Amzn-Trace-Id": "Root=1-5ed27f77-884f503a2aa6760df7679f05"
},
"json": null,
"origin": "17.220.233.154",
"url": "https://httpbin.org/post"
}
觀察結(jié)果可以發(fā)現(xiàn),我們成功設(shè)置了 data、headers 和 method。
另外,headers 也可以用 add_header 方法來(lái)添加:
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
如此一來(lái),我們就可以更加方便地構(gòu)造請(qǐng)求,實(shí)現(xiàn)請(qǐng)求的發(fā)送啦。
在上面的過程中,我們雖然可以構(gòu)造請(qǐng)求,但是對(duì)于一些更高級(jí)的操作(比如 Cookies 處理、代理設(shè)置等),該怎么辦呢?
接下來(lái),就需要更強(qiáng)大的工具 Handler 登場(chǎng)了。簡(jiǎn)而言之,我們可以把它理解為各種處理器,有專門處理登錄驗(yàn)證的,有處理 Cookie 的,有處理代理設(shè)置的。利用它們,我們幾乎可以做到 HTTP 請(qǐng)求中所有的事情。
首先,介紹一下 urllib.request 模塊里的 BaseHandler 類,它是所有其他 Handler 的父類,它提供了最基本的方法,例如 default_open、protocol_request 等。
接下來(lái),就有各種 Handler 子類繼承這個(gè) BaseHandler 類,舉例如下。
另外,還有其他的 Handler 類,這里就不一一列舉了,詳情可以參考官方文檔: https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler。
關(guān)于怎么使用它們,現(xiàn)在先不用著急,后面會(huì)有實(shí)例演示。
另一個(gè)比較重要的類就是 OpenerDirector,我們可以稱為 Opener。我們之前用過 urlopen 這個(gè)方法,實(shí)際上它就是 urllib 為我們提供的一個(gè) Opener。
那么,為什么要引入 Opener 呢?因?yàn)樾枰獙?shí)現(xiàn)更高級(jí)的功能。之前使用的 Request 和 urlopen 相當(dāng)于類庫(kù)為你封裝好了極其常用的請(qǐng)求方法,利用它們可以完成基本的請(qǐng)求,但是現(xiàn)在不一樣了,我們需要實(shí)現(xiàn)更高級(jí)的功能,所以需要深入一層進(jìn)行配置,使用更底層的實(shí)例來(lái)完成操作,所以這里就用到了 Opener。
Opener 可以使用 open 方法,返回的類型和 urlopen 如出一轍。那么,它和 Handler 有什么關(guān)系呢?簡(jiǎn)而言之,就是利用 Handler 來(lái)構(gòu)建 Opener。
下面用幾個(gè)實(shí)例來(lái)看看它們的用法。
在訪問某些設(shè)置了身份認(rèn)證的網(wǎng)站時(shí),例如 https://ssr3.scrape.center/,我們可能會(huì)遇到這樣的認(rèn)證窗口,如圖 2- 所示:
圖 2- 認(rèn)證窗口
如果遇到了這種情況,那么這個(gè)網(wǎng)站就是啟用了基本身份認(rèn)證,英文叫作 HTTP Basic Access Authentication,它是一種用來(lái)允許網(wǎng)頁(yè)瀏覽器或其他客戶端程序在請(qǐng)求時(shí)提供用戶名和口令形式的身份憑證的一種登錄驗(yàn)證方式。
那么,如果要請(qǐng)求這樣的頁(yè)面,該怎么辦呢?借助 HTTPBasicAuthHandler 就可以完成,相關(guān)代碼如下:
from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError
username = 'admin'
password = 'admin'
url = 'https://ssr3.scrape.center/'
p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)
try:
result = opener.open(url)
html = result.read().decode('utf-8')
print(html)
except URLError as e:
print(e.reason)
這里首先實(shí)例化 HTTPBasicAuthHandler 對(duì)象,其參數(shù)是 HTTPPasswordMgrWithDefaultRealm 對(duì)象,它利用 add_password 方法添加進(jìn)去用戶名和密碼,這樣就建立了一個(gè)處理驗(yàn)證的 Handler。
接下來(lái),利用這個(gè) Handler 并使用 build_opener 方法構(gòu)建一個(gè) Opener,這個(gè) Opener 在發(fā)送請(qǐng)求時(shí)就相當(dāng)于已經(jīng)驗(yàn)證成功了。
接下來(lái),利用 Opener 的 open 方法打開鏈接,就可以完成驗(yàn)證了。這里獲取到的結(jié)果就是驗(yàn)證后的頁(yè)面源碼內(nèi)容。
在做爬蟲的時(shí)候,免不了要使用代理,如果要添加代理,可以這樣做:
from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener
proxy_handler = ProxyHandler({
'http': 'http://127.0.0.1:8080',
'https': 'https://127.0.0.1:8080'
})
opener = build_opener(proxy_handler)
try:
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
except URLError as e:
print(e.reason)
這里我們?cè)诒镜匦枰仁孪却罱ㄒ粋€(gè) HTTP 代理,運(yùn)行在 8080 端口上。
這里使用了 ProxyHandler,其參數(shù)是一個(gè)字典,鍵名是協(xié)議類型(比如 HTTP 或者 HTTPS 等),鍵值是代理鏈接,可以添加多個(gè)代理。
然后,利用這個(gè) Handler 及 build_opener 方法構(gòu)造一個(gè) Opener,之后發(fā)送請(qǐng)求即可。
Cookie 的處理就需要相關(guān)的 Handler 了。
我們先用實(shí)例來(lái)看看怎樣將網(wǎng)站的 Cookie 獲取下來(lái),相關(guān)代碼如下:
import http.cookiejar, urllib.request
cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
for item in cookie:
print(item.name + "=" + item.value)
首先,我們必須聲明一個(gè) CookieJar 對(duì)象。接下來(lái),就需要利用 HTTPCookieProcessor 來(lái)構(gòu)建一個(gè) Handler,最后利用 build_opener 方法構(gòu)建出 Opener,執(zhí)行 open 函數(shù)即可。
運(yùn)行結(jié)果如下:
BAIDUID=A09E6C4E38753531B9FB4C60CE9FDFCB:FG=1
BIDUPSID=A09E6C4E387535312F8AA46280C6C502
H_PS_PSSID=31358_1452_31325_21088_31110_31253_31605_31271_31463_30823
PSTM=1590854698
BDSVRTM=10
BD_HOME=1
可以看到,這里輸出了每個(gè) Cookie 條目的名稱和值。
不過既然能輸出,那可不可以輸出成文件格式呢?我們知道 Cookie 實(shí)際上也是以文本形式保存的。
答案當(dāng)然是肯定的,這里通過下面的實(shí)例來(lái)看看:
import urllib.request, http.cookiejar
filename = 'cookie.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)
這時(shí) CookieJar 就需要換成 MozillaCookieJar,它在生成文件時(shí)會(huì)用到,是 CookieJar 的子類,可以用來(lái)處理 Cookie 和文件相關(guān)的事件,比如讀取和保存 Cookie,可以將 Cookie 保存成 Mozilla 型瀏覽器的 Cookie 格式。
運(yùn)行之后,可以發(fā)現(xiàn)生成了一個(gè) cookie.txt 文件,其內(nèi)容如下:
# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This is a generated file! Do not edit.
.baidu.com TRUE / FALSE 1622390755 BAIDUID 0B4A68D74B0C0E53E5B82AFD9BF9178F:FG=1
.baidu.com TRUE / FALSE 3738338402 BIDUPSID 0B4A68D74B0C0E53471FA6329280FA58
.baidu.com TRUE / FALSE H_PS_PSSID 31262_1438_31325_21127_31110_31596_31673_31464_30823_26350
.baidu.com TRUE / FALSE 3738338402 PSTM 1590854754
www.baidu.com FALSE / FALSE BDSVRTM 0
www.baidu.com FALSE / FALSE BD_HOME 1
另外,LWPCookieJar 同樣可以讀取和保存 Cookie,但是保存的格式和 MozillaCookieJar 不一樣,它會(huì)保存成 libwww-perl(LWP)格式的 Cookie 文件。
要保存成 LWP 格式的 Cookie 文件,可以在聲明時(shí)就改為:
cookie = http.cookiejar.LWPCookieJar(filename)
此時(shí)生成的內(nèi)容如下:
#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="1F30EEDA35C7A94320275F991CA5B3A5:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2021-05-30 16:06:39Z"; comment=bd; version=0
Set-Cookie3: BIDUPSID=1F30EEDA35C7A9433C97CF6245CBC383; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2088-06-17 19:20:46Z"; version=0
Set-Cookie3: H_PS_PSSID=31626_1440_21124_31069_31254_31594_30841_31673_31464_31715_30823; path="/"; domain=".baidu.com"; path_spec; domain_dot; discard; version=0
Set-Cookie3: PSTM=1590854799; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2088-06-17 19:20:46Z"; version=0
Set-Cookie3: BDSVRTM=11; path="/"; domain="www.baidu.com"; path_spec; discard; version=0
Set-Cookie3: BD_HOME=1; path="/"; domain="www.baidu.com"; path_spec; discard; version=0
由此看來(lái),生成的格式還是有比較大差異的。
那么,生成了 Cookie 文件后,怎樣從文件中讀取并利用呢?
下面我們以 LWPCookieJar 格式為例來(lái)看一下:
import urllib.request, http.cookiejar
cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookie.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('https://www.baidu.com')
print(response.read().decode('utf-8'))
可以看到,這里調(diào)用 load 方法來(lái)讀取本地的 Cookie 文件,獲取到了 Cookie 的內(nèi)容。不過前提是我們首先生成了 LWPCookieJar 格式的 Cookie,并保存成文件,然后讀取 Cookie 之后使用同樣的方法構(gòu)建 Handler 和 Opener 即可完成操作。
運(yùn)行結(jié)果正常的話,會(huì)輸出百度網(wǎng)頁(yè)的源代碼。
通過上面的方法,我們可以實(shí)現(xiàn)絕大多數(shù)請(qǐng)求功能的設(shè)置了。
這便是 urllib 庫(kù)中 request 模塊的基本用法,如果想實(shí)現(xiàn)更多的功能,可以參考官方文檔的說(shuō)明:https://docs.python.org/3/library/urllib.request.html#basehandler-objects。
在前一節(jié)中,我們了解了請(qǐng)求的發(fā)送過程,但是在網(wǎng)絡(luò)不好的情況下,如果出現(xiàn)了異常,該怎么辦呢?這時(shí)如果不處理這些異常,程序很可能因報(bào)錯(cuò)而終止運(yùn)行,所以異常處理還是十分有必要的。
urllib 的 error 模塊定義了由 request 模塊產(chǎn)生的異常。如果出現(xiàn)了問題,request 模塊便會(huì)拋出 error 模塊中定義的異常。
URLError 類來(lái)自 urllib 庫(kù)的 error 模塊,它繼承自 OSError 類,是 error 異常模塊的基類,由 request 模塊產(chǎn)生的異常都可以通過捕獲這個(gè)類來(lái)處理。
它具有一個(gè)屬性 reason,即返回錯(cuò)誤的原因。
下面用一個(gè)實(shí)例來(lái)看一下:
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/404')
except error.URLError as e:
print(e.reason)
我們打開一個(gè)不存在的頁(yè)面,照理來(lái)說(shuō)應(yīng)該會(huì)報(bào)錯(cuò),但是這時(shí)我們捕獲了 URLError 這個(gè)異常,運(yùn)行結(jié)果如下:
Not Found
程序沒有直接報(bào)錯(cuò),而是輸出了如上內(nèi)容,這樣就可以避免程序異常終止,同時(shí)異常得到了有效處理。
它是 URLError 的子類,專門用來(lái)處理 HTTP 請(qǐng)求錯(cuò)誤,比如認(rèn)證請(qǐng)求失敗等。它有如下 3 個(gè)屬性。
下面我們用幾個(gè)實(shí)例來(lái)看看:
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:
print(e.reason, e.code, e.headers, sep='\n')
運(yùn)行結(jié)果如下:
Not Found
404
Server: nginx/1.10.3 (Ubuntu)
Date: Sat, 30 May 2020 16:08:42 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
Set-Cookie: PHPSESSID=kp1a1b0o3a0pcf688kt73gc780; path=/
Pragma: no-cache
Vary: Cookie
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Link: <https://cuiqingcai.com/wp-json/>; rel="https://api.w.org/"
依然是同樣的網(wǎng)址,這里捕獲了 HTTPError 異常,輸出了 reason、code 和 headers 屬性。
因?yàn)?URLError 是 HTTPError 的父類,所以可以先選擇捕獲子類的錯(cuò)誤,再去捕獲父類的錯(cuò)誤,所以上述代碼的更好寫法如下:
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/404')
except error.HTTPError as e:
print(e.reason, e.code, e.headers, sep='\n')
except error.URLError as e:
print(e.reason)
else:
print('Request Successfully')
這樣就可以做到先捕獲 HTTPError,獲取它的錯(cuò)誤原因、狀態(tài)碼、headers 等信息。如果不是 HTTPError 異常,就會(huì)捕獲 URLError 異常,輸出錯(cuò)誤原因。最后,用 else 來(lái)處理正常的邏輯。這是一個(gè)較好的異常處理寫法。
有時(shí)候,reason 屬性返回的不一定是字符串,也可能是一個(gè)對(duì)象。再看下面的實(shí)例:
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('https://www.baidu.com', timeout=0.01)
except urllib.error.URLError as e:
print(type(e.reason))
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
這里我們直接設(shè)置超時(shí)時(shí)間來(lái)強(qiáng)制拋出 timeout 異常。
運(yùn)行結(jié)果如下:
<class'socket.timeout'>
TIME OUT
可以發(fā)現(xiàn),reason 屬性的結(jié)果是 socket.timeout 類。所以,這里我們可以用 isinstance 方法來(lái)判斷它的類型,作出更詳細(xì)的異常判斷。
本節(jié)中,我們講述了 error 模塊的相關(guān)用法,通過合理地捕獲異常可以做出更準(zhǔn)確的異常判斷,使程序更加穩(wěn)健。
前面說(shuō)過,urllib 庫(kù)里還提供了 parse 模塊,它定義了處理 URL 的標(biāo)準(zhǔn)接口,例如實(shí)現(xiàn) URL 各部分的抽取、合并以及鏈接轉(zhuǎn)換。它支持如下協(xié)議的 URL 處理:file、ftp、gopher、hdl、http、https、imap、mailto、mms、news、nntp、prospero、rsync、rtsp、rtspu、sftp、sip、sips、snews、svn、svn+ssh、telnet 和 wais。本節(jié)中,我們介紹一下該模塊中常用的方法來(lái)看一下它的便捷之處。
該方法可以實(shí)現(xiàn) URL 的識(shí)別和分段,這里先用一個(gè)實(shí)例來(lái)看一下:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)
這里我們利用 urlparse 方法進(jìn)行了一個(gè) URL 的解析。首先,輸出了解析結(jié)果的類型,然后將結(jié)果也輸出出來(lái)。
運(yùn)行結(jié)果如下:
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可以看到,返回結(jié)果是一個(gè) ParseResult 類型的對(duì)象,它包含 6 個(gè)部分,分別是 scheme、netloc、path、params、query 和 fragment。
觀察一下該實(shí)例的 URL:
https://www.baidu.com/index.html;user?id=5#comment
可以發(fā)現(xiàn),urlparse 方法將其拆分成了 6 個(gè)部分。大體觀察可以發(fā)現(xiàn),解析時(shí)有特定的分隔符。比如,:// 前面的就是 scheme,代表協(xié)議;第一個(gè) / 符號(hào)前面便是 netloc,即域名,后面是 path,即訪問路徑;分號(hào);后面是 params,代表參數(shù);問號(hào) ? 后面是查詢條件 query,一般用作 GET 類型的 URL;井號(hào) # 后面是錨點(diǎn),用于直接定位頁(yè)面內(nèi)部的下拉位置。
所以,可以得出一個(gè)標(biāo)準(zhǔn)的鏈接格式,具體如下:
scheme://netloc/path;params?query#fragment
一個(gè)標(biāo)準(zhǔn)的 URL 都會(huì)符合這個(gè)規(guī)則,利用 urlparse 方法可以將它拆分開來(lái)。
除了這種最基本的解析方式外,urlparse 方法還有其他配置嗎?接下來(lái),看一下它的 API 用法:
urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
可以看到,它有 3 個(gè)參數(shù)。
from urllib.parse import urlparse
result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='https')
print(result)
運(yùn)行結(jié)果如下:
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
可以發(fā)現(xiàn),我們提供的 URL 沒有包含最前面的 scheme 信息,但是通過默認(rèn)的 scheme 參數(shù),返回的結(jié)果是 https。
假設(shè)我們帶上了 scheme:
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment', scheme='https')
則結(jié)果如下:
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可見,scheme 參數(shù)只有在 URL 中不包含 scheme 信息時(shí)才生效。如果 URL 中有 scheme 信息,就會(huì)返回解析出的 scheme。
下面我們用實(shí)例來(lái)看一下:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)
運(yùn)行結(jié)果如下:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')
假設(shè) URL 中不包含 params 和 query,我們?cè)偻ㄟ^實(shí)例看一下:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)
運(yùn)行結(jié)果如下:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')
可以發(fā)現(xiàn),當(dāng) URL 中不包含 params 和 query 時(shí),fragment 便會(huì)被解析為 path 的一部分。
返回結(jié)果 ParseResult 實(shí)際上是一個(gè)元組,我們既可以用索引順序來(lái)獲取,也可以用屬性名獲取。示例如下:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result.scheme, result[0], result.netloc, result[1], sep='\n')
這里我們分別用索引和屬性名獲取了 scheme 和 netloc,其運(yùn)行結(jié)果如下:
https
https
www.baidu.com
www.baidu.com
可以發(fā)現(xiàn),二者的結(jié)果是一致的,兩種方法都可以成功獲取。
有了 urlparse 方法,相應(yīng)地就有了它的對(duì)立方法 urlunparse。它接收的參數(shù)是一個(gè)可迭代對(duì)象,但是它的長(zhǎng)度必須是 6,否則會(huì)拋出參數(shù)數(shù)量不足或者過多的問題。先用一個(gè)實(shí)例看一下:
from urllib.parse import urlunparse
data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
這里參數(shù) data 用了列表類型。當(dāng)然,你也可以用其他類型,比如元組或者特定的數(shù)據(jù)結(jié)構(gòu)。
運(yùn)行結(jié)果如下:
https://www.baidu.com/index.html;user?a=6#comment
這樣我們就成功實(shí)現(xiàn)了 URL 的構(gòu)造。
這個(gè)方法和 urlparse 方法非常相似,只不過它不再單獨(dú)解析 params 這一部分,只返回 5 個(gè)結(jié)果。上面例子中的 params 會(huì)合并到 path 中。示例如下:
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result)
運(yùn)行結(jié)果如下:
SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
可以發(fā)現(xiàn),返回結(jié)果是 SplitResult,它其實(shí)也是一個(gè)元組類型,既可以用屬性獲取值,也可以用索引來(lái)獲取。示例如下:
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result.scheme, result[0])
運(yùn)行結(jié)果如下:
https https
與 urlunparse 方法類似,它也是將鏈接各個(gè)部分組合成完整鏈接的方法,傳入的參數(shù)也是一個(gè)可迭代對(duì)象,例如列表、元組等,唯一的區(qū)別是長(zhǎng)度必須為 5。示例如下:
from urllib.parse import urlunsplit
data = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))
運(yùn)行結(jié)果如下:
https://www.baidu.com/index.html?a=6#comment
有了 urlunparse 和 urlunsplit 方法,我們可以完成鏈接的合并,不過前提是必須要有特定長(zhǎng)度的對(duì)象,鏈接的每一部分都要清晰分開。
此外,生成鏈接還有另一個(gè)方法,那就是 urljoin 方法。我們可以提供一個(gè) base_url(基礎(chǔ)鏈接)作為第一個(gè)參數(shù),將新的鏈接作為第二個(gè)參數(shù),該方法會(huì)分析 base_url 的 scheme、netloc 和 path 這 3 個(gè)內(nèi)容并對(duì)新鏈接缺失的部分進(jìn)行補(bǔ)充,最后返回結(jié)果。
下面通過幾個(gè)實(shí)例看一下:
from urllib.parse import urljoin
print(urljoin('https://www.baidu.com', 'FAQ.html'))
print(urljoin('https://www.baidu.com', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html'))
print(urljoin('https://www.baidu.com/about.html', 'https://cuiqingcai.com/FAQ.html?question=2'))
print(urljoin('https://www.baidu.com?wd=abc', 'https://cuiqingcai.com/index.php'))
print(urljoin('https://www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com', '?category=2#comment'))
print(urljoin('www.baidu.com#comment', '?category=2'))
運(yùn)行結(jié)果如下:
https://www.baidu.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html
https://cuiqingcai.com/FAQ.html?question=2
https://cuiqingcai.com/index.php
https://www.baidu.com?category=2#comment
www.baidu.com?category=2#comment
www.baidu.com?category=2
可以發(fā)現(xiàn),base_url 提供了三項(xiàng)內(nèi)容 scheme、netloc 和 path。如果這 3 項(xiàng)在新的鏈接里不存在,就予以補(bǔ)充;如果新的鏈接存在,就使用新的鏈接的部分。而 base_url 中的 params、query 和 fragment 是不起作用的。
通過 urljoin 方法,我們可以輕松實(shí)現(xiàn)鏈接的解析、拼合與生成。
這里我們?cè)俳榻B一個(gè)常用的方法 —— urlencode,它在構(gòu)造 GET 請(qǐng)求參數(shù)的時(shí)候非常有用,示例如下:
from urllib.parse import urlencode
params = {
'name': 'germey',
'age': 25
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
這里首先聲明一個(gè)字典來(lái)將參數(shù)表示出來(lái),然后調(diào)用 urlencode 方法將其序列化為 GET 請(qǐng)求參數(shù)。
運(yùn)行結(jié)果如下:
https://www.baidu.com?name=germey&age=25
可以看到,參數(shù)成功地由字典類型轉(zhuǎn)化為 GET 請(qǐng)求參數(shù)了。
這個(gè)方法非常常用。有時(shí)為了更加方便地構(gòu)造參數(shù),我們會(huì)事先用字典來(lái)表示。要轉(zhuǎn)化為 URL 的參數(shù)時(shí),只需要調(diào)用該方法即可。
有了序列化,必然就有反序列化。如果我們有一串 GET 請(qǐng)求參數(shù),利用 parse_qs 方法,就可以將它轉(zhuǎn)回字典,示例如下:
from urllib.parse import parse_qs
query = 'name=germey&age=25'
print(parse_qs(query))
運(yùn)行結(jié)果如下:
{'name': ['germey'], 'age': ['25']}
可以看到,這樣就成功轉(zhuǎn)回為字典類型了。
另外,還有一個(gè) parse_qsl 方法,它用于將參數(shù)轉(zhuǎn)化為元組組成的列表,示例如下:
from urllib.parse import parse_qsl
query = 'name=germey&age=25'
print(parse_qsl(query))
運(yùn)行結(jié)果如下:
[('name', 'germey'), ('age', '25')]
可以看到,運(yùn)行結(jié)果是一個(gè)列表,而列表中的每一個(gè)元素都是一個(gè)元組,元組的第一個(gè)內(nèi)容是參數(shù)名,第二個(gè)內(nèi)容是參數(shù)值。
該方法可以將內(nèi)容轉(zhuǎn)化為 URL 編碼的格式。URL 中帶有中文參數(shù)時(shí),有時(shí)可能會(huì)導(dǎo)致亂碼的問題,此時(shí)可以用這個(gè)方法可以將中文字符轉(zhuǎn)化為 URL 編碼,示例如下:
from urllib.parse import quote
keyword = '壁紙'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
這里我們聲明了一個(gè)中文的搜索文字,然后用 quote 方法對(duì)其進(jìn)行 URL 編碼,最后得到的結(jié)果如下:
https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8
有了 quote 方法,當(dāng)然還有 unquote 方法,它可以進(jìn)行 URL 解碼,示例如下:
from urllib.parse import unquote
url = 'https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))
這是上面得到的 URL 編碼后的結(jié)果,這里利用 unquote 方法還原,結(jié)果如下:
https://www.baidu.com/s?wd=壁紙
可以看到,利用 unquote 方法可以方便地實(shí)現(xiàn)解碼。
本節(jié)中,我們介紹了 parse 模塊的一些常用 URL 處理方法。有了這些方法,我們可以方便地實(shí)現(xiàn) URL 的解析和構(gòu)造,建議熟練掌握。
利用 urllib 的 robotparser 模塊,我們可以實(shí)現(xiàn)網(wǎng)站 Robots 協(xié)議的分析。本節(jié)中,我們來(lái)簡(jiǎn)單了解一下該模塊的用法。
Robots 協(xié)議也稱作爬蟲協(xié)議、機(jī)器人協(xié)議,它的全名叫作網(wǎng)絡(luò)爬蟲排除標(biāo)準(zhǔn)(Robots Exclusion Protocol),用來(lái)告訴爬蟲和搜索引擎哪些頁(yè)面可以抓取,哪些不可以抓取。它通常是一個(gè)叫作 robots.txt 的文本文件,一般放在網(wǎng)站的根目錄下。
當(dāng)搜索爬蟲訪問一個(gè)站點(diǎn)時(shí),它首先會(huì)檢查這個(gè)站點(diǎn)根目錄下是否存在 robots.txt 文件,如果存在,搜索爬蟲會(huì)根據(jù)其中定義的爬取范圍來(lái)爬取。如果沒有找到這個(gè)文件,搜索爬蟲便會(huì)訪問所有可直接訪問的頁(yè)面。
下面我們看一個(gè) robots.txt 的樣例:
User-agent: *
Disallow: /
Allow: /public/
這實(shí)現(xiàn)了對(duì)所有搜索爬蟲只允許爬取 public 目錄的功能,將上述內(nèi)容保存成 robots.txt 文件,放在網(wǎng)站的根目錄下,和網(wǎng)站的入口文件(比如 index.php、index.html 和 index.jsp 等)放在一起。
上面的 User-agent 描述了搜索爬蟲的名稱,這里將其設(shè)置為 * 則代表該協(xié)議對(duì)任何爬取爬蟲有效。比如,我們可以設(shè)置:
User-agent: Baiduspider
這就代表我們?cè)O(shè)置的規(guī)則對(duì)百度爬蟲是有效的。如果有多條 User-agent 記錄,就有多個(gè)爬蟲會(huì)受到爬取限制,但至少需要指定一條。
Disallow 指定了不允許抓取的目錄,比如上例子中設(shè)置為 / 則代表不允許抓取所有頁(yè)面。
Allow 一般和 Disallow 一起使用,一般不會(huì)單獨(dú)使用,用來(lái)排除某些限制。上例中我們?cè)O(shè)置為 /public/,則表示所有頁(yè)面不允許抓取,但可以抓取 public 目錄。
下面我們?cè)賮?lái)看幾個(gè)例子。禁止所有爬蟲訪問任何目錄的代碼如下:
User-agent: *
Disallow: /
允許所有爬蟲訪問任何目錄的代碼如下:
User-agent: *
Disallow:
另外,直接把 robots.txt 文件留空也是可以的。
禁止所有爬蟲訪問網(wǎng)站某些目錄的代碼如下:
User-agent: *
Disallow: /private/
Disallow: /tmp/
只允許某一個(gè)爬蟲訪問的代碼如下:
User-agent: WebCrawler
Disallow:
User-agent: *
Disallow: /
這些是 robots.txt 的一些常見寫法。
大家可能會(huì)疑惑,爬蟲名是從哪兒來(lái)的?為什么就叫這個(gè)名?其實(shí)它是有固定名字的了,比如百度的就叫作 BaiduSpider。表 2- 列出了一些常見搜索爬蟲的名稱及對(duì)應(yīng)的網(wǎng)站。
表 一些常見搜索爬蟲的名稱及其對(duì)應(yīng)的網(wǎng)站
爬蟲名稱 | 名稱 | 網(wǎng)站 |
BaiduSpider | 百度 | www.baidu.com |
Googlebot | 谷歌 | www.google.com |
360Spider | 360 搜索 | www.so.com |
YodaoBot | 有道 | www.youdao.com |
ia_archiver | Alexa | www.alexa.cn |
Scooter | altavista | www.altavista.com |
Bingbot | 必應(yīng) | www.bing.com |
了解 Robots 協(xié)議之后,我們就可以使用 robotparser 模塊來(lái)解析 robots.txt 了。該模塊提供了一個(gè)類 RobotFileParser,它可以根據(jù)某網(wǎng)站的 robots.txt 文件來(lái)判斷一個(gè)爬蟲是否有權(quán)限來(lái)爬取這個(gè)網(wǎng)頁(yè)。
該類用起來(lái)非常簡(jiǎn)單,只需要在構(gòu)造方法里傳入 robots.txt 的鏈接即可。首先看一下它的聲明:
urllib.robotparser.RobotFileParser(url='')
當(dāng)然,也可以在聲明時(shí)不傳入,默認(rèn)為空,最后再使用 set_url 方法設(shè)置一下即可。
下面列出了這個(gè)類常用的幾個(gè)方法。
下面我們用實(shí)例來(lái)看一下:
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('https://www.baidu.com/robots.txt')
rp.read()
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))
這里以百度為例,首先創(chuàng)建 RobotFileParser 對(duì)象,然后通過 set_url 方法設(shè)置了 robots.txt 的鏈接。當(dāng)然,不用這個(gè)方法的話,可以在聲明時(shí)直接用如下方法設(shè)置:
rp = RobotFileParser('https://www.baidu.com/robots.txt')
接著利用 can_fetch 方法判斷網(wǎng)頁(yè)是否可以被抓取。
運(yùn)行結(jié)果如下:
True
True
False
這里同樣可以使用 parse 方法執(zhí)行讀取和分析,示例如下:
可以看到這里我們利用 Baiduspider 可以抓取百度等首頁(yè)以及 homepage 頁(yè)面,但是 Googlebot 就不能抓取 homepage 頁(yè)面。
打開百度的 robots.txt 文件看下,可以看到如下的信息:
User-agent: Baiduspider
Disallow: /baidu
Disallow: /s?
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bh
User-agent: Googlebot
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bh
由此我們可以看到,Baiduspider 沒有限制 homepage 頁(yè)面的抓取,而 Googlebot 則限制了 homepage 頁(yè)面的抓取。
這里同樣可以使用 parse 方法執(zhí)行讀取和分析,示例如下:
from urllib.request import urlopen
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.parse(urlopen('https://www.baidu.com/robots.txt').read().decode('utf-8').split('\n'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com'))
print(rp.can_fetch('Baiduspider', 'https://www.baidu.com/homepage/'))
print(rp.can_fetch('Googlebot', 'https://www.baidu.com/homepage/'))
運(yùn)行結(jié)果一樣:
True
True
False
本節(jié)介紹了 robotparser 模塊的基本用法和實(shí)例,利用它,我們可以方便地判斷哪些頁(yè)面可以抓取,哪些頁(yè)面不可以抓取。
本節(jié)內(nèi)容比較多,我們介紹了 urllib 的 request、error、parse、robotparser 模塊的基本用法。這些是一些基礎(chǔ)模塊,其中有一些模塊的實(shí)用性還是很強(qiáng)的,比如我們可以利用 parse 模塊來(lái)進(jìn)行 URL 的各種處理。
本節(jié)代碼:https://github.com/Python3WebSpider/UrllibTest。
西第十二賽段,菲利普-吉爾伯特(快步)憑借自己的天賦和經(jīng)驗(yàn),單飛奪冠。此次是他今年自巴黎-魯貝后的第一次勝利,也是他職業(yè)生涯中的第十個(gè)大環(huán)賽冠軍。亞歷克斯-阿蘭布魯(西班牙農(nóng)業(yè)銀行)獲第二,費(fèi)爾南多-巴塞洛(巴斯克-穆里亞斯)第三。
賽段
總成績(jī)方面仍舊沒有發(fā)生改變。
GC
盡管最初不斷有車手嘗試進(jìn)攻,但在前110公里的比賽中,高速的節(jié)奏讓他們始終保持在一起。終點(diǎn)62公里處,維利-斯米特(喀秋莎)發(fā)起決定性的進(jìn)攻,隨后帶出了一個(gè)18人的突圍集團(tuán)。包括吉爾伯特、蒂姆-德克勒爾(快步)、瓦萊里奧-孔蒂和馬可-馬爾卡托(阿聯(lián)酋航空)、朗西斯科-何塞-本托索(CCC)、何塞-華金-羅哈斯(移動(dòng)之星)、尼基亞斯-阿恩特(太陽(yáng)網(wǎng))、約翰-德根科爾布(崔克)、海因里希-豪斯勒(巴林美利達(dá))、曼努埃萊-博阿羅(阿斯塔納)等。
最后40公里,進(jìn)入Alto de Urruztamendi爬坡(全長(zhǎng)2.5公里,坡度9.2%),突圍集團(tuán)中德根科爾布、本托索和豪斯勒掉隊(duì)。格羅斯沙特納進(jìn)攻,格爾邁加入。兩人結(jié)伴,并領(lǐng)先40秒,但這一舉動(dòng)并沒堅(jiān)持多久。
來(lái)到Alto de Arraiz,隨著吉爾伯特和孔蒂一起來(lái)到前方,突圍集團(tuán)的人數(shù)不斷減少。不久,吉爾伯特進(jìn)攻,阿蘭布魯和巴塞洛迅速做出反應(yīng)。吉爾伯特率先下坡后,他們繼續(xù)追趕。
雖然兩人追擊集團(tuán)在最后兩公里與吉爾伯特時(shí)間差只有10秒,但吉爾伯特并沒有給他們機(jī)會(huì),仍加速前進(jìn)。當(dāng)他在終點(diǎn)500米處繞過最后一個(gè)彎道時(shí),環(huán)顧四周,知道自己勝券在握。阿蘭布魯和巴塞洛在吉爾伯特越過終點(diǎn)線的三秒后,也相繼撞線。
而主集團(tuán)進(jìn)入最后一段陡峭的爬坡,不斷有車手掉隊(duì)。紅衫羅格利奇一直頂在前方,旁邊是洛佩斯、金塔納和巴爾維德。三分鐘后,主集團(tuán)輕松地越過了終點(diǎn)線。
圖片來(lái)源:ASO
編輯:夏春花
原文鏈接:http://www.wildto.com/news/48775.html
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。