python3網絡爬蟲一《使用urllib.request發送請求》

使用urllib

在Python2版本中,有urllib和urlib2兩個庫可以用來實現request的發送。而在Python3中,已經不存在urllib2這個庫了,統一爲urllib。Python3 urllib庫官方鏈接:https://docs.python.org/3/library/urllib.html

 

urllib中包括了四個模塊,包括

urllib.request,urllib.error,urllib.parse,urllib.robotparser

urllib.request可以用來發送request和獲取request的結果
urllib.error包含了urllib.request產生的異常
urllib.parse用來解析和處理URL

urllib.robotparse用來解析頁面的robots.txt文件

可見其中模擬請求使用的最主要的庫便是urllib.request,異常處理用urllib.error庫。

下面會對它們一一進行詳細的介紹。

使用urllib.request發送請求

urllib.request.urlopen()基本使用
urllib.request 模塊提供了最基本的構造 HTTP 請求的方法,利用它可以模擬瀏覽器的一個請求發起過程,同時它還帶有處理 authenticaton (授權驗證), redirections (重定向), cookies (瀏覽器Cookies)以及其它內容。好,那麼首先我們來感受一下它的強大之處,我們百度爲例,我們來把這個網頁抓下來。

import urllib.request
 
response = urllib.request.urlopen("https://www.baidu.com")
print(response.read().decode("utf-8"))
真正的代碼只有兩行,我們便完成了百度的抓取,輸出了網頁的源代碼,得到了源代碼之後呢?你想要的鏈接、圖片地址、文本信息不就都可以提取出來了嗎?接下來我們看下它返回的到底是什麼,利用 type 函數輸出 response 的類型。
import urllib.request
response = urllib.request.urlopen("https://www.baidu.com")
print(type(response))
<class 'http.client.HTTPResponse'>

通過輸出結果可以發現它是一個 HTTPResposne 類型的對象,它主要包含的方法有 read() 、 readinto() 、getheader(name) 、 getheaders() 、 fileno() 等函數和 msg 、 version 、 status 、 reason 、 debuglevel 、 closed 等屬性。 得到這個對象之後,賦值爲 response ,然後就可以用 response 調用這些方法和屬性,得到返回結果的一系列信息。例如 response.read() 就可以得到返回的網頁內容, response.status 就可以得到返回結果的狀態碼,如200代表請求成功,404代表網頁未找到等。

下面再來一個實例感受一下:

import urllib.request
response = urllib.request.urlopen("https://www.baidu.com")

print(response.status)
200

print(response.getheaders())
[('Accept-Ranges', 'bytes'), ('Cache-Control', 'no-cache'), ('Content-Length', '227'), ('Content-Type', 'text/html'), ('Date', 'Tue, 25 Jul 2017 06:36:40 GMT'), ('Last-Modified', 'Wed, 28 Jun 2017 02:16:00 GMT'), ('P3p', 'CP=" OTI DSP COR IVA OUR IND COM "'), ('Pragma', 'no-cache'), ('Server', 'BWS/1.1'), ('Set-Cookie', 'BD_NOT_HTTPS=1; path=/; Max-Age=300'), ('Set-Cookie', 'BIDUPSID=BEF13521D9F33BE4108EA36C07303743; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Set-Cookie', 'PSTM=1500964600; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com'), ('Strict-Transport-Security', 'max-age=0'), ('X-Ua-Compatible', 'IE=Edge,chrome=1'), ('Connection', 'close')]

print(response.getheader("Server"))
BWS/1.1


可見,三個輸出分別輸出了響應的狀態碼,響應的頭信息,以及通過傳遞一個參數獲取了 Server 的類型。

urllib.request.urlopen()詳解

利用以上最基本的 urlopen() 方法,我們可以完成最基本的簡單網頁的 GET 請求抓取。
如果我們想給鏈接傳遞一些參數該怎麼實現呢?我們首先看一下 urlopen() 函數的API。

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

可以發現除了第一個參數可以傳遞URL之外,我們還可以傳遞其它的內容,比如 data (附加參數), timeout (超時時間)等等。

data 參數是可選的,如果要添加 data ,它要是字節流編碼格式的內容,即 bytes 類型,通過 bytes() 函數可以進行轉化,另外如果你傳遞了這個 data 參數,它的請求方式就不再是 GET 方式請求,而是 POST 。

# coding=utf-8
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding=
'utf8')
response = urllib.request.urlopen('http://httpbin.org/post', dat
a=data)
print(response.read())

