Python 爬蟲十六式 - 第二式: urllib 與 urllib3

Connor_Zhang發表於2019-01-07

Python請求標準庫 urllib 與 urllib3

學習一時爽,一直學習一直爽!

  大家好,我是 Connor,一個從無到有的技術小白。上一次我們說到了什麼是HTTP協議,那麼這一次我們就要動手,來真正的瞭解如何使用Python訪問一個網站了。今天我們要說的是Python自帶的標準庫,Urllib與Urllib3。

1.urllib庫

 1.1urllib的簡介

​  urllib`是Python中請求url連線的官方標準庫,在Python2中主要為urllib和urllib2,在Python3中整合成了urllib。urllib中一共有四個模組,分別是 request,error,parse,robotparser,下面我們就來看一下urllib怎麼使用吧。


 1.2 urllib.request庫

​  urllib.request模組定義了在複雜世界中幫助開啟URLs (主要是HTTP )的函式和類——基本和摘要認證、重定向、cookies等等,這個模組主要負責構造和發起網路請求,並在其中加入Headers、Proxy等。

  1.2.1 發起 GET 請求

​  如果你想開啟一個網頁,那麼使urlopen()方法來發起請求無疑是正確的選擇,你可以這樣做:

from urllib import request

resp = reqeust.urlopen('https://www.baidu.com')
print(resp.read().decode())
複製程式碼

​  在urlopen()方法中,直接寫入要訪問的url地址字串,該方法就會主動的訪問目標網址,然後返回訪問結果,返回的訪問結果是一個 http.client.HTTPResponse物件,使用該物件的read()方法即可獲取訪問網頁獲取的資料,這個資料是二進位制格式的,所以我們還需要使用decode()方法來將獲取的二進位制資料進行解碼,轉換成我們可以看的懂得字串。

  1.2.2 發起 POST 請求

​  在urlopen()方法中,urlopen()預設的訪問方式是GET,當在urlopen()方法中傳入data引數時,則會發起POST請求。注意:傳遞的data資料需要為bytes格式,你可以這樣做:

from urllib import reuqest

resp = request.urlopen('http://httpbin.org/post', data=b'word=hello')
print(resp.read().decode())
複製程式碼
  1.2.3 Request物件

  在urlopen()中不止可以傳入字串格式的url,也可以傳入Request物件來實現功能的擴充,Request物件如下所示:

class urllib.request.Request(url, data=None, headers={},
                             origin_req_host=None,
                             unverifiable=False, method=None)
複製程式碼
  1.2.4 其它引數

​  當我們需要模擬一些其他的引數的時候,簡單的urlopen() 方法已經無法滿足我們的需求了,這個時候我們就需要使用urllib.request中的Request物件來幫助我們實現一些其它引數的模擬,這些需要模擬的其它引數有如下幾種:

  • Headers:

  通過urllib發起請求的時候會有一個預設的Headers,這個Headers是"User-Agent": "Python-urllib/3.x",如果網站設有UA驗證,那麼我們的程式無法訪問成功,這個時候我們就需要偽裝UA來進行訪問,直接使用Request物件來新增Headers即可:

  from urllib import request
  
  url = 'http://httpbin.org/get'
  headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36'}
  
  # 需要使用url和headers生成一個Request物件,然後將其傳入urlopen方法中
  req = request.Request(url, headers=headers)
  resp = request.urlopen(req)
  print(resp.read().decode())
複製程式碼
  • Cookie:

  有時候當我們訪問一些網站的時候需要進行翻頁或者跳轉等其它操作,為了防止無法訪問我們想要的資料,需要讓網站識別我們是同一個使用者。這個時候我們就需要帶上cookie進行訪問。

  在設定cookie的時候由於urllib並沒有很好的處理cookie的物件,所以在這裡我們需要用到一個別的庫,即http庫,並使用裡面的cookiejar來進行cookie的管理:

  from http import cookiejar
  from urllib import request
  
  url = 'https://www.baidu.com'
  # 建立一個cookiejar物件
  cookie = cookiejar.CookieJar()
  # 使用HTTPCookieProcessor建立cookie處理器
  cookies = request.HTTPCookieProcessor(cookie)
  # 並以它為引數建立Opener物件
  opener = request.build_opener(cookies)
  # 使用這個opener來發起請求
  resp = opener.open(url)
  
  # 檢視之前的cookie物件,則可以看到訪問百度獲得的cookie
  for i in cookie:
      print(i)
複製程式碼

  在上述的方法中我們還用到了一個新的東西,即request.build_opener()方法,其實urlopen()就是一個構造好了的opener物件,在這裡我們使用request.build_opener()方法重構了一個opener()物件,我們可以通過這個物件實現urlopen()實現的任何東西。

  當然,如果把這個生成的opener使用install_opener方法來設定為全域性的,那麼這個opener就是全域性的,之後的每次訪問都會帶上這個cookie。

  # 將這個opener設定為全域性的opener
  request.install_opener(opener)
  resp = request.urlopen(url)
複製程式碼
  • Proxy:

  使用爬蟲來爬取資料的時候,如果過於頻繁的訪問,而且網站還設有限制的話,很有可能會禁封我們的ip地址,這個時候就需要設定代理,來隱藏我們的真實IP,代理需要建立一個代理處理器,使用request.ProxyHandler來生成一個代理處理器,你應該這樣做:

  from urllib import request
  
  url = 'http://httpbin.org/ip'
  proxy = {'http': '218.18.232.26:80', 'https': '218.18.232.26:80'}
  proxies = request.ProxyHandler(proxy)  # 建立代理處理器
  opener = request.build_opener(proxies)  # 建立opener物件
  
  resp = opener.open(url)
  print(resp.read().decode())
複製程式碼
  1.2.5 請求中需要注意的事情
  1. decode()方法中,如果你不寫解碼格式,那麼它會自動的將二進位制格式轉碼成當前編輯器環境的格式,通常為UTF-8,當然,有些網站的編碼格式並不會使用utf-8格式來編寫網站,這個時候就需要你指定解碼格式,如果你不清楚網站的解碼格式的話,可以嘗試檢視網頁原始碼,ctrl + F來查詢charset屬性,這時候直接指定解碼格式即可,當然如果網站中並沒有寫明charset屬性的話,那就只有多試幾次了...

  2. urlopen(),opener的open()中的data資料是二進位制的,一定要先將它們用encode()方法轉換成二進位制資料中再進行傳送。


 1.3 urllib.response

  在使用urlopen()方法或者opener的open()方法發起請求後,獲得的結果是一個response物件。

  這個物件有一些方法和屬性,可以讓我們對請求返回的結果進行一些處理。

  • read()

    獲取響應返回的資料,只能使用一次。

  • getcode()

    獲取伺服器返回的狀態碼。

  • getheaders()

    獲取返回響應的響應報頭。

  • geturl()

    獲取訪問的url。


 1.4 urllib.parse

  urllib.parse`是urllib中用來解析各種資料格式的模組。這之中有我們常用的兩個方法,所以我們只著重說兩個方法:

  1.4.1 urllib.parse.quote

  再url中,只能使用ASCII中包含的字元,也就是說,ASCII不包含特殊字元,包括中文,韓語,日語等各種字元,這些字元都無法在url中進行使用,而我們有的時候又需要將一些中文字元加入到url中,例如百度的搜尋:

