Python3網絡爬蟲實戰---20、使用Urllib:發送請求

上一篇文章: Python3網絡爬蟲實戰---1九、代理基本原理
下一篇文章: Python3網絡爬蟲實戰---2一、使用Urllib:處理異常

學習爬蟲,最初的操做即是來模擬瀏覽器向服務器發出一個請求,那麼咱們須要從哪一個地方作起呢?請求須要咱們本身來構造嗎?咱們須要關心請求這個數據結構的實現嗎?咱們須要瞭解 HTTP、TCP、IP 層的網絡傳輸通訊嗎?咱們須要知道服務器的響應和應答原理嗎?html

可能你不知道無從下手,不用擔憂,Python 的強大之處就是提供了功能齊全的類庫來幫助咱們完成這些請求,最基礎的 HTTP 庫有 Urllib、Httplib二、Requests、Treq 等。python

拿 Urllib 這個庫來講,有了它,咱們只須要關心請求的連接是什麼,須要傳的參數是什麼以及可選的請求頭設置就行了,不用深刻到底層去了解它究竟是怎樣來傳輸和通訊的。有了它,兩行代碼就能夠完成一個請求和響應的處理過程,獲得網頁內容,是否是感受方便極了?nginx

接下來,就讓咱們從最基礎的部分開始瞭解這些庫的使用方法吧。json

使用Urllib

在 Python2 版本中,有 Urllib 和 Urlib2 兩個庫能夠用來實現Request的發送。而在 Python3 中,已經不存在 Urllib2 這個庫了,統一爲 Urllib,其官方文檔連接爲:https://docs.python.org/3/lib...segmentfault

咱們首先了解一下 Urllib 庫,它是 Python 內置的 HTTP 請求庫,也就是說咱們不須要額外安裝便可使用,它包含四個模塊:瀏覽器

  • 第一個模塊 request,它是最基本的 HTTP 請求模塊,咱們能夠用它來模擬發送一請求,就像在瀏覽器裏輸入網址而後敲擊回車同樣,只須要給庫方法傳入 URL 還有額外的參數,就能夠模擬實現這個過程了。
  • 第二個 error 模塊即異常處理模塊,若是出現請求錯誤,咱們能夠捕獲這些異常,而後進行重試或其餘操做保證程序不會意外終止。
  • 第三個 parse 模塊是一個工具模塊,提供了許多 URL 處理方法,好比拆分、解析、合併等等的方法。
  • 第四個模塊是 robotparser,主要是用來識別網站的 robots.txt 文件,而後判斷哪些網站能夠爬,哪些網站不能夠爬的,其實用的比較少。

在這裏重點對前三個模塊進行下講解。服務器

發送請求

使用 Urllib 的 request 模塊咱們能夠方便地實現 Request 的發送並獲得 Response,咱們本節來看下它的具體用法。cookie

1. urlopen()

urllib.request 模塊提供了最基本的構造 HTTP 請求的方法,利用它能夠模擬瀏覽器的一個請求發起過程,同時它還帶有處理authenticaton(受權驗證),redirections(重定向),cookies(瀏覽器Cookies)以及其它內容。
咱們來感覺一下它的強大之處,以 Python 官網爲例,咱們來把這個網頁抓下來:網絡

import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))

看一下運行結果,如圖 3-1 所示:數據結構

clipboard.png

圖 3-1 運行結果
真正的代碼只有兩行,咱們便完成了 Python 官網的抓取,輸出了網頁的源代碼,獲得了源代碼以後呢?咱們想要的連接、圖片地址、文本信息不就均可以提取出來了嗎?
接下來咱們看下它返回的究竟是什麼,利用 type() 方法輸出 Response 的類型。

import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print(type(response))

輸出結果以下:

<class 'http.client.HTTPResponse'>

經過輸出結果能夠發現它是一個 HTTPResposne 類型的對象,它主要包含的方法有 read()、readinto()、getheader(name)、getheaders()、fileno() 等方法和 msg、version、status、reason、debuglevel、closed 等屬性。
獲得這個對象以後,咱們把它賦值爲 response 變量,而後就能夠調用這些方法和屬性,獲得返回結果的一系列信息了。
例如調用 read() 方法能夠獲得返回的網頁內容,調用 status 屬性就能夠獲得返回結果的狀態碼,如 200 表明請求成功,404 表明網頁未找到等。
下面再來一個實例感覺一下:

