網路爬蟲必備知識之requests庫
就庫的範圍,個人認為網路爬蟲必備庫知識包括urllib、requests、re、BeautifulSoup、concurrent.futures,接下來將結對requests庫的使用方法進行總結
1. requests庫簡介
官方中文文件: ofollow,noindex">http://docs.python-requests.org/zh_CN/latest/user/quickstart.html
Requests 是用Python語言編寫,基於 urllib,採用 Apache2 Licensed 開源協議的 HTTP 庫。
它比 urllib 更加方便,上篇部落格中使用的urllib方法都可以通過requests實現,個人認為更方便於爬蟲。
requests庫功能過於強大,包括:保持活力和連線池、支援國際域名和網址、會話與Cookie永續性、瀏覽器式SSL驗證、自動內容解碼、基本/摘要式身份驗證、自動解壓縮、Unicode響應body、HTTP(s)代理支援、多部分檔案上傳、流媒體下載、連線超時、分塊的請求、.netrc 支援等
request依賴包關係:
requests==2.19.1 - certifi [required: >=2017.4.17, installed: 2018.4.16]#CA認證模組 - chardet [required: <3.1.0,>=3.0.2, installed: 3.0.4]#通用字元編碼檢測器模組 - idna [required: <2.8,>=2.5, installed: 2.7]#國際化域名解析模組 - urllib3 [required: <1.24,>=1.21.1, installed: 1.23] #執行緒安全HTTP庫
2. Response請求響應類
由於resquests方法都將返回一個Response物件,為了方便大家對後面每個request方法的測試,我決定將Response類放在最前面總結
Response類封裝在requests.modles中,該Response物件包含伺服器對HTTP請求的響應資訊
該物件包含的屬性和方法:
-
apparent_encodind:
由chardet庫提供的明顯編碼。
-
close():
將連線釋放回池中,即關閉連線,通常不需要呼叫
-
content:
響應的內容,以位元組為單位。
-
cookies
=None : 伺服器發回的Cookies CookieJar。
-
elapsed
=None : 傳送請求和響應到達之間所經過的時間量(作為timedelta)。該屬性具體測量傳送請求的第一個位元組和完成解析報頭之間的時間。因此,它不受消費響應內容或stream
關鍵字引數值的影響。
-
encoding
=None : 編碼以在訪問r.text時進行解碼。
-
headers
=None : 不區分大小寫的響應頭字典。例如,headers['content-encoding']
將返回'Content-Encoding'
響應頭的值。
-
history
=None :Response
請求歷史記錄中的物件列表。任何重定向響應都會在這裡結束。該列表從最舊的到最近的請求進行排序。
-
is_permanent_redirect:
如果此響應為真,則為重定向的永久版本之一。
-
is_redirect:
如果此響應是可以自動處理的格式良好的HTTP重定向,則為真。
-
iter_content
( chunk_size = 1 , decode_unicode = False ):迭代響應資料。在請求中設定stream = True時,可以避免將內容一次性讀入記憶體以獲得較大的響應。塊大小是它應該讀入記憶體的位元組數;chunk_size必須是int或None型別。stream = True將在資料以任何大小接收到的資料到達時讀取資料。如果stream = False,則資料作為單個塊返回;如果decode_unicode為True,則內容將使用基於響應的最佳可用編碼進行解碼。
-
iter_lines(
chunk_size = 512 , decode_unicode = None , delimiter = None ): 迭代響應資料,一次一行。在請求中設定stream = True時,可以避免將內容一次性讀入記憶體以獲得較大的響應。
-
json
( ** kwargs ):返回響應的json編碼內容
-
links:
返回解析的響應頭部連結
-
next:
返回重定向鏈中下一個請求的PreparedRequest
-
ok:
如果status_code
小於400 則返回True,否則返回False
-
reason
=None: 響應HTTP狀態的文字原因,例如“未找到”或“確定”。
-
request
=None: 一個響應的物件。
-
status_code
=None: 整數響應HTTP狀態的程式碼,例如404或200。
-
text:
響應的內容,以unicode表示。
-
url
=None: 響應的最終URL位置
3. requests提供的系列HTTP方法
requests庫提供了包含requests.request函式在內告知伺服器意圖的 7種方法,7種方法分別是:get,options,head,post,put,pach,delete ,這7種方法都封裝在 api.py 模組種,通過閱讀原始碼我們會發現這7種方法的內部實現都是 直接呼叫的request函式介面 ,也就是說所有方法的引數都在request函式引數中,所有方法能實現的功能都可以由request函式實現。下面將依次介紹各方法的功能和函式原型,若沒有例子不用在意,將在第三節內容後通過具體實踐對各方法詳細介紹。
(1)get:獲取資源
請求訪問已被URI識別的資源,指定的資源經伺服器端解析後返回響應內容,也就是說如果請求的是文字,那就保持原樣返回,如果是像CGI(通用閘道器介面)那樣的程式,則返回經過執行後的輸出結果。
函式原型:
def get(url, params=None, **kwargs): r"""Sends a GET request. :param url: URL for the new :class:`Request` object. :param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ kwargs.setdefault('allow_redirects', True) return request('get', url, params=params, **kwargs)
注意註釋已說明引數為字典或位元組,返回一個response物件,看下面簡單示例:
params={'name':'張三','age':17} response = requests.get('https://www.baidu.com', params = params) print('###以下為response屬性:') print([attr for attr in dir(response) if not attr.startswith('_')]) print("status_code:",response.status_code) print("url:",response.url)
從輸出介面可以看出response物件的屬性(以_開頭的屬性都沒打印出來),大家可以一一的列印看看各屬性值是什麼。
這裡主要說下response.text和response.content的區別,這兩個屬性都返回響應內容主體,text返回的是字串型別,content返回的是二進位制型別,很多情況下的網站如果直接response.text會出現亂碼的問題,可以通過response.content.decode('utf-8')的方法解決通過response.text直接返回顯示亂碼的問題。
(2)opetions:詢問支援的方法
options方法用來查詢針對請求URL指定的資源支援的方法,說的直白點就是伺服器返回它支援的這7種方法中的某種
函式原型:
def options(url, **kwargs): r"""Sends an OPTIONS request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ kwargs.setdefault('allow_redirects', True) return request('options', url, **kwargs)
上面提到了預設支援允許重定向,options方法使用很少
response = requests.options('https://www.baidu.com')
(3)head:獲得報文首部
函式原型:
def head(url, **kwargs): r"""Sends a HEAD request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ kwargs.setdefault('allow_redirects', False) return request('head', url, **kwargs)
特別注意,上面的允許重定向屬性預設為False
response = requests.head('https://www.baidu.com') print("status_code:",response.status_code) print("url:",response.url) print(response.headers)
(4)post:傳輸實體主體
函式原型:
def post(url, data=None, json=None, **kwargs): r"""Sends a POST request. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ return request('post', url, data=data, json=json, **kwargs)
簡單示例:
import requests data = { "name":"fate0729", "age":23 } response = requests.post("http://httpbin.org/post",data=data)
(5)put:傳送更新資源(全部更新)
put方法用於傳送更新資源,比如傳輸檔案,要求在請求報文的主體中包含檔案內容,然後儲存到請求URL指定位置,但是鑑於HTTP/1.1的PUT方法自身不帶驗證機制,任何人都可以上傳檔案存在安全性問題,因此一般的web網站不使用該方法,若配合web應用程式的驗證機制,或架構設計採用REST標準的同類網站,就可以開放使用put方法
函式原型:
def put(url, data=None, **kwargs): r"""Sends a PUT request. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ return request('put', url, data=data, **kwargs)
(6)pach:傳送更新資源(區域性更新)
pach和put的區別,我在度娘上看到了下面的一段話,覺得很明瞭:
假設我們有一個UserInfo,裡面有userId, userName, userGender等10個欄位。可你的編輯功能因為需求,在某個特別的頁面裡只能修改userName,這時候的更新怎麼做?人們通常(為省事)把一個包
含了修改後userName的完整userInfo而put雖然也是更新資源,但要求前端提供的一定是一個完整的資源物件,理論上說,如果你用了put,但卻沒有提供完整的UserInfo,那麼缺了的那些欄位應該被清空物件
傳給後端,做完整更新。但仔細想想,這種做法感覺有點二,而且真心浪費頻寬(純技術上講,你不關心頻寬那是你土豪)。於是patch誕生,只傳一個userName到指定資源去,表示該請求是一個區域性更新,後
端僅更新接收到的欄位。
函式原型:
def patch(url, data=None, **kwargs): r"""Sends a PATCH request. :param url: URL for the new :class:`Request` object. :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`. :param json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ return request('patch', url, data=data, **kwargs)
(7)delete:刪除指定的資源
delete方法與put方法相反,與put方法一樣,鑑於HTTP/1.1的PUT方法自身不帶驗證機制,一般的網站不使用delete方法
函式原型:
def delete(url, **kwargs): r"""Sends a DELETE request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response <Response>` object :rtype: requests.Response """ return request('delete', url, **kwargs)
(8)request函式
對該函式引數的理解無比重要,其實也很簡單,都是大家熟悉的
函式原型:
def request(method, url, **kwargs): with sessions.Session() as session: return session.request(method=method, url=url, **kwargs)
**kwargs 引數說明:
- method :request物件的方法(POST)
- url :request物件的URL
- params :可選的,要在查詢字串中傳送的字典或位元組request
- data :可選的,字典或元祖列表以表單編碼,位元組或類似檔案的物件在主體中傳送[(key,value)]
- json :可選的,一個json可序列化的python物件,在主體中傳送request
- headers :可選的,用於編寫http頭資訊
- cookies :可選,用dict或cookieJar物件傳送Cookies
- file :可選,用於多部分編碼上傳的字典,可以是多元祖,其中是定義給定檔案的內容型別的字串,以及包含問檔案新增的額外標頭檔案的類字典物件
- auth :可選,身份驗證元祖,自定義http身份驗證
- timeout :可選,傳送等待請求資料的超時時間(float/tuple),設定為元祖即為練級connect和read讀取超時,如果設定為None即為永久等待
- allow_redirects :布林值,可選,啟用或禁用GET,OPTIONS,POST,PUT,PATCH,DELETE,HEAD重定向,預設為true
- proxies :可選,字典對映協議到代理的URL
- verify :可選,可以是布林值,可以指定驗證伺服器的TLS證書路徑,預設為true
- stream :可選,如果是False,響應內容將立即下載
- cert :可選,如果是string,則為ssl客戶端證書檔案路徑,如果是元祖則('cert','key')指定證書和金鑰
下面將通過模擬登入示例別展示requests模組的部分高階用法
4. 模擬登入示例
以模擬登入github官網為例,登入網址: https://github.com/login
(1)首先使用錯誤的賬號和密碼登入,分析請求頭和引數,按F2可以找到與登入對應的POST方法
特別注意,請求網址為https://github.com/session,而不是 https://github.com/login ,小心入坑,檢視引數:
解析來進行編碼:
(2)設定請求頭:
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0', 'Connection':'keep-alive', 'Host':'github.com'}
(3)設定cookies,模擬登入github網必須使用cookies,否則將登入失敗
cookies = response.cookies
(4)獲取請求引數
html = response.content.decode('utf-8') soup = bs(html,'lxml') login_data = {} for item in soup.select('input[name]'): if item.get("name"): login_data[item.get("name")] = item.get("value")
(5)修改請求引數,參入登入名和密碼
login_data["login"] = 你自己的登入名 login_data["password"] = 你自己的密碼
(6)設定等待超時:防止一直等待
timeout=3
(7)萬事具備,只欠測試
import requests from bs4 import BeautifulSoup as bs from requests.exceptions import ReadTimeout,ConnectionError,RequestException url = 'https://github.com/session' headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0', 'Connection':'keep-alive', 'Host':'github.com'} response = requests.request('get',url,headers = headers) # 這裡可以替換為 response = requests.get(url,headers = headers) print('起始url:',response.url) html = response.content.decode('utf-8') soup = bs(html,'lxml') login_data = {} for item in soup.select('input[name]'): if item.get("name"): login_data[item.get("name")] = item.get("value") login_data["login"] = "------------" login_data["password"] = "--------" print('data:',login_data) try: response = requests.request('post',url,data=login_data, headers = headers,cookies = response.cookies,timeout=3) # 上面程式碼可以替換為 #response = requests.post(url,data=login_data, #headers = headers,cookies = response.cookies,timeout=3) except Timeout: print("timeout") except ConnectionError: print("connection Error") except RequestException: print("error") else: print("status_code:",response.status_code) print("登陸後url:",response.url)
輸出:
從結果可以看出起始URL和登入後URL已不同,說明模擬登入成功,github的模擬登入很簡單,大家可以試試複雜點的,比如知乎網的模擬登入
(8)大多數網站都會檢測某一段事件某個IP的訪問次數,如果訪問次數過多,它會禁止你的訪問,這個時候需要設定代理伺服器來爬取資料。
proxies= { "http":"http://127.0.0.1:9999", "https":"http://127.0.0.1:8888" } response= requests.request('get',url, proxies=proxies)
如果代理需要設定賬戶名和密碼,只需要將字典更改為如下:
proxies = { "http":"http://user:[email protected]:9999" }
如果你的代理是通過sokces這種方式則需要pip install "requests[socks]"
proxies= { "http":"socks5://127.0.0.1:9999", "https":"sockes5://127.0.0.1:8888" }
(9) 使用Session 修改上面的程式碼實現同樣的功能
從原始碼上我們知道request內部呼叫的是Session物件的request函式
with sessions.Session() as session: return session.request(method=method, url=url, **kwargs)
每次都使用了新的Session物件,其實我們可以一直以一個Session物件維持會話,當一個會話物件進行請求時,已經獲取了當前的cookie,這各可以從原始碼看到
if cookiejar is None: cookiejar = RequestsCookieJar()
修改上面的程式碼:
session = requests.Session() response = session.request('get',url,headers = headers) ................ ............. ........ response = session('post',url,data=login_data, headers = headers,timeout=3) .............. >>>> 效果和之前一樣
5. SSL證書驗證
requests提供了證書驗證的功能,當傳送http請求時,它會檢查SSL證書,可以通過verify引數來控制是否檢查此證書,預設為True會自動驗證,測試https://www.12306.cn
import requests r=requests.get('https://www.12306.cn') print(r.status_code)
>>> raiser SSLError
將verify引數設定為flase:
import requests r=requests.get('https://www.12306.cn',verify=False) print(r.status_code) #能正常返回200,但有個警告資訊 C:\virtualenv-36\.venv\lib\site-packages\urllib3\connectionpool.py:857 : InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning)
我們如果有證書的話,可以cert引數指定證書路徑,如果是單個檔案包含金鑰和證書,也可以設定兩個檔案路徑的元祖分別指向證書和金鑰
import requests response = requests.get('https://www.12306.cn', cert= ('/path/server.crt', '/path/key')) print(response.status_code) #注意,如果是本地私有證書的key必須是解密狀態,加密狀態的key是不支援的
6. 身份認證
如果訪問的網站需要身份認證的話,可以使用requests自帶的身份認證功能
其中包括四個類:
class requests.auth.AuthBase:所有auth實現派生自的基類
class requests.auth.HTTPBasicAuth(username, password):將HTTP基本認證附加到給定的請求物件
class requests.auth.HTTPProxyAuth(username, password):將HTTP代理身份驗證附加到給定的請求物件
class requests.auth.HTTPDigestAuth(username, password):將HTTP摘要式身份驗證附加到給定的請求物件
(1)基本認證
import requests from requests.auth import HTTPBasicAuth url='http://192.168.146.140/admin/' s=requests.Session() #建立密碼認證物件 auth=HTTPBasicAuth('admin','123') #附加認證資訊 response=s.get(url,auth=auth) print(response.text)
(2)代理身份認證
import requests from requests.auth import HTTPProxyAuth proauth=HTTPProxyAuth(username='admin',password='123') proxies={ 'http':'10.0.0.10:3324' } #建立session物件 s=requests.Session() #新增代理身份驗證 s.trust_env=proauth #新增代理 s.proxies=proxies response=s.get('https://www.facebook.com') print(response.status_code)
7. 異常處理
上圖來自官網: http://www.python-requests.org/en/master/api/#exceptions
從原始碼我可以看出各異常類的繼承關係:
RequestException繼承IOError HTTPError,ConnectionError,Timeout繼承RequestionException ProxyError,SSLError繼承ConnectionError ReadTimeout繼承Timeout異常
之前的模擬登入中已經列出了異常的使用方法,大家可以自己分別測試各異常出現的情況
except Timeout: print("timeout") except ConnectionError: print("connection Error") except RequestException: print("error")
補充:requests庫涉及到http協議的方方面面,要想了解的更全面更詳細,建議大家多閱讀原始碼,再經過demo進行測試,這樣的話會有更直接的體會。