www.baidu.com/s?wd=底特律

  ?之後的wd引數,就是我們搜尋的關鍵詞。我們實現的方法就是將特殊字元進行url編碼,轉換成可以使用url傳輸的格式,urlib中可以使用quote()方法來實現這個功能。

In [1]: from urllib import parse

In [2]: parse.quote('底特律')
Out[2]: '%E5%BA%95%E7%89%B9%E5%BE%8B'
複製程式碼

  這樣我們就把中文字串轉換成了url能夠識別的形式了,但是有的時候你會發現你明明進行了轉碼,但是訪問的時候還是會無法訪問,原因在於你的編碼格式和網站指定的編碼格式並不相符。

urllib.parse.quote(string, safe='/', encoding=None, errors=None)

  在上面的方法中不難看到,quote擁有多個引數,你可以通過指定編碼格式的方法來確保你轉碼後獲得的資料是正確的。

In [1]: from urllib import parse
    
In [2]: parse.quote("底特律", encoding='utf-8')
Out[2]: '%E5%BA%95%E7%89%B9%E5%BE%8B'
    
In [3]: parse.quote("底特律", encoding='gbk')
Out[3]: '%B5%D7%CC%D8%C2%C9'
複製程式碼

  效果還是很明顯的,明顯的指定不同格式後的字串編碼是不同的。當然,如過你需要解碼的時候,你也可以使用unquote()方法來解碼:

In [1]: from urllib import parse

