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

Python請求標準庫 urllib 與 urllib3

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

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

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()方法來發起請求無疑是正確的選擇,你能夠這樣作:cookie

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對象來幫助咱們實現一些其它參數的模擬,這些須要模擬的其它參數有以下幾種:post

  • 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 爬蟲十六式 - 第八式:實例解析-全書網 >>>

相關文章
相關標籤/搜索