1. 程式人生 > >小白學 Python 爬蟲(12):urllib 基礎使用(二)

小白學 Python 爬蟲(12):urllib 基礎使用(二)

人生苦短,我用 Python

前文傳送門:

小白學 Python 爬蟲(1):開篇

小白學 Python 爬蟲(2):前置準備(一)基本類庫的安裝

小白學 Python 爬蟲(3):前置準備(二)Linux基礎入門

小白學 Python 爬蟲(4):前置準備(三)Docker基礎入門

小白學 Python 爬蟲(5):前置準備(四)資料庫基礎

小白學 Python 爬蟲(6):前置準備(五)爬蟲框架的安裝

小白學 Python 爬蟲(7):HTTP 基礎

小白學 Python 爬蟲(8):網頁基礎

小白學 Python 爬蟲(9):爬蟲基礎

小白學 Python 爬蟲(10):Session 和 Cookies

小白學 Python 爬蟲(11):urllib 基礎使用(一)

引言

上一篇我們聊了 urlopen 的基本使用姿勢,但這幾個簡單的引數並不足以構建一個完整的請求。對於複雜的請求,例如需要新增請求頭就顯得無能為力,這時我們可以選擇使用 Request 。

Request

官方文件:https://docs.python.org/zh-cn/3.7/library/urllib.request.html

首先來看一下 Request 的使用語法:

class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
  • url:請求的地址連結,只有這個是必傳引數,其餘都是可選引數。
  • data:如果這個引數需要傳遞,則必須傳bytes(位元組流)型別的。
  • headers:請求頭資訊,它是一個字典,可以在構造請求的時候通過 headers 之間構造,也可以呼叫 add_header() 新增。
  • origin_req_host:發起請求一方的 host 名稱或者也可以是 ip 地址。
  • unverifiable:指的是這個請求是否是無法驗證的,預設是 False 。意思就是說使用者沒有足夠許可權來選擇接收這個請求的結果。例如我們請求一個HTML文件中的圖片,但是我們沒有自動抓取影象的許可權,這時 unverifiable 的值就是 True 。
  • method:請求方法,如 GET 、 POST 、 PUT 、 DELETE 等等。

還是先來看一個簡單的示例,使用 Request 爬取部落格網站:

import urllib.request

request = urllib.request.Request('https://www.geekdigging.com/')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))

可以看到,這裡還是使用 urlopen() 來發起請求,只是引數不再是之前的 URL 、 Data 、 timeout 等等資訊,而是變成了 Request 型別的物件。

我們來構建一個稍微複雜一點的請求。

import urllib.request, urllib.parse
import json

url = 'https://httpbin.org/post'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36',
    'Content-Type': 'application/json;encoding=utf-8',
    'Host': 'geekdigging.com'
}
data = {
    'name': 'geekdigging',
    'hello':'world'
}
data = bytes(json.dumps(data), encoding='utf8')
req = urllib.request.Request(url=url, data=data, headers=headers, method='POST')
resp = urllib.request.urlopen(req)
print(resp.read().decode('utf-8'))

結果如下:

{
  "args": {}, 
  "data": "{\"name\": \"geekdigging\", \"hello\": \"world\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "41", 
    "Content-Type": "application/json;encoding=utf-8", 
    "Host": "geekdigging.com", 
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"
  }, 
  "json": {
    "hello": "world", 
    "name": "geekdigging"
  }, 
  "origin": "116.234.254.11, 116.234.254.11", 
  "url": "https://geekdigging.com/post"
}

這裡我們通過 4 個引數構建了一個 Request 物件。

通過 url 指定了訪問的連結,還是前面一片文章中提到的測試連結。

在 headers 中指定了 User-AgentContent-TypeHost 3 個引數。

在 data 中使用 json.dumps() 將一個 dict 轉換成 json 格式,並通過 bytes() 最終轉換為位元組流。

最後,指定了訪問方式為 POST 。

從最終的結果中,可以看到我們前面的設定全都成功。

進階操作

前面我們使用 Request 完成了請求頭的新增,如果我們想處理 Cookies 和使用代理訪問,就需要使用到更加強大的 Handler 了。 Handler 可以簡單理解為各種功能的處理器,使用它,幾乎可以為我們做到所有有關 HTTP 請求的事情。

urllib.request 為我們提供了 BaseHandler 類,它是所有其他 Handler 的父類,它提供了直接使用使用的方法如下:

  • add_parent():新增director作為父類。
  • close():關閉它的父類。
  • parent():開啟使用不同的協議或處理錯誤。
  • default_open():捕獲所有的URL及子類,在協議開啟之前呼叫。

接下來,就有各種 Handler 子類整合這個 BaseHandler 類:

  • HTTPDefaultErrorHandler:用來處理http響應錯誤,錯誤會丟擲HTTPError類的異常。
  • HTTPRedirectHandler:用於處理重定向。
  • ProxyHandler:用於設定代理,預設代理為空。
  • HTTPPasswordMgr:用於管理密碼,它維護使用者名稱和密碼錶。
  • AbstractBasicAuthHandler:用於獲取使用者/密碼對,然後重試請求來處理身份驗證請求。
  • HTTPBasicAuthHandler:用於重試帶有身份認證資訊的請求。
  • HTTPCookieProcessor:用於處理cookies。

等等, urllib 為我們提供的 BaseHandler 子類非常的多,小編這裡就不一一列舉,各位同學可以通過訪問官方文件來檢視。

官方文件地址:https://docs.python.org/zh-cn/3.7/library/urllib.request.html#basehandler-objects