In [2]: parse.unquote('%E5%BA%95%E7%89%B9%E5%BE%8B',encoding='utf-8')
Out[2]: '底特律'
複製程式碼
  1.4.2 urllib.parse.urlencode

  在訪問url時,我們常常需要傳遞很多的url引數,而如果用字串的方法去拼接url的話,會比較麻煩,所以urllib中提供了urlencode這個方法來拼接url引數,該方法同樣支援指定編碼格式。

In [1]: from urllib import parse

In [2]: paramers = {'address': '底特律', 'phone': '123456789', 'name': 'Connor'}

In [3]: parse.urlencode(paramers)
Out[3]: 'address=%E5%BA%95%E7%89%B9%E5%BE%8B&phone=123456789&name=Connor'

In [4]: parse.urlencode(paramers,encoding='utf-8')
Out[4]: 'address=%E5%BA%95%E7%89%B9%E5%BE%8B&phone=123456789&name=Connor'

In [5]: parse.urlencode(paramers,encoding='gbk')
Out[5]: 'address=%B5%D7%CC%D8%C2%C9&phone=123456789&name=Connor'
複製程式碼

 1.5 urllib.error

  在urllib中主要設定了兩個異常,一個是URLError,一個是HTTPErrorHTTPErrorURLError的子類。

HTTPError還包含了三個屬性:

  • code:請求的狀態碼
  • reason:錯誤的原因
  • headers:響應的報頭

例子:

In [1]: from urllib.error import HTTPError

In [2]: try:
   ...:     request.urlopen('https://www.jianshu.com')
   ...: except HTTPError as e:
   ...:     print(e.code)

403
複製程式碼

2.urllib3

  其實我原本放棄寫urllib3的打算了,因為相比requests來說,urllib和urllib3都顯得太繁瑣了,亂七八糟的功能導致幾乎沒有人寫urlib3的說明,現有的urllib3幾乎都是出自一人之手,大家相互抄都抄爛了... 我就簡單的寫一點吧,不見得比抄爛了的好,大家見諒吧。

 2.1 urllib3 簡介

​ urllib3是一個功能強大,對SAP 健全的 HTTP客戶端。許多Python生態系統已經使用了urllib3,你也應該這樣做。                     ——來自官方的自述

  urllib一共有兩個子包和八個模組,包幾乎用不到,八個模組中並不是所有的都是我們常用的東西,大概有六個左右是我們經常用的,下面我們來看一下這幾個模組吧


 2.2 urllib3 PoolManager物件

  2.2.1 urllib3.PoolMagent

  如果你想通過urllib3訪問一個網頁,那麼你必須先構造一個PoolManager物件,然後通過PoolMagent中的request方法來訪問一個網頁。

class urllib3.poolmanager.PoolManager(num_pools = 10,headers = None,** connection_pool_kw )
複製程式碼

​ 這是生成一個PoolMaager所需要的引數:

  1. num_pools 代表了快取的池的個數,如果訪問的個數大於num_pools,將按順序丟棄最初始的快取,將快取的個數維持在池的大小。
  2. headers 代表了請求頭的資訊,如果在初始化PoolManager的時候制定了headers,那麼之後每次使用PoolManager來進行訪問的時候,都將使用該headers來進行訪問。
  3. ** connection_pool_kw 是基於connection_pool 來生成的其它設定,這裡我們不細述
  2.2.2 request() 和 urlopen() 方法

  當生成了一個PoolManager之後,我們就可以開始訪問我們需要訪問的網頁了。

  你可以通過 urlopen()方法或者request()方法來訪問一個網頁,當你訪問完成之後,將會返回一個HTTPREsopnse物件給你,你可以通過如下的方法來讀取獲取的響應內容:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.baidu.com', body=data)
print(resp1.data.decode())
複製程式碼

  當然,你也可以使用urlopen()方法來開啟一個網頁,他們兩者幾乎沒有任何區別:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp2 = http.urlopen('GET', 'http://www.baidu.com', body=data)
print(resp2.data.decode())
複製程式碼

  除了普通的 GET 請求之外,你還可以使用request()進行 POST 請求:

import urllib3
import json

data = json.dumps({'abc': '123'})
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('POST', 'http://www.httpbin.org/post', body=data)
print(resp1.data.decode())
複製程式碼

  當然,request() 能做的東西,urlopen()都能做:

import urllib3
import json

data = json.dumps({'abc': '123'})
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp2 = http.urlopen('POST', 'http://www.httpbin.org/post', body=data)
print(resp1.data.decode())
複製程式碼

  你還可以為他們設定超時時間,只需要添上timeout引數即可:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.baidu.com', body=data, timeout=5)