import urllib.request

response = urllib.request.urlopen('https://www.python.org')
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))

運行結果以下:

200
[('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('X-Clacks-Overhead', 'GNU Terry Pratchett'), ('Content-Length', '47397'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 01 Aug 2016 09:57:31 GMT'), ('Via', '1.1 varnish'), ('Age', '2473'), ('Connection', 'close'), ('X-Served-By', 'cache-lcy1125-LCY'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '23'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
nginx

可見,三個輸出分別輸出了響應的狀態碼,響應的頭信息,以及經過調用 getheader() 方法並傳遞一個參數 Server 獲取了 headers 中的 Server 值,結果是 nginx,意思就是服務器是 nginx 搭建的。
利用以上最基本的 urlopen() 方法,咱們能夠完成最基本的簡單網頁的 GET 請求抓取。
若是咱們想給連接傳遞一些參數該怎麼實現呢?咱們首先看一下 urlopen() 函數的API:

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)

能夠發現除了第一個參數能夠傳遞 URL 以外,咱們還能夠傳遞其它的內容,好比 data(附加數據)、timeout(超時時間)等等。
下面咱們詳細說明下這幾個參數的用法。

data參數

data 參數是可選的,若是要添加 data,它要是字節流編碼格式的內容,即 bytes 類型,經過 bytes() 方法能夠進行轉化,另外若是傳遞了這個 data 參數,它的請求方式就再也不是 GET 方式請求,而是 POST。
下面用一個實例來感覺一下:

import urllib.parse
import urllib.request

data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read())

在這裏咱們傳遞了一個參數 word,值是 hello。它須要被轉碼成bytes(字節流)類型。其中轉字節流採用了 bytes() 方法,第一個參數須要是 str(字符串)類型,須要用 urllib.parse 模塊裏的 urlencode() 方法來將參數字典轉化爲字符串。第二個參數指定編碼格式,在這裏指定爲 utf8。
在這裏請求的站點是 httpbin.org,它能夠提供 HTTP 請求測試,本次咱們請求的 URL 爲:http://httpbin.org/post,這個連接能夠用來測試 POST 請求,它能夠輸出 Request 的一些信息,其中就包含咱們傳遞的 data 參數。
運行結果以下:

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "word": "hello"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "10", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Python-urllib/3.7"
  }, 
  "json": null, 
  "origin": "124.126.3.94, 124.126.3.94", 
  "url": "https://httpbin.org/post"
}

咱們傳遞的參數出如今了 form 字段中,這代表是模擬了表單提交的方式,以 POST 方式傳輸數據。

timeout參數

timeout 參數能夠設置超時時間,單位爲秒,意思就是若是請求超出了設置的這個時間尚未獲得響應,就會拋出異常,若是不指定,就會使用全局默認時間。它支持 HTTP、HTTPS、FTP 請求。
下面來用一個實例感覺一下:

import urllib.request

response = urllib.request.urlopen('http://httpbin.org/get', timeout=1)
print(response.read())

運行結果以下:

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('http://httpbin.org/get', timeout=1)
...
urllib.error.URLError: <urlopen error timed out>

在這裏咱們設置了超時時間是 1 秒,程序 1 秒事後服務器依然沒有響應,因而拋出了 URLError 異常,它屬於 urllib.error 模塊,錯誤緣由是超時。
所以咱們能夠經過設置這個超時時間來控制一個網頁若是長時間未響應就跳過它的抓取,利用 try except 語句就能夠實現這樣的操做,代碼以下:

import socket
import urllib.request
import urllib.error

try:
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')

在這裏咱們請求了 http://httpbin.org/get 這個測試連接,設置了超時時間是 0.1 秒,而後捕獲了 URLError 這個異常,而後判斷異常緣由是 socket.timeout 類型,意思就是超時異常,就得出它確實是由於超時而報錯,打印輸出了 TIME OUT。
運行結果以下:

TIME OUT

常理來講,0.1 秒內基本不可能獲得服務器響應,所以輸出了 TIME OUT 的提示。
這樣,咱們能夠經過設置 timeout 這個參數來實現超時處理,有時仍是頗有用的。