在介紹如何使用 Handler 之前,先介紹一個高階類: OpenerDirector 。

OpenerDirector 是用來處理URL的高階類,它分三個階段來開啟URL:

在每個階段中呼叫這些方法的順序是通過對處理程式例項進行排序來確定的;每個使用此類方法的程式都會呼叫 protocol_request() 方法來預處理請求,然後呼叫 protocol_open() 來處理請求;最後呼叫 protocol_response() 方法來處理響應。

我們可以稱 OpenerDirector 為 Opener 。我們之前用過 urlopen() 這個方法,實際上它就是 urllib 為我們提供的一個 Opener 。

Opener的方法包括:

  • add_handler(handler):新增處理程式到連結中
  • open(url,data=None[,timeout]):開啟給定的URL與urlopen()方法相同
  • error(proto,*args):處理給定協議的錯誤

下面我們來演示一下如何獲取網站的 Cookies :

import http.cookiejar, urllib.request

# 例項化cookiejar物件
cookie = http.cookiejar.CookieJar()
# 使用 HTTPCookieProcessor 構建一個 handler
handler = urllib.request.HTTPCookieProcessor(cookie)
# 構建Opener
opener = urllib.request.build_opener(handler)
# 發起請求
response = opener.open('https://www.baidu.com/')
print(cookie)
for item in cookie:
    print(item.name + " = " + item.value)

程式碼中具體的含義小編就不再解釋了,註釋已經寫得比較完善。最後得到的列印結果如下:

<CookieJar[<Cookie BAIDUID=48EA1A60922D7A30F711A420D3C5BA22:FG=1 for .baidu.com/>, <Cookie BIDUPSID=48EA1A60922D7A30DA2E4CBE7B81D738 for .baidu.com/>, <Cookie PSTM=1575167484 for .baidu.com/>, <Cookie BD_NOT_HTTPS=1 for www.baidu.com/>]>
BAIDUID = 48EA1A60922D7A30F711A420D3C5BA22:FG=1
BIDUPSID = 48EA1A60922D7A30DA2E4CBE7B81D738
PSTM = 1575167484
BD_NOT_HTTPS = 1

這裡產生一個問題, cookie 既然可以列印,那麼我們能不能將 cookie 的輸出儲存到檔案中呢?

答案當然是可以的,因為我們知道, cookie 本身就是儲存在檔案中的。

# cookies 儲存 Mozilla 型檔案示例
filename = 'cookies_mozilla.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)
print('cookies_mozilla 儲存成功')

這裡我們需修改之前的 CookieJarMozillaCookieJar ,它在生成檔案時會用到,是 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   1606703804  BAIDUID 0A7A76A3705A730B35A559B601425953:FG=1
.baidu.com  TRUE    /   FALSE   3722651451  BIDUPSID    0A7A76A3705A730BE64A1F6D826869B5
.baidu.com  TRUE    /   FALSE       H_PS_PSSID  1461_21102_30211_30125_26350_30239
.baidu.com  TRUE    /   FALSE   3722651451  PSTM    1575167805
.baidu.com  TRUE    /   FALSE       delPer  0
www.baidu.com   FALSE   /   FALSE       BDSVRTM 0
www.baidu.com   FALSE   /   FALSE       BD_HOME 0

小編比較懶,就不截圖了,直接貼結果了。

當然我們除了可以將 cookies 儲存成為 Mozilla 型瀏覽器的格式,還可以將 cookies 儲存成為 libwww-perl(LWP) 格式的 Cookies 檔案。

要儲存成LWP格式的Cookies檔案,在宣告的時候需要修改為 LWPCookieJar:

# cookies 儲存 LWP 型檔案示例
filename = 'cookies_lwp.txt'
cookie = http.cookiejar.LWPCookieJar(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)
print('cookies_lwp 儲存成功')

執行結果如下:

#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="D634D45523004545C6E23691E7CE3894:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2020-11-30 02:45:24Z"; comment=bd; version=0
Set-Cookie3: BIDUPSID=D634D455230045458E6056651566B7E3; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2087-12-19 05:59:31Z"; version=0
Set-Cookie3: H_PS_PSSID=1427_21095_30210_18560_30125; path="/"; domain=".baidu.com"; path_spec; domain_dot; discard; version=0
Set-Cookie3: PSTM=1575168325; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2087-12-19 05:59:31Z"; 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

可以看到,兩種型別產生的 cookie 檔案格式差異還是非常大的。

已經生成了 cookie 檔案,下一步我們就是要在請求的時候新增 cookie ,示例程式碼如下:

# 請求是使用 Mozilla 型檔案
cookie = http.cookiejar.MozillaCookieJar()
cookie.load('cookies_mozilla.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() 方法來讀取本地的Cookies檔案,獲取到了 Cookies 的內容。

前提是,我們需要提前生成了 Mozilla 格式的 cookie 檔案,然後讀取 Cookies 之後使用同樣的方法構建 Handler 和 Opener 即可。

請求正常的時候可以相應擺渡首頁的原始碼,結果小編也就不貼了,屬實有點長。

本篇的內容就到這裡了,希望各位同學記得自己動手寫程式碼哦~~~

示例程式碼

本系列的所有程式碼小編都會放在程式碼管理倉庫 Github 和 Gitee 上,方便大家取用。

示例程式碼-Github

示例程式碼-Gitee

參考

https://www.cnblogs.com/zhangxinqi/p/9170312.html

https://cuiqingcai.com/5500.h