西第十二賽段,菲利普-吉爾伯特(快步)憑借自己的天賦和經(jīng)驗,單飛奪冠。此次是他今年自巴黎-魯貝后的第一次勝利,也是他職業(yè)生涯中的第十個大環(huán)賽冠軍。亞歷克斯-阿蘭布魯(西班牙農(nóng)業(yè)銀行)獲第二,費爾南多-巴塞洛(巴斯克-穆里亞斯)第三。
賽段
總成績方面仍舊沒有發(fā)生改變。
GC
盡管最初不斷有車手嘗試進攻,但在前110公里的比賽中,高速的節(jié)奏讓他們始終保持在一起。終點62公里處,維利-斯米特(喀秋莎)發(fā)起決定性的進攻,隨后帶出了一個18人的突圍集團。包括吉爾伯特、蒂姆-德克勒爾(快步)、瓦萊里奧-孔蒂和馬可-馬爾卡托(阿聯(lián)酋航空)、朗西斯科-何塞-本托索(CCC)、何塞-華金-羅哈斯(移動之星)、尼基亞斯-阿恩特(太陽網(wǎng))、約翰-德根科爾布(崔克)、海因里希-豪斯勒(巴林美利達)、曼努埃萊-博阿羅(阿斯塔納)等。
最后40公里,進入Alto de Urruztamendi爬坡(全長2.5公里,坡度9.2%),突圍集團中德根科爾布、本托索和豪斯勒掉隊。格羅斯沙特納進攻,格爾邁加入。兩人結(jié)伴,并領(lǐng)先40秒,但這一舉動并沒堅持多久。
來到Alto de Arraiz,隨著吉爾伯特和孔蒂一起來到前方,突圍集團的人數(shù)不斷減少。不久,吉爾伯特進攻,阿蘭布魯和巴塞洛迅速做出反應。吉爾伯特率先下坡后,他們繼續(xù)追趕。
雖然兩人追擊集團在最后兩公里與吉爾伯特時間差只有10秒,但吉爾伯特并沒有給他們機會,仍加速前進。當他在終點500米處繞過最后一個彎道時,環(huán)顧四周,知道自己勝券在握。阿蘭布魯和巴塞洛在吉爾伯特越過終點線的三秒后,也相繼撞線。
而主集團進入最后一段陡峭的爬坡,不斷有車手掉隊。紅衫羅格利奇一直頂在前方,旁邊是洛佩斯、金塔納和巴爾維德。三分鐘后,主集團輕松地越過了終點線。
圖片來源:ASO
編輯:夏春花
原文鏈接:http://www.wildto.com/news/48775.html
先我們介紹一個 Python 庫,叫做 urllib,利用它我們可以實現(xiàn) HTTP 請求的發(fā)送,而不用去關(guān)心 HTTP 協(xié)議本身甚至更低層的實現(xiàn)。我們只需要指定請求的 URL、請求頭、請求體等信息即可實現(xiàn) HTTP 請求的發(fā)送,同時 urllib 還可以把服務器返回的響應轉(zhuǎn)化為 Python 對象,通過該對象我們便可以方便地獲取響應的相關(guān)信息了,如響應狀態(tài)碼、響應頭、響應體等等。
注意:在 Python 2 中,有 urllib 和 urllib2 兩個庫來實現(xiàn)請求的發(fā)送。而在 Python 3 中,已經(jīng)不存在 urllib2 這個庫了,統(tǒng)一為 urllib,其官方文檔鏈接為:https://docs.python.org/3/library/urllib.html。
首先,我們來了解一下 urllib 庫的使用方法,它是 Python 內(nèi)置的 HTTP 請求庫,也就是說不需要額外安裝即可使用。它包含如下 4 個模塊。
使用 urllib 的 request 模塊,我們可以方便地實現(xiàn)請求的發(fā)送并得到響應。我們先來看下它的具體用法。
urllib.request 模塊提供了最基本的構(gòu)造 HTTP 請求的方法,利用它可以模擬瀏覽器的一個請求發(fā)起過程,同時它還帶有處理授權(quán)驗證(Authentication)、重定向(Redirection)、瀏覽器 Cookie 以及其他內(nèi)容。
下面我們來看一下它的強大之處。這里以 Python 官網(wǎng)為例,我們來把這個網(wǎng)頁抓下來:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
運行結(jié)果如圖所示。
圖 運行結(jié)果
這里我們只用了兩行代碼,便完成了 Python 官網(wǎng)的抓取,輸出了網(wǎng)頁的源代碼。得到源代碼之后呢?我們想要的鏈接、圖片地址、文本信息不就都可以提取出來了嗎?
接下來,看看它返回的到底是什么。利用 type 方法輸出響應的類型:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(type(response))
輸出結(jié)果如下:
<class 'http.client.HTTPResponse'>
可以發(fā)現(xiàn),它是一個 HTTPResposne 類型的對象,主要包含 read、readinto、getheader、getheaders、fileno 等方法,以及 msg、version、status、reason、debuglevel、closed 等屬性。
得到這個對象之后,我們把它賦值為 response 變量,然后就可以調(diào)用這些方法和屬性,得到返回結(jié)果的一系列信息了。
例如,調(diào)用 read 方法可以得到返回的網(wǎng)頁內(nèi)容,調(diào)用 status 屬性可以得到返回結(jié)果的狀態(tài)碼,如 200 代表請求成功,404 代表網(wǎng)頁未找到等。
下面再通過一個實例來看看:
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))
運行結(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
可見,前兩個輸出分別輸出了響應的狀態(tài)碼和響應的頭信息,最后一個輸出通過調(diào)用 getheader 方法并傳遞一個參數(shù) Server 獲取了響應頭中的 Server 值,結(jié)果是 nginx,意思是服務器是用 Nginx 搭建的。
利用最基本的 urlopen 方法,可以完成最基本的簡單網(wǎng)頁的 GET 請求抓取。
如果想給鏈接傳遞一些參數(shù),該怎么實現(xiàn)呢?首先看一下 urlopen 方法的 API:
urllib.request.urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None)
可以發(fā)現(xiàn),除了第一個參數(shù)可以傳遞 URL 之外,我們還可以傳遞其他內(nèi)容,比如 data(附加數(shù)據(jù))、timeout(超時時間)等。
下面我們詳細說明這幾個參數(shù)的用法。
data 參數(shù)是可選的。如果要添加該參數(shù),需要使用 bytes 方法將參數(shù)轉(zhuǎn)化為字節(jié)流編碼格式的內(nèi)容,即 bytes 類型。另外,如果傳遞了這個參數(shù),則它的請求方式就不再是 GET 方式,而是 POST 方式。
下面用實例來看一下:
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'))
這里我們傳遞了一個參數(shù) word,值是 hello。它需要被轉(zhuǎn)碼成 bytes(字節(jié)流)類型。其中轉(zhuǎn)字節(jié)流采用了 bytes 方法,該方法的第一個參數(shù)需要是 str(字符串)類型,需要用 urllib.parse 模塊里的 urlencode 方法來將參數(shù)字典轉(zhuǎn)化為字符串;第二個參數(shù)指定編碼格式,這里指定為 utf-8。
這里請求的站點是 httpbin.org,它可以提供 HTTP 請求測試。本次我們請求的 URL 為 https://httpbin.org/post,這個鏈接可以用來測試 POST 請求,它可以輸出 Request 的一些信息,其中就包含我們傳遞的 data 參數(shù)。
運行結(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ù),就會使用全局默認時間。它支持 HTTP、HTTPS、FTP 請求。
下面用實例來看一下:
import urllib.request
response = urllib.request.urlopen('https://httpbin.org/get', timeout=0.1)
print(response.read())
運行結(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>
這里我們設(shè)置的超時時間是 1 秒。程序運行 1 秒過后,服務器依然沒有響應,于是拋出了 URLError 異常。該異常屬于 urllib.error 模塊,錯誤原因是超時。
因此,可以通過設(shè)置這個超時時間來控制一個網(wǎng)頁如果長時間未響應,就跳過它的抓取。這可以利用 try…except 語句來實現(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')
這里我們請求了 https://httpbin.org/get 這個測試鏈接,設(shè)置的超時時間是 0.1 秒,然后捕獲了 URLError 這個異常,然后判斷異常類型是 socket.timeout,意思就是超時異常。因此,得出它確實是因為超時而報錯,打印輸出了 TIME OUT。
運行結(jié)果如下:
TIME OUT
按照常理來說,0.1 秒內(nèi)基本不可能得到服務器響應,因此輸出了 TIME OUT 的提示。
通過設(shè)置 timeout 這個參數(shù)來實現(xiàn)超時處理,有時還是很有用的。
除了 data 參數(shù)和 timeout 參數(shù)外,還有 context 參數(shù),它必須是 ssl.SSLContext 類型,用來指定 SSL 設(shè)置。
此外,cafile 和 capath 這兩個參數(shù)分別指定 CA 證書和它的路徑,這個在請求 HTTPS 鏈接時會有用。
cadefault 參數(shù)現(xiàn)在已經(jīng)棄用了,其默認值為 False。
前面講解了 urlopen 方法的用法,通過這個最基本的方法,我們可以完成簡單的請求和網(wǎng)頁抓取。若需更加詳細的信息,可以參見官方文檔:https://docs.python.org/3/library/urllib.request.html。
我們知道利用 urlopen 方法可以實現(xiàn)最基本請求的發(fā)起,但這幾個簡單的參數(shù)并不足以構(gòu)建一個完整的請求。如果請求中需要加入 Headers 等信息,就可以利用更強大的 Request 類來構(gòu)建。
首先,我們用實例來感受一下 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 方法來發(fā)送這個請求,只不過這次該方法的參數(shù)不再是 URL,而是一個 Request 類型的對象。通過構(gòu)造這個數(shù)據(jù)結(jié)構(gòu),一方面我們可以將請求獨立成一個對象,另一方面可更加豐富和靈活地配置參數(shù)。
下面我們看一下 Request 可以通過怎樣的參數(shù)來構(gòu)造,它的構(gòu)造方法如下:
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
其中,第一個參數(shù) url 用于請求 URL,這是必傳參數(shù),其他都是可選參數(shù)。
第二個參數(shù) data 如果要傳,必須傳 bytes(字節(jié)流)類型的。如果它是字典,可以先用 urllib.parse 模塊里的 urlencode() 編碼。
第三個參數(shù) headers 是一個字典,它就是請求頭。我們在構(gòu)造請求時,既可以通過 headers 參數(shù)直接構(gòu)造,也可以通過調(diào)用請求實例的 add_header() 方法添加。
添加請求頭最常用的方法就是通過修改 User-Agent 來偽裝瀏覽器。默認的 User-Agent 是 Python-urllib,我們可以通過修改它來偽裝瀏覽器。比如要偽裝火狐瀏覽器,你可以把它設(shè)置為:
Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11
第四個參數(shù) origin_req_host 指的是請求方的 host 名稱或者 IP 地址。
第五個參數(shù) unverifiable 表示這個請求是否是無法驗證的,默認是 False,意思就是說用戶沒有足夠權(quán)限來選擇接收這個請求的結(jié)果。例如,我們請求一個 HTML 文檔中的圖片,但是我們沒有自動抓取圖像的權(quán)限,這時 unverifiable 的值就是 True。
第六個參數(shù) method 是一個字符串,用來指示請求使用的方法,比如 GET、POST 和 PUT 等。
下面我們傳入多個參數(shù)來構(gòu)建請求:
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 個參數(shù)構(gòu)造了一個請求,其中 url 即請求 URL,headers 中指定了 User-Agent 和 Host,參數(shù) data 用 urlencode 和 bytes 方法轉(zhuǎn)成字節(jié)流。另外,指定了請求方式為 POST。
運行結(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 方法來添加:
req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')
如此一來,我們就可以更加方便地構(gòu)造請求,實現(xiàn)請求的發(fā)送啦。
在上面的過程中,我們雖然可以構(gòu)造請求,但是對于一些更高級的操作(比如 Cookies 處理、代理設(shè)置等),該怎么辦呢?
接下來,就需要更強大的工具 Handler 登場了。簡而言之,我們可以把它理解為各種處理器,有專門處理登錄驗證的,有處理 Cookie 的,有處理代理設(shè)置的。利用它們,我們幾乎可以做到 HTTP 請求中所有的事情。
首先,介紹一下 urllib.request 模塊里的 BaseHandler 類,它是所有其他 Handler 的父類,它提供了最基本的方法,例如 default_open、protocol_request 等。
接下來,就有各種 Handler 子類繼承這個 BaseHandler 類,舉例如下。
另外,還有其他的 Handler 類,這里就不一一列舉了,詳情可以參考官方文檔: https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler。
關(guān)于怎么使用它們,現(xiàn)在先不用著急,后面會有實例演示。
另一個比較重要的類就是 OpenerDirector,我們可以稱為 Opener。我們之前用過 urlopen 這個方法,實際上它就是 urllib 為我們提供的一個 Opener。
那么,為什么要引入 Opener 呢?因為需要實現(xiàn)更高級的功能。之前使用的 Request 和 urlopen 相當于類庫為你封裝好了極其常用的請求方法,利用它們可以完成基本的請求,但是現(xiàn)在不一樣了,我們需要實現(xiàn)更高級的功能,所以需要深入一層進行配置,使用更底層的實例來完成操作,所以這里就用到了 Opener。
Opener 可以使用 open 方法,返回的類型和 urlopen 如出一轍。那么,它和 Handler 有什么關(guān)系呢?簡而言之,就是利用 Handler 來構(gòu)建 Opener。
下面用幾個實例來看看它們的用法。
在訪問某些設(shè)置了身份認證的網(wǎng)站時,例如 https://ssr3.scrape.center/,我們可能會遇到這樣的認證窗口,如圖 2- 所示:
圖 2- 認證窗口
如果遇到了這種情況,那么這個網(wǎng)站就是啟用了基本身份認證,英文叫作 HTTP Basic Access Authentication,它是一種用來允許網(wǎng)頁瀏覽器或其他客戶端程序在請求時提供用戶名和口令形式的身份憑證的一種登錄驗證方式。
那么,如果要請求這樣的頁面,該怎么辦呢?借助 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)
這里首先實例化 HTTPBasicAuthHandler 對象,其參數(shù)是 HTTPPasswordMgrWithDefaultRealm 對象,它利用 add_password 方法添加進去用戶名和密碼,這樣就建立了一個處理驗證的 Handler。
接下來,利用這個 Handler 并使用 build_opener 方法構(gòu)建一個 Opener,這個 Opener 在發(fā)送請求時就相當于已經(jīng)驗證成功了。
接下來,利用 Opener 的 open 方法打開鏈接,就可以完成驗證了。這里獲取到的結(jié)果就是驗證后的頁面源碼內(nèi)容。
在做爬蟲的時候,免不了要使用代理,如果要添加代理,可以這樣做:
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)
這里我們在本地需要先事先搭建一個 HTTP 代理,運行在 8080 端口上。
這里使用了 ProxyHandler,其參數(shù)是一個字典,鍵名是協(xié)議類型(比如 HTTP 或者 HTTPS 等),鍵值是代理鏈接,可以添加多個代理。
然后,利用這個 Handler 及 build_opener 方法構(gòu)造一個 Opener,之后發(fā)送請求即可。
Cookie 的處理就需要相關(guān)的 Handler 了。
我們先用實例來看看怎樣將網(wǎng)站的 Cookie 獲取下來,相關(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)
首先,我們必須聲明一個 CookieJar 對象。接下來,就需要利用 HTTPCookieProcessor 來構(gòu)建一個 Handler,最后利用 build_opener 方法構(gòu)建出 Opener,執(zhí)行 open 函數(shù)即可。
運行結(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
可以看到,這里輸出了每個 Cookie 條目的名稱和值。
不過既然能輸出,那可不可以輸出成文件格式呢?我們知道 Cookie 實際上也是以文本形式保存的。
答案當然是肯定的,這里通過下面的實例來看看:
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)
這時 CookieJar 就需要換成 MozillaCookieJar,它在生成文件時會用到,是 CookieJar 的子類,可以用來處理 Cookie 和文件相關(guān)的事件,比如讀取和保存 Cookie,可以將 Cookie 保存成 Mozilla 型瀏覽器的 Cookie 格式。
運行之后,可以發(fā)現(xiàn)生成了一個 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 不一樣,它會保存成 libwww-perl(LWP)格式的 Cookie 文件。
要保存成 LWP 格式的 Cookie 文件,可以在聲明時就改為:
cookie = http.cookiejar.LWPCookieJar(filename)
此時生成的內(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
由此看來,生成的格式還是有比較大差異的。
那么,生成了 Cookie 文件后,怎樣從文件中讀取并利用呢?
下面我們以 LWPCookieJar 格式為例來看一下:
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 方法來讀取本地的 Cookie 文件,獲取到了 Cookie 的內(nèi)容。不過前提是我們首先生成了 LWPCookieJar 格式的 Cookie,并保存成文件,然后讀取 Cookie 之后使用同樣的方法構(gòu)建 Handler 和 Opener 即可完成操作。
運行結(jié)果正常的話,會輸出百度網(wǎng)頁的源代碼。
通過上面的方法,我們可以實現(xiàn)絕大多數(shù)請求功能的設(shè)置了。
這便是 urllib 庫中 request 模塊的基本用法,如果想實現(xiàn)更多的功能,可以參考官方文檔的說明:https://docs.python.org/3/library/urllib.request.html#basehandler-objects。
在前一節(jié)中,我們了解了請求的發(fā)送過程,但是在網(wǎng)絡(luò)不好的情況下,如果出現(xiàn)了異常,該怎么辦呢?這時如果不處理這些異常,程序很可能因報錯而終止運行,所以異常處理還是十分有必要的。
urllib 的 error 模塊定義了由 request 模塊產(chǎn)生的異常。如果出現(xiàn)了問題,request 模塊便會拋出 error 模塊中定義的異常。
URLError 類來自 urllib 庫的 error 模塊,它繼承自 OSError 類,是 error 異常模塊的基類,由 request 模塊產(chǎn)生的異常都可以通過捕獲這個類來處理。
它具有一個屬性 reason,即返回錯誤的原因。
下面用一個實例來看一下:
from urllib import request, error
try:
response = request.urlopen('https://cuiqingcai.com/404')
except error.URLError as e:
print(e.reason)
我們打開一個不存在的頁面,照理來說應該會報錯,但是這時我們捕獲了 URLError 這個異常,運行結(jié)果如下:
Not Found
程序沒有直接報錯,而是輸出了如上內(nèi)容,這樣就可以避免程序異常終止,同時異常得到了有效處理。
它是 URLError 的子類,專門用來處理 HTTP 請求錯誤,比如認證請求失敗等。它有如下 3 個屬性。
下面我們用幾個實例來看看:
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')
運行結(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 屬性。
因為 URLError 是 HTTPError 的父類,所以可以先選擇捕獲子類的錯誤,再去捕獲父類的錯誤,所以上述代碼的更好寫法如下:
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,獲取它的錯誤原因、狀態(tài)碼、headers 等信息。如果不是 HTTPError 異常,就會捕獲 URLError 異常,輸出錯誤原因。最后,用 else 來處理正常的邏輯。這是一個較好的異常處理寫法。
有時候,reason 屬性返回的不一定是字符串,也可能是一個對象。再看下面的實例:
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è)置超時時間來強制拋出 timeout 異常。
運行結(jié)果如下:
<class'socket.timeout'>
TIME OUT
可以發(fā)現(xiàn),reason 屬性的結(jié)果是 socket.timeout 類。所以,這里我們可以用 isinstance 方法來判斷它的類型,作出更詳細的異常判斷。
本節(jié)中,我們講述了 error 模塊的相關(guān)用法,通過合理地捕獲異常可以做出更準確的異常判斷,使程序更加穩(wěn)健。
前面說過,urllib 庫里還提供了 parse 模塊,它定義了處理 URL 的標準接口,例如實現(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é)中,我們介紹一下該模塊中常用的方法來看一下它的便捷之處。
該方法可以實現(xiàn) URL 的識別和分段,這里先用一個實例來看一下:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)
這里我們利用 urlparse 方法進行了一個 URL 的解析。首先,輸出了解析結(jié)果的類型,然后將結(jié)果也輸出出來。
運行結(jié)果如下:
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可以看到,返回結(jié)果是一個 ParseResult 類型的對象,它包含 6 個部分,分別是 scheme、netloc、path、params、query 和 fragment。
觀察一下該實例的 URL:
https://www.baidu.com/index.html;user?id=5#comment
可以發(fā)現(xiàn),urlparse 方法將其拆分成了 6 個部分。大體觀察可以發(fā)現(xiàn),解析時有特定的分隔符。比如,:// 前面的就是 scheme,代表協(xié)議;第一個 / 符號前面便是 netloc,即域名,后面是 path,即訪問路徑;分號;后面是 params,代表參數(shù);問號 ? 后面是查詢條件 query,一般用作 GET 類型的 URL;井號 # 后面是錨點,用于直接定位頁面內(nèi)部的下拉位置。
所以,可以得出一個標準的鏈接格式,具體如下:
scheme://netloc/path;params?query#fragment
一個標準的 URL 都會符合這個規(guī)則,利用 urlparse 方法可以將它拆分開來。
除了這種最基本的解析方式外,urlparse 方法還有其他配置嗎?接下來,看一下它的 API 用法:
urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
可以看到,它有 3 個參數(shù)。
from urllib.parse import urlparse
result = urlparse('www.baidu.com/index.html;user?id=5#comment', scheme='https')
print(result)
運行結(jié)果如下:
ParseResult(scheme='https', netloc='', path='www.baidu.com/index.html', params='user', query='id=5', fragment='comment')
可以發(fā)現(xiàn),我們提供的 URL 沒有包含最前面的 scheme 信息,但是通過默認的 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 信息時才生效。如果 URL 中有 scheme 信息,就會返回解析出的 scheme。
下面我們用實例來看一下:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(result)
運行結(jié)果如下:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')
假設(shè) URL 中不包含 params 和 query,我們再通過實例看一下:
from urllib.parse import urlparse
result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(result)
運行結(jié)果如下:
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')
可以發(fā)現(xiàn),當 URL 中不包含 params 和 query 時,fragment 便會被解析為 path 的一部分。
返回結(jié)果 ParseResult 實際上是一個元組,我們既可以用索引順序來獲取,也可以用屬性名獲取。示例如下:
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,其運行結(jié)果如下:
https
https
www.baidu.com
www.baidu.com
可以發(fā)現(xiàn),二者的結(jié)果是一致的,兩種方法都可以成功獲取。
有了 urlparse 方法,相應地就有了它的對立方法 urlunparse。它接收的參數(shù)是一個可迭代對象,但是它的長度必須是 6,否則會拋出參數(shù)數(shù)量不足或者過多的問題。先用一個實例看一下:
from urllib.parse import urlunparse
data = ['https', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
這里參數(shù) data 用了列表類型。當然,你也可以用其他類型,比如元組或者特定的數(shù)據(jù)結(jié)構(gòu)。
運行結(jié)果如下:
https://www.baidu.com/index.html;user?a=6#comment
這樣我們就成功實現(xiàn)了 URL 的構(gòu)造。
這個方法和 urlparse 方法非常相似,只不過它不再單獨解析 params 這一部分,只返回 5 個結(jié)果。上面例子中的 params 會合并到 path 中。示例如下:
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result)
運行結(jié)果如下:
SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
可以發(fā)現(xiàn),返回結(jié)果是 SplitResult,它其實也是一個元組類型,既可以用屬性獲取值,也可以用索引來獲取。示例如下:
from urllib.parse import urlsplit
result = urlsplit('https://www.baidu.com/index.html;user?id=5#comment')
print(result.scheme, result[0])
運行結(jié)果如下:
https https
與 urlunparse 方法類似,它也是將鏈接各個部分組合成完整鏈接的方法,傳入的參數(shù)也是一個可迭代對象,例如列表、元組等,唯一的區(qū)別是長度必須為 5。示例如下:
from urllib.parse import urlunsplit
data = ['https', 'www.baidu.com', 'index.html', 'a=6', 'comment']
print(urlunsplit(data))
運行結(jié)果如下:
https://www.baidu.com/index.html?a=6#comment
有了 urlunparse 和 urlunsplit 方法,我們可以完成鏈接的合并,不過前提是必須要有特定長度的對象,鏈接的每一部分都要清晰分開。
此外,生成鏈接還有另一個方法,那就是 urljoin 方法。我們可以提供一個 base_url(基礎(chǔ)鏈接)作為第一個參數(shù),將新的鏈接作為第二個參數(shù),該方法會分析 base_url 的 scheme、netloc 和 path 這 3 個內(nèi)容并對新鏈接缺失的部分進行補充,最后返回結(jié)果。
下面通過幾個實例看一下:
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'))
運行結(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 提供了三項內(nèi)容 scheme、netloc 和 path。如果這 3 項在新的鏈接里不存在,就予以補充;如果新的鏈接存在,就使用新的鏈接的部分。而 base_url 中的 params、query 和 fragment 是不起作用的。
通過 urljoin 方法,我們可以輕松實現(xiàn)鏈接的解析、拼合與生成。
這里我們再介紹一個常用的方法 —— urlencode,它在構(gòu)造 GET 請求參數(shù)的時候非常有用,示例如下:
from urllib.parse import urlencode
params = {
'name': 'germey',
'age': 25
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
這里首先聲明一個字典來將參數(shù)表示出來,然后調(diào)用 urlencode 方法將其序列化為 GET 請求參數(shù)。
運行結(jié)果如下:
https://www.baidu.com?name=germey&age=25
可以看到,參數(shù)成功地由字典類型轉(zhuǎn)化為 GET 請求參數(shù)了。
這個方法非常常用。有時為了更加方便地構(gòu)造參數(shù),我們會事先用字典來表示。要轉(zhuǎn)化為 URL 的參數(shù)時,只需要調(diào)用該方法即可。
有了序列化,必然就有反序列化。如果我們有一串 GET 請求參數(shù),利用 parse_qs 方法,就可以將它轉(zhuǎn)回字典,示例如下:
from urllib.parse import parse_qs
query = 'name=germey&age=25'
print(parse_qs(query))
運行結(jié)果如下:
{'name': ['germey'], 'age': ['25']}
可以看到,這樣就成功轉(zhuǎn)回為字典類型了。
另外,還有一個 parse_qsl 方法,它用于將參數(shù)轉(zhuǎn)化為元組組成的列表,示例如下:
from urllib.parse import parse_qsl
query = 'name=germey&age=25'
print(parse_qsl(query))
運行結(jié)果如下:
[('name', 'germey'), ('age', '25')]
可以看到,運行結(jié)果是一個列表,而列表中的每一個元素都是一個元組,元組的第一個內(nèi)容是參數(shù)名,第二個內(nèi)容是參數(shù)值。
該方法可以將內(nèi)容轉(zhuǎn)化為 URL 編碼的格式。URL 中帶有中文參數(shù)時,有時可能會導致亂碼的問題,此時可以用這個方法可以將中文字符轉(zhuǎn)化為 URL 編碼,示例如下:
from urllib.parse import quote
keyword = '壁紙'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
這里我們聲明了一個中文的搜索文字,然后用 quote 方法對其進行 URL 編碼,最后得到的結(jié)果如下:
https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8
有了 quote 方法,當然還有 unquote 方法,它可以進行 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 方法可以方便地實現(xiàn)解碼。
本節(jié)中,我們介紹了 parse 模塊的一些常用 URL 處理方法。有了這些方法,我們可以方便地實現(xiàn) URL 的解析和構(gòu)造,建議熟練掌握。
利用 urllib 的 robotparser 模塊,我們可以實現(xiàn)網(wǎng)站 Robots 協(xié)議的分析。本節(jié)中,我們來簡單了解一下該模塊的用法。
Robots 協(xié)議也稱作爬蟲協(xié)議、機器人協(xié)議,它的全名叫作網(wǎng)絡(luò)爬蟲排除標準(Robots Exclusion Protocol),用來告訴爬蟲和搜索引擎哪些頁面可以抓取,哪些不可以抓取。它通常是一個叫作 robots.txt 的文本文件,一般放在網(wǎng)站的根目錄下。
當搜索爬蟲訪問一個站點時,它首先會檢查這個站點根目錄下是否存在 robots.txt 文件,如果存在,搜索爬蟲會根據(jù)其中定義的爬取范圍來爬取。如果沒有找到這個文件,搜索爬蟲便會訪問所有可直接訪問的頁面。
下面我們看一個 robots.txt 的樣例:
User-agent: *
Disallow: /
Allow: /public/
這實現(xiàn)了對所有搜索爬蟲只允許爬取 public 目錄的功能,將上述內(nèi)容保存成 robots.txt 文件,放在網(wǎng)站的根目錄下,和網(wǎng)站的入口文件(比如 index.php、index.html 和 index.jsp 等)放在一起。
上面的 User-agent 描述了搜索爬蟲的名稱,這里將其設(shè)置為 * 則代表該協(xié)議對任何爬取爬蟲有效。比如,我們可以設(shè)置:
User-agent: Baiduspider
這就代表我們設(shè)置的規(guī)則對百度爬蟲是有效的。如果有多條 User-agent 記錄,就有多個爬蟲會受到爬取限制,但至少需要指定一條。
Disallow 指定了不允許抓取的目錄,比如上例子中設(shè)置為 / 則代表不允許抓取所有頁面。
Allow 一般和 Disallow 一起使用,一般不會單獨使用,用來排除某些限制。上例中我們設(shè)置為 /public/,則表示所有頁面不允許抓取,但可以抓取 public 目錄。
下面我們再來看幾個例子。禁止所有爬蟲訪問任何目錄的代碼如下:
User-agent: *
Disallow: /
允許所有爬蟲訪問任何目錄的代碼如下:
User-agent: *
Disallow:
另外,直接把 robots.txt 文件留空也是可以的。
禁止所有爬蟲訪問網(wǎng)站某些目錄的代碼如下:
User-agent: *
Disallow: /private/
Disallow: /tmp/
只允許某一個爬蟲訪問的代碼如下:
User-agent: WebCrawler
Disallow:
User-agent: *
Disallow: /
這些是 robots.txt 的一些常見寫法。
大家可能會疑惑,爬蟲名是從哪兒來的?為什么就叫這個名?其實它是有固定名字的了,比如百度的就叫作 BaiduSpider。表 2- 列出了一些常見搜索爬蟲的名稱及對應的網(wǎ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 | 必應 | www.bing.com |
了解 Robots 協(xié)議之后,我們就可以使用 robotparser 模塊來解析 robots.txt 了。該模塊提供了一個類 RobotFileParser,它可以根據(jù)某網(wǎng)站的 robots.txt 文件來判斷一個爬蟲是否有權(quán)限來爬取這個網(wǎng)頁。
該類用起來非常簡單,只需要在構(gòu)造方法里傳入 robots.txt 的鏈接即可。首先看一下它的聲明:
urllib.robotparser.RobotFileParser(url='')
當然,也可以在聲明時不傳入,默認為空,最后再使用 set_url 方法設(shè)置一下即可。
下面列出了這個類常用的幾個方法。
下面我們用實例來看一下:
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 對象,然后通過 set_url 方法設(shè)置了 robots.txt 的鏈接。當然,不用這個方法的話,可以在聲明時直接用如下方法設(shè)置:
rp = RobotFileParser('https://www.baidu.com/robots.txt')
接著利用 can_fetch 方法判斷網(wǎng)頁是否可以被抓取。
運行結(jié)果如下:
True
True
False
這里同樣可以使用 parse 方法執(zhí)行讀取和分析,示例如下:
可以看到這里我們利用 Baiduspider 可以抓取百度等首頁以及 homepage 頁面,但是 Googlebot 就不能抓取 homepage 頁面。
打開百度的 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 頁面的抓取,而 Googlebot 則限制了 homepage 頁面的抓取。
這里同樣可以使用 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/'))
運行結(jié)果一樣:
True
True
False
本節(jié)介紹了 robotparser 模塊的基本用法和實例,利用它,我們可以方便地判斷哪些頁面可以抓取,哪些頁面不可以抓取。
本節(jié)內(nèi)容比較多,我們介紹了 urllib 的 request、error、parse、robotparser 模塊的基本用法。這些是一些基礎(chǔ)模塊,其中有一些模塊的實用性還是很強的,比如我們可以利用 parse 模塊來進行 URL 的各種處理。
本節(jié)代碼:https://github.com/Python3WebSpider/UrllibTest。
西第十二賽段,菲利普-吉爾伯特(快步)憑借自己的天賦和經(jīng)驗,單飛奪冠。此次是他今年自巴黎-魯貝后的第一次勝利,也是他職業(yè)生涯中的第十個大環(huán)賽冠軍。亞歷克斯-阿蘭布魯(西班牙農(nóng)業(yè)銀行)獲第二,費爾南多-巴塞洛(巴斯克-穆里亞斯)第三。
賽段
總成績方面仍舊沒有發(fā)生改變。
GC
盡管最初不斷有車手嘗試進攻,但在前110公里的比賽中,高速的節(jié)奏讓他們始終保持在一起。終點62公里處,維利-斯米特(喀秋莎)發(fā)起決定性的進攻,隨后帶出了一個18人的突圍集團。包括吉爾伯特、蒂姆-德克勒爾(快步)、瓦萊里奧-孔蒂和馬可-馬爾卡托(阿聯(lián)酋航空)、朗西斯科-何塞-本托索(CCC)、何塞-華金-羅哈斯(移動之星)、尼基亞斯-阿恩特(太陽網(wǎng))、約翰-德根科爾布(崔克)、海因里希-豪斯勒(巴林美利達)、曼努埃萊-博阿羅(阿斯塔納)等。
最后40公里,進入Alto de Urruztamendi爬坡(全長2.5公里,坡度9.2%),突圍集團中德根科爾布、本托索和豪斯勒掉隊。格羅斯沙特納進攻,格爾邁加入。兩人結(jié)伴,并領(lǐng)先40秒,但這一舉動并沒堅持多久。
來到Alto de Arraiz,隨著吉爾伯特和孔蒂一起來到前方,突圍集團的人數(shù)不斷減少。不久,吉爾伯特進攻,阿蘭布魯和巴塞洛迅速做出反應。吉爾伯特率先下坡后,他們繼續(xù)追趕。
雖然兩人追擊集團在最后兩公里與吉爾伯特時間差只有10秒,但吉爾伯特并沒有給他們機會,仍加速前進。當他在終點500米處繞過最后一個彎道時,環(huán)顧四周,知道自己勝券在握。阿蘭布魯和巴塞洛在吉爾伯特越過終點線的三秒后,也相繼撞線。
而主集團進入最后一段陡峭的爬坡,不斷有車手掉隊。紅衫羅格利奇一直頂在前方,旁邊是洛佩斯、金塔納和巴爾維德。三分鐘后,主集團輕松地越過了終點線。
圖片來源:ASO
編輯:夏春花
原文鏈接:http://www.wildto.com/news/48775.html
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。