其餘參數

還有 context 參數,它必須是 ssl.SSLContext 類型,用來指定 SSL 設置。
cafile 和 capath 兩個參數是指定 CA 證書和它的路徑,這個在請求 HTTPS 連接時會有用。
cadefault 參數如今已經棄用了,默認爲 False。
以上講解了 urlopen() 方法的用法,經過這個最基本的函數能夠完成簡單的請求和網頁抓取,如需更加詳細瞭解,能夠參見官方文檔:https://docs.python.org/3/lib...

2. Request

由上咱們知道利用 urlopen() 方法能夠實現最基本請求的發起,但這幾個簡單的參數並不足以構建一個完整的請求,若是請求中須要加入 Headers 等信息,咱們就能夠利用更強大的 Request 類來構建一個請求。
首先咱們用一個實例來感覺一下 Request 的用法:

import urllib.request

request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))

能夠發現,咱們依然是用 urlopen() 方法來發送這個請求,只不過此次 urlopen() 方法的參數再也不是一個 URL,而是一個 Request 類型的對象,經過構造這個這個數據結構,一方面咱們能夠將請求獨立成一個對象,另外一方面可配置參數更加豐富和靈活。
下面咱們看一下 Request 均可以經過怎樣的參數來構造,它的構造方法以下:

class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
  • 第一個 url 參數是請求 URL,這個是必傳參數,其餘的都是可選參數。
  • 第二個 data 參數若是要傳必須傳 bytes(字節流)類型的,若是是一個字典,能夠先用 urllib.parse 模塊裏的 urlencode() 編碼。
  • 第三個 headers 參數是一個字典,這個就是 Request Headers 了,你能夠在構造 Request 時經過 headers 參數直接構造,也能夠經過調用 Request 實例的 add_header() 方法來添加。

添加 Request Headers 最經常使用的用法就是經過修改 User-Agent 來假裝瀏覽器,默認的 User-Agent 是 Python-urllib,咱們能夠經過修改它來假裝瀏覽器,好比要假裝火狐瀏覽器,你能夠把它設置爲:

Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11
  • 第四個 origin_req_host 參數指的是請求方的 host 名稱或者 IP 地址。
  • 第五個 unverifiable 參數指的是這個請求是不是沒法驗證的,默認是False。意思就是說用戶沒有足夠權限來選擇接收這個請求的結果。例如咱們請求一個 HTML 文檔中的圖片,可是咱們沒有自動抓取圖像的權限,這時 unverifiable 的值就是 True。
  • 第六個 method 參數是一個字符串,它用來指示請求使用的方法,好比GET,POST,PUT等等。

下面咱們傳入多個參數構建一個 Request 來感覺一下:

from urllib import request, parse

url = 'http://httpbin.org/post'
headers = {
    'User-Agent': ,
    'Host': 'httpbin.org'
}
dict = {
    'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

在這裏咱們經過四個參數構造了一個 Request,url 即請求 URL,在headers 中指定了 User-Agent 和 Host,傳遞的參數 data 用了 urlencode() 和 bytes() 方法來轉成字節流,另外指定了請求方式爲 POST。
運行結果以下:

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "mark"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "9", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
  }, 
  "json": null, 
  "origin": "124.126.3.94, 124.126.3.94", 
  "url": "https://httpbin.org/post"
}

經過觀察結果能夠發現,咱們成功設置了 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)')

如此一來,咱們就能夠更加方便地構造一個 Request,實現請求的發送啦。

3. 高級用法

