第07讲:入门首选,Requests 库的基本使用
上一課時我們了解了一些學習爬蟲所需要的基本知識。從本課時開始,我們正式步入Python 爬蟲的大門。
學習爬蟲,最基礎的便是模擬瀏覽器向服務器發出請求,那么我們需要從什么地方做起呢?請求需要我們自己來構造嗎?需要關心請求這個數據結構的實現嗎?需要了解 HTTP、TCP、IP 層的網絡傳輸通信嗎?需要知道服務器的響應和應答原理嗎?
可能你無從下手,不過不用擔心,Python 的強大之處就是提供了功能齊全的類庫來幫助我們完成這些請求。利用 Python 現有的庫我們可以非常方便地實現網絡請求的模擬,常見的庫有 urllib、requests 等。
拿 requests 這個庫來說,有了它,我們只需要關心請求的鏈接是什么,需要傳的參數是什么,以及如何設置可選的參數就好了,不用深入到底層去了解它到底是怎樣傳輸和通信的。有了它,兩行代碼就可以完成一個請求和響應的處理過程,非常方便地得到網頁內容。
接下來,就讓我們用 Python 的 requests 庫開始我們的爬蟲之旅吧。
1.安裝
首先,requests 庫是 Python 的一個第三方庫,不是自帶的。所以我們需要額外安裝。
在這之前需要你先安裝好 Python3 環境,如 Python 3.6 版本,如若沒有安裝可以參考:https://cuiqingcai.com/5059.html。
安裝好 Python3 之后,我們使用 pip3 即可輕松地安裝好 requests 庫:
pip3 install requests更詳細的安裝方式可以參考:https://cuiqingcai.com/5132.html。
安裝完成之后,我們就可以開始我們的網絡爬蟲之旅了。
2.實例引入
用 Python 寫爬蟲的第一步就是模擬發起一個請求,把網頁的源代碼獲取下來。
當我們在瀏覽器中輸入一個 URL 并回車,實際上就是讓瀏覽器幫我們發起一個 GET 類型的 HTTP 請求,瀏覽器得到源代碼后,把它渲染出來就可以看到網頁內容了。
那如果我們想用 requests 來獲取源代碼,應該怎么辦呢?很簡單,requests 這個庫提供了一個 get 方法,我們調用這個方法,并傳入對應的 URL 就能得到網頁的源代碼。
比如這里有一個示例網站:https://static1.scrape.cuiqingcai.com/,其內容如下:
這個網站展示了一些電影數據,如果我們想要把這個網頁里面的數據爬下來,比如獲取各個電影的名稱、上映時間等信息,然后把它存下來的話,該怎么做呢?
第一步當然就是獲取它的網頁源代碼了。
我們可以用 requests 這個庫輕松地完成這個過程,代碼的寫法是這樣的:
import requests r = requests.get('https://static1.scrape.cuiqingcai.com/') print(r.text)運行結果如下:
<html lang="en"> <head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/static/img/favicon.ico"><title>Scrape | Movie</title><link href="/static/css/app.css" type="text/css" rel="stylesheet"><link href="/static/css/index.css" type="text/css" rel="stylesheet"> </head> <body> <div id="app"> ... <div data-v-7f856186="" id="index"><div data-v-7f856186="" class="el-row"><div data-v-7f856186="" class="el-col el-col-18 el-col-offset-3"><div data-v-7f856186="" class="el-card item m-t is-hover-shadow"><div class="el-card__body"><div data-v-7f856186="" class="el-row"><div data-v-7f856186="" class="el-col el-col-24 el-col-xs-8 el-col-sm-6 el-col-md-4"><a data-v-7f856186=""href="/detail/1"class=""><imgdata-v-7f856186="" src="https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c"class="cover"></a></div><div data-v-7f856186="" class="p-h el-col el-col-24 el-col-xs-9 el-col-sm-13 el-col-md-16"><a data-v-7f856186="" href="/detail/1" class=""><h2 data-v-7f856186="" class="m-b-sm">肖申克的救贖 - The Shawshank Redemption</h2></a><div data-v-7f856186="" class="categories"><button data-v-7f856186="" type="button"class="el-button category el-button--primary el-button--mini"><span>劇情</span></button><button data-v-7f856186="" type="button"class="el-button category el-button--primary el-button--mini"><span>犯罪</span></button></div><div data-v-7f856186="" class="m-v-sm info"><span data-v-7f856186="">美國</span><span data-v-7f856186=""> / </span><span data-v-7f856186="">142 分鐘</span></div><div data-v-7f856186="" class="m-v-sm info"><span data-v-7f856186="">1994-09-10 上映</span></div></div></div></div></div></div></div>... </div> </div> </body>由于網頁內容比較多,這里省略了大部分內容。
不過看運行結果,我們已經成功獲取網頁的 HTML 源代碼,里面包含了電影的標題、類型、上映時間,等等。把網頁源代碼獲取下來之后,下一步我們把想要的數據提取出來,數據的爬取就完成了。
這個實例的目的是讓你體會一下 requests 這個庫能幫我們實現什么功能。我們僅僅用 requests 的 get 方法就成功發起了一個 GET 請求,把網頁源代碼獲取下來了,是不是很方便呢?
3.請求
HTTP 中最常見的請求之一就是 GET 請求,下面我們來詳細了解利用 requests 庫構建 GET 請求的方法。
3.1 GET 請求
我們換一個示例網站,其 URL 為 http://httpbin.org/get,如果客戶端發起的是 GET 請求的話,該網站會判斷并返回相應的請求信息,包括 Headers、IP 等。
我們還是用相同的方法來發起一個 GET 請求,代碼如下:
import requests r = requests.get('http://httpbin.org/get') print(r.text)運行結果如下:
{"args": {}, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Host": "httpbin.org", "User-Agent": "python-requests/2.10.0" }, "origin": "122.4.215.33", "url": "http://httpbin.org/get" }可以發現,我們成功發起了 GET 請求,也通過這個網站的返回結果得到了請求所攜帶的信息,包括 Headers、URL、IP,等等。
對于 GET 請求,我們知道 URL 后面是可以跟上一些參數的,如果我們現在想添加兩個參數,其中 name 是 germey,age 是 25,URL 就可以寫成如下內容:
http://httpbin.org/get?name=germey&age=25要構造這個請求鏈接,是不是要直接寫成這樣呢?
r = requests.get('http://httpbin.org/get?name=germey&age=25')這樣也可以,但如果這些參數還需要我們手動拼接,未免有點不人性化。
一般情況下,這種信息我們利用 params 這個參數就可以直接傳遞了,示例如下:
import requests data = { 'name': 'germey', 'age': 25 } r = requests.get('http://httpbin.org/get', params=data) print(r.text)運行結果如下:
{ "args": { "age": "25","name": "germey" }, "headers": { "Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Host": "httpbin.org", "User-Agent": "python-requests/2.10.0" }, "origin": "122.4.215.33", "url": "http://httpbin.org/get?age=22&name=germey" }在這里我們把 URL 參數通過字典的形式傳給 get 方法的 params 參數,通過返回信息我們可以判斷,請求的鏈接自動被構造成了:http://httpbin.org/get?age=22&name=germey,這樣我們就不用再去自己構造 URL 了,非常方便。
另外,網頁的返回類型實際上是 str 類型,但是它很特殊,是 JSON 格式的。所以,如果想直接解析返回結果,得到一個 JSON 格式的數據的話,可以直接調用 json 方法。
示例如下:
import requests r = requests.get('http://httpbin.org/get') print(type(r.text)) print(r.json()) print(type(r.json()))運行結果如下:
<class'str'> {'headers': {'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.10.0'}, 'url': 'http://httpbin.org/get', 'args': {}, 'origin': '182.33.248.131'} <class 'dict'>可以發現,調用 json 方法,就可以將返回結果是 JSON 格式的字符串轉化為字典。
但需要注意的是,如果返回結果不是 JSON 格式,便會出現解析錯誤,拋出 json.decoder.JSONDecodeError 異常。
3.2 抓取網頁
上面的請求鏈接返回的是 JSON 形式的字符串,那么如果請求普通的網頁,則肯定能獲得相應的內容了。下面以本課時最初的實例頁面為例,我們再加上一點提取信息的邏輯,將代碼完善成如下的樣子:
import requests import rer = requests.get('https://static1.scrape.cuiqingcai.com/') pattern = re.compile('<h2.*?>(.*?)</h2>', re.S) titles = re.findall(pattern, r.text) print(titles)在這個例子中我們用到了最基礎的正則表達式來匹配出所有的標題。關于正則表達式的相關內容,我們會在下一課時詳細介紹,這里作為實例來配合講解。
運行結果如下:
['肖申克的救贖 - The Shawshank Redemption', '霸王別姬 - Farewell My Concubine', '泰坦尼克號 - Titanic', '羅馬假日 - Roman Holiday', '這個殺手不太冷 - Léon', '魂斷藍橋 - Waterloo Bridge', '唐伯虎點秋香 - Flirting Scholar', '喜劇之王 - The King of Comedy', '楚門的世界 - The Truman Show', '活著 - To Live']我們發現,這里成功提取出了所有的電影標題。一個最基本的抓取和提取流程就完成了。
3.3 抓取二進制數據
在上面的例子中,我們抓取的是網站的一個頁面,實際上它返回的是一個 HTML 文檔。如果想抓取圖片、音頻、視頻等文件,應該怎么辦呢?
圖片、音頻、視頻這些文件本質上都是由二進制碼組成的,由于有特定的保存格式和對應的解析方式,我們才可以看到這些形形色色的多媒體。所以,想要抓取它們,就要拿到它們的二進制數據。
下面以 GitHub 的站點圖標為例來看一下:
import requestsr = requests.get('https://github.com/favicon.ico') print(r.text) print(r.content)這里抓取的內容是站點圖標,也就是在瀏覽器每一個標簽上顯示的小圖標,如圖所示:
這里打印了 Response 對象的兩個屬性,一個是 text,另一個是 content。
運行結果如圖所示,其中前兩行是 r.text 的結果,最后一行是 r.content 的結果。
可以注意到,前者出現了亂碼,后者結果前帶有一個 b,這代表是 bytes 類型的數據。
由于圖片是二進制數據,所以前者在打印時轉化為 str 類型,也就是圖片直接轉化為字符串,這當然會出現亂碼。
上面返回的結果我們并不能看懂,它實際上是圖片的二進制數據,沒關系,我們將剛才提取到的信息保存下來就好了,代碼如下:
import requestsr = requests.get('https://github.com/favicon.ico') with open('favicon.ico', 'wb') as f:f.write(r.content)這里用了 open 方法,它的第一個參數是文件名稱,第二個參數代表以二進制的形式打開,可以向文件里寫入二進制數據。
運行結束之后,可以發現在文件夾中出現了名為 favicon.ico 的圖標,如圖所示。
這樣,我們就把二進制數據成功保存成一張圖片了,這個小圖標就被我們成功爬取下來了。
同樣地,音頻和視頻文件我們也可以用這種方法獲取。
3.4 添加 headers
我們知道,在發起一個 HTTP 請求的時候,會有一個請求頭 Request Headers,那么這個怎么來設置呢?
很簡單,我們使用 headers 參數就可以完成了。
在剛才的實例中,實際上我們是沒有設置 Request Headers 信息的,如果不設置,某些網站會發現這不是一個正常的瀏覽器發起的請求,網站可能會返回異常的結果,導致網頁抓取失敗。
要添加 Headers 信息,比如我們這里想添加一個 User-Agent 字段,我們可以這么來寫:
import requestsheaders = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' } r = requests.get('https://static1.scrape.cuiqingcai.com/', headers=headers) print(r.text)當然,我們可以在 headers 這個參數中任意添加其他的字段信息。
3.5 POST 請求
前面我們了解了最基本的 GET 請求,另外一種比較常見的請求方式是 POST。使用 requests 實現 POST 請求同樣非常簡單,示例如下:
import requestsdata = {'name': 'germey', 'age': '25'} r = requests.post("http://httpbin.org/post", data=data) print(r.text)這里還是請求 http://httpbin.org/post,該網站可以判斷如果請求是 POST 方式,就把相關請求信息返回。
運行結果如下:
{"args": {}, "data": "", "files": {}, "form": {"age": "25", "name": "germey"}, "headers": {"Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "18", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "python-requests/2.22.0", "X-Amzn-Trace-Id": "Root=1-5e5bdc26-b40d7e9862e3715f689cb5e6"}, "json": null, "origin": "167.220.232.237", "url": "http://httpbin.org/post" }可以發現,我們成功獲得了返回結果,其中 form 部分就是提交的數據,這就證明 POST 請求成功發送了。
3.6 響應
發送請求后,得到的自然就是響應,即 Response。
在上面的實例中,我們使用 text 和 content 獲取了響應的內容。此外,還有很多屬性和方法可以用來獲取其他信息,比如狀態碼、響應頭、Cookies 等。示例如下:
import requestsr = requests.get('https://static1.scrape.cuiqingcai.com/') print(type(r.status_code), r.status_code) print(type(r.headers), r.headers) print(type(r.cookies), r.cookies) print(type(r.url), r.url) print(type(r.history), r.history)這里分別打印輸出 status_code 屬性得到狀態碼,輸出 headers 屬性得到響應頭,輸出 cookies 屬性得到 Cookies,輸出 url 屬性得到 URL,輸出 history 屬性得到請求歷史。
運行結果如下:
<class 'int'> 200 <class 'requests.structures.CaseInsensitiveDict'> {'Server': 'nginx/1.17.8', 'Date': 'Sun, 01 Mar 2020 13:31:54 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'X-Frame-Options': 'SAMEORIGIN', 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains', 'Content-Encoding': 'gzip'} <class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[]> <class 'str'> https://static1.scrape.cuiqingcai.com/ <class 'list'> []可以看到,headers 和 cookies 這兩個屬性得到的結果分別是 CaseInsensitiveDict 和 RequestsCookieJar 類型。
在第一課時我們知道,狀態碼是用來表示響應狀態的,比如返回 200 代表我們得到的響應是沒問題的,上面的例子正好輸出的結果也是 200,所以我們可以通過判斷 Response 的狀態碼來確認是否爬取成功。
requests 還提供了一個內置的狀態碼查詢對象 requests.codes,用法示例如下:
import requestsr = requests.get('https://static1.scrape.cuiqingcai.com/') exit() if not r.status_code == requests.codes.ok else print('Request Successfully')這里通過比較返回碼和內置的成功的返回碼,來保證請求得到了正常響應,輸出成功請求的消息,否則程序終止,這里我們用 requests.codes.ok 得到的是成功的狀態碼 200。
這樣的話,我們就不用再在程序里面寫狀態碼對應的數字了,用字符串表示狀態碼會顯得更加直觀。
當然,肯定不能只有 ok 這個條件碼。
下面列出了返回碼和相應的查詢條件:
# 信息性狀態碼 100: ('continue',), 101: ('switching_protocols',), 102: ('processing',), 103: ('checkpoint',), 122: ('uri_too_long', 'request_uri_too_long'), # 成功狀態碼 200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '?'), 201: ('created',), 202: ('accepted',), 203: ('non_authoritative_info', 'non_authoritative_information'), 204: ('no_content',), 205: ('reset_content', 'reset'), 206: ('partial_content', 'partial'), 207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), 208: ('already_reported',), 226: ('im_used',), # 重定向狀態碼 300: ('multiple_choices',), 301: ('moved_permanently', 'moved', '\\o-'), 302: ('found',), 303: ('see_other', 'other'), 304: ('not_modified',), 305: ('use_proxy',), 306: ('switch_proxy',), 307: ('temporary_redirect', 'temporary_moved', 'temporary'), 308: ('permanent_redirect', 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 # 客戶端錯誤狀態碼 400: ('bad_request', 'bad'), 401: ('unauthorized',), 402: ('payment_required', 'payment'), 403: ('forbidden',), 404: ('not_found', '-o-'), 405: ('method_not_allowed', 'not_allowed'), 406: ('not_acceptable',), 407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), 408: ('request_timeout', 'timeout'), 409: ('conflict',), 410: ('gone',), 411: ('length_required',), 412: ('precondition_failed', 'precondition'), 413: ('request_entity_too_large',), 414: ('request_uri_too_large',), 415: ('unsupported_media_type', 'unsupported_media', 'media_type'), 416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), 417: ('expectation_failed',), 418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), 421: ('misdirected_request',), 422: ('unprocessable_entity', 'unprocessable'), 423: ('locked',), 424: ('failed_dependency', 'dependency'), 425: ('unordered_collection', 'unordered'), 426: ('upgrade_required', 'upgrade'), 428: ('precondition_required', 'precondition'), 429: ('too_many_requests', 'too_many'), 431: ('header_fields_too_large', 'fields_too_large'), 444: ('no_response', 'none'), 449: ('retry_with', 'retry'), 450: ('blocked_by_windows_parental_controls', 'parental_controls'), 451: ('unavailable_for_legal_reasons', 'legal_reasons'), 499: ('client_closed_request',), # 服務端錯誤狀態碼 500: ('internal_server_error', 'server_error', '/o\\', '?'), 501: ('not_implemented',), 502: ('bad_gateway',), 503: ('service_unavailable', 'unavailable'), 504: ('gateway_timeout',), 505: ('http_version_not_supported', 'http_version'), 506: ('variant_also_negotiates',), 507: ('insufficient_storage',), 509: ('bandwidth_limit_exceeded', 'bandwidth'), 510: ('not_extended',), 511: ('network_authentication_required', 'network_auth', 'network_authentication')比如,如果想判斷結果是不是 404 狀態,可以用 requests.codes.not_found 來比對。
4.高級用法
剛才,我們了解了 requests 的基本用法,如基本的 GET、POST 請求以及 Response 對象。當然 requests 能做到的不僅這些,它幾乎可以幫我們完成 HTTP 的所有操作。
下面我們再來了解下 requests 的一些高級用法,如文件上傳、Cookies 設置、代理設置等。
4.1文件上傳
我們知道 requests 可以模擬提交一些數據。假如有的網站需要上傳文件,我們也可以用它來實現,示例如下:
import requestsfiles = {'file': open('favicon.ico', 'rb')} r = requests.post('http://httpbin.org/post', files=files) print(r.text)在上一課時中我們保存了一個文件 favicon.ico,這次用它來模擬文件上傳的過程。需要注意的是,favicon.ico 需要和當前腳本在同一目錄下。如果有其他文件,當然也可以使用其他文件來上傳,更改下代碼即可。
運行結果如下:
{"args": {}, "data": "","files": {"file":"data:application/octet-stream;base64,AAAAAA...="},"form": {},"headers": {"Accept":"*/*","Accept-Encoding":"gzip, deflate","Content-Length":"6665","Content-Type":"multipart/form-data; boundary=809f80b1a2974132b133ade1a8e8e058","Host":"httpbin.org","User-Agent":"python-requests/2.10.0"},"json": null,"origin":"60.207.237.16","url":"http://httpbin.org/post"}以上省略部分內容,這個網站會返回響應,里面包含 files 這個字段,而 form 字段是空的,這證明文件上傳部分會單獨有一個 files 字段來標識。
4.2 Cookies
我們如果想用 requests 獲取和設置 Cookies 也非常方便,只需一步即可完成。
我們先用一個實例看一下獲取 Cookies 的過程:
import requestsr = requests.get('http://www.baidu.com') print(r.cookies) for key, value in r.cookies.items():print(key + '=' + value)運行結果如下:
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]> BDORZ=27315這里我們首先調用 cookies 屬性即可成功得到 Cookies,可以發現它是 RequestCookieJar 類型。然后用 items 方法將其轉化為元組組成的列表,遍歷輸出每一個 Cookie 的名稱和值,實現 Cookie 的遍歷解析。
當然,我們也可以直接用 Cookie 來維持登錄狀態,下面我們以 GitHub 為例來說明一下,首先我們登錄 GitHub,然后將 Headers 中的 Cookie 內容復制下來,如圖所示:
這里可以替換成你自己的 Cookie,將其設置到 Headers 里面,然后發送請求,示例如下:
我們發現,結果中包含了登錄后才能顯示的結果,如圖所示:
可以看到這里包含了我的 GitHub 用戶名信息,你如果嘗試同樣可以得到你的用戶信息。
得到這樣類似的結果,說明我們用 Cookies 成功模擬了登錄狀態,這樣我們就能爬取登錄之后才能看到的頁面了。
當然,我們也可以通過 cookies 參數來設置 Cookies 的信息,這里我們可以構造一個 RequestsCookieJar 對象,然后把剛才復制的 Cookie 處理下并賦值,示例如下:
import requestscookies = '_octo=GH1.1.1849343058.1576602081; _ga=GA1.2.90460451.1576602111; __Host-user_session_same_site=nbDv62kHNjp4N5KyQNYZ208waeqsmNgxFnFC88rnV7gTYQw_; _device_id=a7ca73be0e8f1a81d1e2ebb5349f9075; user_session=nbDv62kHNjp4N5KyQNYZ208waeqsmNgxFnFC88rnV7gTYQw_; logged_in=yes; dotcom_user=Germey; tz=Asia%2FShanghai; has_recent_activity=1; _gat=1; _gh_sess=your_session_info' jar = requests.cookies.RequestsCookieJar() headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36' } for cookie in cookies.split(';'):key, value = cookie.split('=', 1)jar.set(key, value) r = requests.get('https://github.com/', cookies=jar, headers=headers) print(r.text)這里我們首先新建一個 RequestCookieJar 對象,然后將復制下來的 cookies 利用 split 方法分割,接著利用 set 方法設置好每個 Cookie 的 key 和 value,最后通過調用 requests 的 get 方法并傳遞給 cookies 參數即可。
測試后,發現同樣可以正常登錄。
4.3 Session 維持
在 requests 中,如果直接利用 get 或 post 等方法的確可以做到模擬網頁的請求,但是這實際上是相當于不同的 Session,相當于你用兩個瀏覽器打開了不同的頁面。
設想這樣一個場景,第一個請求利用 post 方法登錄了某個網站,第二次想獲取成功登錄后的自己的個人信息,你又用了一次 get 方法去請求個人信息頁面。實際上,這相當于打開了兩個瀏覽器,是兩個完全不相關的 Session,能成功獲取個人信息嗎?當然不能。
有人會問,我在兩次請求時設置一樣的 Cookies 不就行了?可以,但這樣做起來很煩瑣,我們有更簡單的解決方法。
解決這個問題的主要方法就是維持同一個 Session,相當于打開一個新的瀏覽器選項卡而不是新開一個瀏覽器。但我又不想每次設置 Cookies,那該怎么辦呢?這時候就有了新的利器 ——Session 對象。
利用它,我們可以方便地維護一個 Session,而且不用擔心 Cookies 的問題,它會幫我們自動處理好。示例如下:
import requestsrequests.get('http://httpbin.org/cookies/set/number/123456789') r = requests.get('http://httpbin.org/cookies') print(r.text)這里我們請求了一個測試網址 http://httpbin.org/cookies/set/number/123456789。請求這個網址時,可以設置一個 cookie,名稱叫作 number,內容是 123456789,隨后又請求了 http://httpbin.org/cookies,此網址可以獲取當前的 Cookies。
這樣能成功獲取到設置的 Cookies 嗎?試試看。
運行結果如下:
{"cookies": {} }這并不行。我們再用 Session 試試看:
import requestss = requests.Session() s.get('http://httpbin.org/cookies/set/number/123456789') r = s.get('http://httpbin.org/cookies') print(r.text)再看下運行結果:
{"cookies": {"number": "123456789"} }成功獲取!這下能體會到同一個Session和不同Session的區別了吧!
所以,利用 Session,可以做到模擬同一個 Session 而不用擔心 Cookies 的問題。它通常用于模擬登錄成功之后再進行下一步的操作。
4.4 SSL 證書驗證
現在很多網站都要求使用 HTTPS 協議,但是有些網站可能并沒有設置好 HTTPS 證書,或者網站的 HTTPS 證書不被 CA 機構認可,這時候,這些網站可能就會出現 SSL 證書錯誤的提示。
比如這個示例網站:https://static2.scrape.cuiqingcai.com/。
如果我們用 Chrome 瀏覽器打開這個 URL,則會提示「您的連接不是私密連接」這樣的錯誤,如圖所示:
我們可以在瀏覽器中通過一些設置來忽略證書的驗證。
但是如果我們想用 requests 來請求這類網站,會遇到什么問題呢?我們用代碼來試一下:
import requestsresponse = requests.get('https://static2.scrape.cuiqingcai.com/') print(response.status_code)運行結果如下:
requests.exceptions.SSLError: HTTPSConnectionPool(host='static2.scrape.cuiqingcai.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')])")))可以看到,這里直接拋出了 SSLError 錯誤,原因就是因為我們請求的 URL 的證書是無效的。
那如果我們一定要爬取這個網站怎么辦呢?我們可以使用 verify 參數控制是否驗證證書,如果將其設置為 False,在請求時就不會再驗證證書是否有效。如果不加 verify 參數的話,默認值是 True,會自動驗證。
我們改寫代碼如下:
import requestsresponse = requests.get('https://static2.scrape.cuiqingcai.com/', verify=False) print(response.status_code)這樣就會打印出請求成功的狀態碼:
/usr/local/lib/python3.7/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-warningsInsecureRequestWarning) 200不過我們發現報了一個警告,它建議我們給它指定證書。我們可以通過設置忽略警告的方式來屏蔽這個警告:
import requests from requests.packages import urllib3urllib3.disable_warnings() response = requests.get('https://static2.scrape.cuiqingcai.com/', verify=False) print(response.status_code)或者通過捕獲警告到日志的方式忽略警告:
import logging import requests logging.captureWarnings(True) response = requests.get('https://static2.scrape.cuiqingcai.com/', verify=False) print(response.status_code)當然,我們也可以指定一個本地證書用作客戶端證書,這可以是單個文件(包含密鑰和證書)或一個包含兩個文件路徑的元組:
import requestsresponse = requests.get('https://static2.scrape.cuiqingcai.com/', cert=('/path/server.crt', '/path/server.key')) print(response.status_code)當然,上面的代碼是演示實例,我們需要有 crt 和 key 文件,并且指定它們的路徑。另外注意,本地私有證書的 key 必須是解密狀態,加密狀態的 key 是不支持的。
4.5 超時設置
在本機網絡狀況不好或者服務器網絡響應延遲甚至無響應時,我們可能會等待很久才能收到響應,甚至到最后收不到響應而報錯。為了防止服務器不能及時響應,應該設置一個超時時間,即超過了這個時間還沒有得到響應,那就報錯。這需要用到 timeout 參數。這個時間的計算是發出請求到服務器返回響應的時間。示例如下:
import requestsr = requests.get('https://httpbin.org/get', timeout=1) print(r.status_code)通過這樣的方式,我們可以將超時時間設置為 1 秒,如果 1 秒內沒有響應,那就拋出異常。
實際上,請求分為兩個階段,即連接(connect)和讀取(read)。
上面設置的 timeout 將用作連接和讀取這二者的 timeout 總和。
如果要分別指定,就可以傳入一個元組:
r = requests.get('https://httpbin.org/get', timeout=(5, 30))如果想永久等待,可以直接將 timeout 設置為 None,或者不設置直接留空,因為默認是 None。這樣的話,如果服務器還在運行,但是響應特別慢,那就慢慢等吧,它永遠不會返回超時錯誤的。其用法如下:
r = requests.get('https://httpbin.org/get', timeout=None)或直接不加參數:
r = requests.get('https://httpbin.org/get')4.6 身份認證
在訪問某些設置了身份認證的網站時,例如:https://static3.scrape.cuiqingcai.com/,我們可能會遇到這樣的認證窗口,如圖所示:
如果遇到了這種情況,那就是這個網站啟用了基本身份認證,英文叫作 HTTP Basic Access Authentication,它是一種用來允許網頁瀏覽器或其他客戶端程序在請求時提供用戶名和口令形式的身份憑證的一種登錄驗證方式。
如果遇到了這種情況,怎么用 reqeusts 來爬取呢,當然也有辦法。
我們可以使用 requests 自帶的身份認證功能,通過 auth 參數即可設置,示例如下:
import requests from requests.auth import HTTPBasicAuth r = requests.get('https://static3.scrape.cuiqingcai.com/', auth=HTTPBasicAuth('admin', 'admin')) print(r.status_code)這個示例網站的用戶名和密碼都是 admin,在這里我們可以直接設置。
如果用戶名和密碼正確的話,請求時會自動認證成功,返回 200 狀態碼;如果認證失敗,則返回 401 狀態碼。
當然,如果參數都傳一個 HTTPBasicAuth 類,就顯得有點煩瑣了,所以 requests 提供了一個更簡單的寫法,可以直接傳一個元組,它會默認使用 HTTPBasicAuth 這個類來認證。
所以上面的代碼可以直接簡寫如下:
import requestsr = requests.get('https://static3.scrape.cuiqingcai.com/', auth=('admin', 'admin')) print(r.status_code)此外,requests 還提供了其他認證方式,如 OAuth 認證,不過此時需要安裝 oauth 包,安裝命令如下:
pip3 install requests_oauthlib使用 OAuth1 認證的方法如下:
import requests from requests_oauthlib import OAuth1url = 'https://api.twitter.com/1.1/account/verify_credentials.json' auth = OAuth1('YOUR_APP_KEY', 'YOUR_APP_SECRET','USER_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET') requests.get(url, auth=auth)更多詳細的功能就可以參考 requests_oauthlib 的官方文檔:https://requests-oauthlib.readthedocs.org/,在此就不再贅述了。
4.7 代理設置
某些網站在測試的時候請求幾次,能正常獲取內容。但是對于大規模且頻繁的請求,網站可能會彈出驗證碼,或者跳轉到登錄認證頁面,更甚者可能會直接封禁客戶端的 IP,導致一定時間段內無法訪問。
為了防止這種情況發生,我們需要設置代理來解決這個問題,這就需要用到 proxies 參數。可以用這樣的方式設置:
import requestsproxies = {'http': 'http://10.10.10.10:1080','https': 'http://10.10.10.10:1080', } requests.get('https://httpbin.org/get', proxies=proxies)當然,直接運行這個實例或許行不通,因為這個代理可能是無效的,可以直接搜索尋找有效的代理并替換試驗一下。
若代理需要使用上文所述的身份認證,可以使用類似 http://user:password@host:port 這樣的語法來設置代理,示例如下:
import requestsproxies = {'https': 'http://user:password@10.10.10.10:1080/',} requests.get('https://httpbin.org/get', proxies=proxies)除了基本的 HTTP 代理外,requests 還支持 SOCKS 協議的代理。
首先,需要安裝 socks 這個庫:
pip3 install "requests[socks]"然后就可以使用 SOCKS 協議代理了,示例如下:
import requestsproxies = {'http': 'socks5://user:password@host:port','https': 'socks5://user:password@host:port' } requests.get('https://httpbin.org/get', proxies=proxies)5.Prepared Request
我們使用 requests 庫的 get 和 post 方法可以直接發送請求,但你有沒有想過,這個請求在 requests 內部是怎么實現的呢?
實際上,requests 在發送請求的時候在內部構造了一個 Request 對象,并給這個對象賦予了各種參數,包括 url、headers、data ,等等。然后直接把這個 Request 對象發送出去,請求成功后會再得到一個 Response 對象,再解析即可。
那么這個 Request 是什么類型呢?實際上它就是 Prepared Request。
我們深入一下,不用 get 方法,直接構造一個 Prepared Request 對象來試試,代碼如下:
from requests import Request, Sessionurl = 'http://httpbin.org/post' data = {'name': 'germey'} headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36' } s = Session() req = Request('POST', url, data=data, headers=headers) prepped = s.prepare_request(req) r = s.send(prepped) print(r.text)這里我們引入了 Request,然后用 url、data 和 headers 參數構造了一個 Request 對象,這時需要再調用 Session 的 prepare_request 方法將其轉換為一個 Prepared Request 對象,然后調用 send 方法發送,運行結果如下:
{"args": {}, "data": "", "files": {}, "form": {"name": "germey"}, "headers": {"Accept": "*/*", "Accept-Encoding": "gzip, deflate", "Content-Length": "11", "Content-Type": "application/x-www-form-urlencoded", "Host": "httpbin.org", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36", "X-Amzn-Trace-Id": "Root=1-5e5bd6a9-6513c838f35b06a0751606d8"}, "json": null, "origin": "167.220.232.237", "url": "http://httpbin.org/post" }可以看到,我們達到了同樣的 POST 請求效果。
有了 Request 這個對象,就可以將請求當作獨立的對象來看待,這樣在一些場景中我們可以直接操作這個 Request 對象,更靈活地實現請求的調度和各種操作。
更多的用法可以參考 requests 的官方文檔:http://docs.python-requests.org/。
本課時 requests 庫的基本用法就介紹到這里了。怎么樣?是不是找到一點爬蟲的感覺了?
總結
以上是生活随笔為你收集整理的第07讲:入门首选,Requests 库的基本使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第15讲:Selenium 爬取实战
- 下一篇: 第08讲:解析无所不能的正则表达式