print(resp1.data.decode())

複製程式碼
import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.urlopen('GET', 'http://www.baidu.com', body=data, timeout=5)
print(resp1.data.decode())
複製程式碼

  你也可以使用retries設定重試次數:

import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.baidu.com', body=data, timeout=5, retries=5)
print(resp1.data.decode())

複製程式碼
import urllib3

http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.urlopen('GET', 'http://www.baidu.com', body=data, timeout=5, retries=5)
print(resp1.data.decode())
複製程式碼

注意事項

  • urllib3 並沒有辦法單獨設定cookie,所以如果你想使用cookie的話,可以將cookie放入到headers中
  2.2.3 request() 和 urlopen() 的區別

  我個人還是比較建議使用request()來進行訪問的,因為相比之下,使用request()來進行訪問,原因很簡單,我們可以看一下ruquest()urlopen()之間的差距:

def request(self, method, url, fields=None, headers=None, **urlopen_kw):
複製程式碼
def urlopen(self, method, url, redirect=True, **kw):
複製程式碼

  如果你用的是 request() 方法的話,那你還可以通過這種方式來直接進行post請求,不需要將 data引數轉換成JSON格式

import urllib3
import json

data = {'abc': '123'}
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('POST', 'http://www.httpbin.org/post', field=data)
print(resp1.data.decode())
複製程式碼

  如果你用的是 request() 方法的話,那你還可以通過這種方式來直接進行GET請求,不需要自己拼接url引數

import urllib3
import json

data = {'abc': '123'}
http = urllib3.PoolManager(num_pools=5, headers={'User-Agent': 'ABCDE'})
resp1 = http.request('GET', 'http://www.httpbin.org/post', field=data)
print(resp1.data.decode())
複製程式碼

  差距還是很明顯的,urlopen()request()有三個引數是不一樣的,你會發現request()多了fields,headers兩個引數。而且相比之下 reuqest() 方法還是比較符合人們的使用方式的,所以更多的也就使用 request() 方法了。

  雖然urlopen()沒有fielder,但這並不代表它不能進行POST訪問,相反,兩個方法都有一個body屬性,這個屬性就是我們平時POST請求時所熟知的data引數,只不過你需要將data字典先轉換成JSON格式再進行傳輸。fielder屬性其實也是用來傳輸data的,但是它的功能是可以直接將字典物件傳入,無需再進行json格式轉換了。如果你用的是GET方式的話,那fielder可以幫你進行url拼接,如果是POST方式的話,filder可以幫你主動轉換成JSON格式。不過特別要宣告的一點是 fielder 和 body 中只能存在一個,這個功能說實話,個人覺得寫這兩個特別類似的方法... Emmn... 總是有點雞肋的感覺。


 2.2 urllib3 ProxyManager

  如果你需要使用代理來訪問某個網站的話, 那麼你可以使用 ProxyManager 物件來進行設定

def __init__(self, proxy_url, num_pools=10, headers=None,
             proxy_headers=None, **connection_pool_kw):
複製程式碼

ProxyManager和PoolManager的方法基本完全相同,這裡舉個簡單的小例子,就不再贅述了:

import urllib3
import json

data = {'abc': '123'}
proxy = urllib3.ProxyManager('http://50.233.137.33:80', headers={'connection': 'keep-alive'})
resp1 = proxy.request('POST', 'http://www.httpbin.org/post', field=data)
print(resp1.data.decode())
複製程式碼

  好啦,這就是全部的urllib和urllib3的文章啦,恭喜你終於讀完了這篇文章,不知道今天你又學會了多少呢?   我是Connor,一個從無到有的技術小白,這一次我們就講到這裡啦,我們下期再見!

學習一時爽,一直學習一直爽!


系列文章連線:

Python 爬蟲十六式 - 第一式:HTTP協議 >>>
Python 爬蟲十六式 - 第三式:Requests的用法 >>>
Python 爬蟲十六式 - 第四式:使用Xpath提取網頁內容 >>>
Python 爬蟲十六式 - 第五式:BeautifulSoup,美味的湯 >>>
Python 爬蟲十六式 - 第六式:JQuery的假兄弟-pyquery >>>
Python 爬蟲十六式 - 第七式:正則的藝術 >>>
Python 爬蟲十六式 - 第八式:例項解析-全書網 >>>

相關文章