有沒有發現,在上面的過程當中,咱們雖然能夠構造 Request,可是一些更高級的操做,好比 Cookies 處理,代理設置等操做咱們該怎麼辦?
接下來就須要更強大的工具 Handler 登場了。
簡而言之咱們能夠把它理解爲各類處理器,有專門處理登陸驗證的,有處理 Cookies 的,有處理代理設置的,利用它們咱們幾乎能夠作到任何 HTTP 請求中全部的事情。
首先介紹下 urllib.request 模塊裏的 BaseHandler類,它是全部其餘 Handler 的父類,它提供了最基本的 Handler 的方法,例如 default_open()、protocol_request() 方法等。
接下來就有各類 Handler 子類繼承這個 BaseHandler 類,舉例幾個以下:

  • HTTPDefaultErrorHandler 用於處理 HTTP 響應錯誤,錯誤都會拋出 HTTPError 類型的異常。
  • HTTPRedirectHandler 用於處理重定向。
  • HTTPCookieProcessor 用於處理 Cookies。
  • ProxyHandler 用於設置代理,默認代理爲空。
  • HTTPPasswordMgr 用於管理密碼,它維護了用戶名密碼的表。
  • HTTPBasicAuthHandler 用於管理認證,若是一個連接打開時須要認證,那麼能夠用它來解決認證問題。
  • 另外還有其餘的 Handler 類,在這不一一列舉了,詳情能夠參考官方文檔: https://docs.python.org/3/lib...

它們怎麼來使用,不用着急,下面會有實例爲你演示。
另一個比較重要的類就是 OpenerDirector,咱們能夠稱之爲 Opener,咱們以前用過 urlopen() 這個方法,實際上它就是 Urllib爲咱們提供的一個 Opener。
那麼爲何要引入 Opener 呢?由於咱們須要實現更高級的功能,以前咱們使用的 Request、urlopen() 至關於類庫爲你封裝好了極其經常使用的請求方法,利用它們兩個咱們就能夠完成基本的請求,可是如今不同了,咱們須要實現更高級的功能,因此咱們須要深刻一層進行配置,使用更底層的實例來完成咱們的操做。
因此,在這裏咱們就用到了比調用 urlopen() 的對象的更廣泛的對象,也就是 Opener。
Opener 可使用 open() 方法,返回的類型和 urlopen() 一模一樣。那麼它和 Handler 有什麼關係?簡而言之,就是利用 Handler 來構建 Opener。
下面咱們用幾個實例來感覺一下他們的用法:

認證

有些網站在打開時它就彈出了一個框,直接提示你輸入用戶名和密碼,認證成功以後才能查看頁面,如圖 3-2 所示:

clipboard.png

圖 3-2 認證頁面
那麼咱們若是要請求這樣的頁面怎麼辦呢?
藉助於 HTTPBasicAuthHandler 就能夠完成,代碼以下:

from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError

username = 'username'
password = 'password'
url = 'http://localhost:5000/'

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 對象,參數是 HTTPPasswordMgrWithDefaultRealm 對象,它利用 add_password() 添加進去用戶名和密碼,這樣咱們就創建了一個處理認證的 Handler。
接下來利用 build_opener() 方法來利用這個 Handler 構建一個 Opener,那麼這個 Opener 在發送請求的時候就至關於已經認證成功了。
接下來利用 Opener 的 open() 方法打開連接,就能夠完成認證了,在這裏獲取到的結果就是認證後的頁面源碼內容。

代理

在作爬蟲的時候免不了要使用代理,若是要添加代理,能夠這樣作:

from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener

proxy_handler = ProxyHandler({
    'http': 'http://127.0.0.1:9743',
    'https': 'https://127.0.0.1:9743'
})
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)

在此本地搭建了一個代理,運行在 9743 端口上。
在這裏使用了 ProxyHandler,ProxyHandler 的參數是一個字典,鍵名是協議類型,好比 HTTP 仍是 HTTPS 等,鍵值是代理連接,能夠添加多個代理。
而後利用 build_opener() 方法利用這個 Handler 構造一個 Opener,而後發送請求便可。

Cookies

Cookies 的處理就須要 Cookies 相關的 Handler 了。
咱們先用一個實例來感覺一下怎樣將網站的 Cookies 獲取下來,代碼以下:

import http.cookiejar, urllib.request

cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
for item in cookie:
    print(item.name+"="+item.value)

首先咱們必須聲明一個 CookieJar 對象,接下來咱們就須要利用 HTTPCookieProcessor 來構建一個 Handler,最後利用 build_opener() 方法構建出 Opener,執行 open() 函數便可。
運行結果以下:

BAIDUID=4329C4F53C9D52CA1E6AC6CA18DA356F:FG=1
BIDUPSID=4329C4F53C9D52CA1E6AC6CA18DA356F
H_PS_PSSID=26522_1449_21090_29135_29238_28519_29098_29368_28834_29221_26350_20719
PSTM=1560743836
delPer=0
BDSVRTM=0
BD_HOME=0