在這裏我們傳遞了一個參數 word ,值是 hello 。它需要被轉碼成 bytes (字節流)類型。其中轉字節流採用了 bytes() 方法,第一個參數需要是 str (字符串)類型,需要用 urllib.parse.urlencode() 方法來將參數字典轉化爲字符串。第二個參數指定編碼格式,在這裏指定爲 utf8 。
提交的網址是 httpbin.org ,它可以提供 HTTP 請求測試。 http://httpbin.org/post 這個地址可以用來測試 POST 請求,它可以輸出請求和響應信息,其中就包含我們傳遞的 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.5"
},
"json": null,
"origin": "123.124.23.253",
"url": "http://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 occurr
ed:
Traceback (most recent call last): File "/var/py/python/urllibte
st.py", line 4, in <module> response = urllib.request.urlopen('h
ttp://httpbin.org/get', timeout=1)
...
urllib.error.URLError: <urlopen error timed out>

在這裏我們設置了超時時間是1秒,程序1秒過後服務器依然沒有響應,於是拋出了 urllib.error.URLError 異常,錯誤原因是 timed out 。

因此我們可以通過設置這個超時時間來控制一個網頁如果長時間未響應就跳過它的抓取,利用 try,except 語句就可以實現這樣的操作。

import urllib.request
import  socket
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秒,然後捕獲了 urllib.error.URLError 這個異常,然後判斷異常原因是超時異常,就得出它確實是因爲超時而報錯,打印輸出了 TIME OUT ,當然你也可以在這裏做其他的處理。

運行結果如下:

Time out!

常理來說,0.1秒內基本不可能得到服務器響應,因此輸出了 TIME OUT 的提示。這樣,我們可以通過設置 timeout 這個參數來實現超時處理,有時還是很有用的。

 

其他參數

還有 context 參數,它必須是 ssl.SSLContext 類型,用來指定 SSL 設置。cafile 和 capath 兩個參數是指定CA證書和它的路徑,這個在請求 HTTPS 鏈接時會有用。

cadefault 參數現在已經棄用了,默認爲 False 。

以上講解了 url.request.urlopen() 方法的用法,通過這個最基本的函數可以完成簡單的請求和網頁抓取,如需詳細瞭解,可以參見官方文檔。
https://docs.python.org/3/library/urllib.request.html

 

urllib.request.Request的使用

由上我們知道利用 urlopen() 方法可以實現最基本的請求發起,但這幾個簡單的參數並不足以構建一個完整的請求,如果請求中需要加入 headers 等信息,我們就可以利用更強大的 Request 類來構建一個請求。

首先我們用一個實例來感受一下 Request 的用法:

import urllib.request
 
request =urllib.request.Request("https://www.baidu.com")
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)

 

第一個參數是請求鏈接,這個是必傳參數,其他的都是可選參數。

data 參數如果要傳必須傳 bytes (字節流)類型的,如果是一個字典,可以先用 urllib.parse.urlencode() 編碼。

headers 參數是一個字典,你可以在構造 Request 時通過 headers 參數傳遞,也可以通過調用 Request 對象的 add_header() 方法來添加請求頭。請求頭最常用的用法就是通過修改 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":'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
    "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 即請求鏈接,在 headers 中指定了 User-Agent 和 Host ,傳遞的參數 data 用了 urlencode() 和 bytes() 方法來轉成字節流,另外指定了請求方式爲 POST 。

運行結果如下:

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "Germey"
  }, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Connection": "close", 
    "Content-Length": "11", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)"
  }, 
  "json": null, 
  "origin": "1.85.221.5", 
  "url": "http://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 ,實現請求的發送。

urllib.request高級特性

 

大家有沒有發現,在上面的過程中,我們雖然可以構造 Request ,但是一些更高級的操作,比如 Cookies 處理,代理該怎樣來設置?
接下來就需要更強大的工具 Handler 登場了。

 

簡而言之你可以把它理解爲各種處理器,有專門處理登錄驗證的,有處理 Cookies 的,有處理代理設置的,利用它們我們幾乎可以做到任何 HTTP 請求中所有的事情。

 

首先介紹下 urllib.request.BaseHandler ,它是所有其他 Handler 的父類,它提供了最基本的 Handler 的方法,例
如 default_open() 、 protocol_request() 等。
接下來就有各種 Handler 類繼承這個 BaseHandler ,列舉如下:

  • HTTPDefaultErrorHandler 用於處理HTTP響應錯誤,錯誤都會拋出 HTTPError 類型的異常。
  • HTTPRedirectHandler 用於處理重定向。
  • HTTPCookieProcessor 用於處理 Cookie 。
  • ProxyHandler 用於設置代理,默認代理爲空。
  • HTTPPasswordMgr 用於管理密碼,它維護了用戶名密碼的表。
  • HTTPBasicAuthHandler 用於管理認證,如果一個鏈接打開時需要認證,那麼可以用它來解決認證問題。 另外還有其他的 Handler ,可以參考官方文檔。

https://docs.python.org/3/library/urllib.request.html#urllib.request.BaseHandler

 

它們怎麼來使用,不用着急,下面會有實例爲你演示。
另外一個比較重要的就是 OpenerDirector ,我們可以稱之爲 Opener ,我們之前用過 urllib.request.urlopen() 這個方法,實際上它就是一個 Opener 。
那麼爲什麼要引入 Opener 呢?因爲我們需要實現更高級的功能,之前我們使用的 Request 、 urlopen() 相當於類庫爲你封裝好了極其常用的請求方法,利用它們兩個我們就可以完成基本的請求,但是現在不一樣了,我們需要實現更高級的功能,所以我們需要深入一層,使用更上層的實例來完成我們的操作。所以,在這裏我們就用到了比調用 urlopen() 的對象的更普遍的對象,也就是 Opener 。

Opener 可以使用 open() 方法,返回的類型和 urlopen() 如出一轍。那麼它和 Handler 有什麼關係?簡而言之,就是利用 Handler 來構建 Opener 。
認證
我們先用一個實例來感受一下:

import urllib.request
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm='PDQ Application',
                          uri='https://mahler:8092/site-updates.py',
                          user='klem',
                          passwd='kadidd!ehopper')
opener = urllib.request.build_opener(auth_handler)
urllib.request.install_opener(opener)
urllib.request.urlopen('http://www.example.com/login.html')

此處代碼爲實例代碼,用於說明 Handler 和 Opener 的使用方法。在這裏,首先實例化了一個 HTTPBasicAuthHandler 對象,然後利用 add_password() 添加進去用戶名和密碼,相當於建立了一個處理認證的處理器。
接下來利用 urllib.request.build_opener() 方法來利用這個處理器構建一個 Opener ,那麼這個 Opener 在發送請求的時候就具備了認證功能了。接下來利用 Opener 的 open() 方法打開鏈接,就可以完成認證了。
代理

如果添加代理,可以這樣做:

import urllib.request
proxy_handler = urllib.request.ProxyHandler({
'http': 'http://218.202.111.10:80',
'https': 'https://180.250.163.34:8888'
})
opener = urllib.request.build_opener(proxy_handler)
response = opener.open('https://www.baidu.com')
print(response.read())

此處代碼爲實例代碼,用於說明代理的設置方法,代理可能已經失效。

 

在這裏使用了 ProxyHandler , ProxyHandler 的參數是一個字典,key是協議類型,比如 http 還是 https 等,value是代理鏈接,可以添加多個代理。
然後利用 build_opener() 方法利用這個 Handler 構造一個 Opener ,然後發送請求即可。

 

Cookie設置

我們先用一個實例來感受一下怎樣將網站的 Cookie 獲取下來。

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=2E65A683F8A8BA3DF521469DF8EFF1E1:FG=1
BIDUPSID=2E65A683F8A8BA3DF521469DF8EFF1E1
H_PS_PSSID=20987_1421_18282_17949_21122_17001_21227_21189_21161_20927
PSTM=1474900615
BDSVRTM=0
BD_HOME=0

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

filename = 'cookie.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 的子類,可以用來處理 Cookie 和文件相關的事件,讀取和保存 Cookie ,它可以將 Cookie 保存成 Mozilla 型的格式。
運行之後可以發現生成了一個 cookie.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 3622386254 BAIDUID 05A
E39B5F56C1DEC474325CDA522D44F:FG=1
.baidu.com TRUE / FALSE 3622386254 BIDUPSID 05
AE39B5F56C1DEC474325CDA522D44F
.baidu.com TRUE / FALSE H_PS_PSSID 19638_1453
_17710_18240_21091_18560_17001_21191_21161
.baidu.com TRUE / FALSE 3622386254 PSTM 147490
2606
www.baidu.com FALSE / FALSE BDSVRTM 0
www.baidu.com FALSE / FALSE BD_HOME 0

 

另外還有一個 LWPCookieJar ,同樣可以讀取和保存 Cookie ,但是保存的格式和 MozillaCookieJar 的不一樣,它會保存成與libwww-perl的Set-Cookie3文件格式的 Cookie 。
那麼在聲明時就改爲

 

cookie = http.cookiejar.LWPCookieJar(filename)

#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="0CE9C56F598E69DB375B7C294AE5C591:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="208
4-10-14 18:25:19Z"; version=0
Set-Cookie3: BIDUPSID=0CE9C56F598E69DB375B7C294AE5C591; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2084-10-1
4 18:25:19Z"; version=0
Set-Cookie3: H_PS_PSSID=20048_1448_18240_17944_21089_21192_21161_20929; path="/"; domain=".baidu.com"; path_spec; domain_dot; di
scard; version=0
Set-Cookie3: PSTM=1474902671; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2084-10-14 18:25:19Z"; 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 文件,怎樣從文件讀取並利用呢?
下面我們以 LWPCookieJar 格式爲例來感受一下:

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('http://www.baidu.com')
print(response.read().decode('utf-8'))

前提是我們首先利用上面的方式生成了 LWPCookieJar 格式的 Cookie ,然後利用 load() 方法,傳入文件名稱,後面同樣的方法構建 handler 和 opener 即可。
運行結果正常輸出百度網頁的源代碼。

好,通過如上用法,我們可以實現絕大多數請求功能的設置了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章