能夠看到輸出了每一條 Cookie 的名稱還有值。
不過既然能輸出,那可不能夠輸出成文件格式呢?咱們知道 Cookies 實際也是以文本形式保存的。
答案固然是確定的,咱們用下面的實例來感覺一下:

filename = 'cookies.txt'
cookie = http.cookiejar.MozillaCookieJar(filename)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True, ignore_expires=True)

這時的 CookieJar就須要換成 MozillaCookieJar,生成文件時須要用到它,它是 CookieJar 的子類,能夠用來處理 Cookies 和文件相關的事件,讀取和保存 Cookies,它能夠將 Cookies 保存成 Mozilla 型瀏覽器的 Cookies 的格式。
運行以後能夠發現生成了一個 cookies.txt 文件。
內容以下:

# Netscape HTTP Cookie File
# http://curl.haxx.se/rfc/cookie_spec.html
# This is a generated file!  Do not edit.

.baidu.com    TRUE    /    FALSE    3708227627    BAIDUID    7270D7398BA0805A388F14699840D7DC:FG=1
.baidu.com    TRUE    /    FALSE    3708227627    BIDUPSID    7270D7398BA0805A388F14699840D7DC
.baidu.com    TRUE    /    FALSE        H_PS_PSSID    1430_21093_29135_29237_28518_29098_29368_28837_29221
.baidu.com    TRUE    /    FALSE    3708227627    PSTM    1560743980
.baidu.com    TRUE    /    FALSE        delPer    0
www.baidu.com    FALSE    /    FALSE        BDSVRTM    0
www.baidu.com    FALSE    /    FALSE        BD_HOME    0

另外還有一個 LWPCookieJar,一樣能夠讀取和保存 Cookies,可是保存的格式和 MozillaCookieJar 的不同,它會保存成與 libwww-perl(LWP) 的 Cookies 文件格式。
要保存成 LWP 格式的 Cookies 文件,能夠在聲明時就改成:

cookie = http.cookiejar.LWPCookieJar(filename)

生成的內容以下:

#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="A19638BE46B11E183219DD2CFBC4557E:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2087-07-05 07:14:46Z"; version=0
Set-Cookie3: BIDUPSID=A19638BE46B11E183219DD2CFBC4557E; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2087-07-05 07:14:46Z"; version=0
Set-Cookie3: H_PS_PSSID=26524_1444_21120_29135_29237_28519_29098_29369_28832_29220; path="/"; domain=".baidu.com"; path_spec; domain_dot; discard; version=0
Set-Cookie3: PSTM=1560744039; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2087-07-05 07:14:46Z"; version=0
Set-Cookie3: delPer=0; path="/"; domain=".baidu.com"; path_spec; domain_dot; discard; version=0
Set-Cookie3: BDSVRTM=0; path="/"; domain="www.baidu.com"; path_spec; discard; version=0
Set-Cookie3: BD_HOME=0; path="/"; domain="www.baidu.com"; path_spec; discard; version=0

由此看來生成的格式仍是有比較大的差別的。
那麼生成了 Cookies 文件,怎樣從文件讀取並利用呢?
下面咱們以 LWPCookieJar 格式爲例來感覺一下:

cookie = http.cookiejar.LWPCookieJar()
cookie.load('cookies.txt', ignore_discard=True, ignore_expires=True)
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))

能夠看到咱們這裏調用了 load() 方法來讀取本地的 Coookis 文件,獲取到了 Cookies 的內容。不過前提是咱們首先利用生成了 LWPCookieJar 格式的 Cookies,獲取到 Cookies 以後,後面一樣的方法構建 Handler 和 Opener 便可。
運行結果正常輸出百度網頁的源代碼。
好,經過如上用法,咱們能夠實現絕大多數請求功能的設置了。

4. 結語

以上即是 Urllib 庫中 request 模塊的基本用法,若是有更多想實現的功能,能夠參考官方文檔的說明:https://docs.python.org/3/lib...

上一篇文章: Python3網絡爬蟲實戰---1九、代理基本原理
下一篇文章: Python3網絡爬蟲實戰---2一、使用Urllib:處理異常
相關文章
相關標籤/搜索