Python Requests庫使用指南
本文為譯文,原文連結 python-requests-library-guide
本人部落格: 程式設計禪師
requests 庫是用來在Python中發出標準的HTTP請求。 它將請求背後的複雜性抽象成一個漂亮,簡單的API,以便你可以專注於與服務互動和在應用程式中使用資料。
在本文中,你將看到 requests
提供的一些有用的功能,以及如何針對你可能遇到的不同情況來自定義和優化這些功能。 你還將學習如何有效的使用 requests
,以及如何防止對外部服務的請求導致減慢應用程式的速度。
在本教程中,你將學習如何:
- 使用常見的HTTP方法傳送請求
- 定製你的請求頭和資料,使用查詢字串和訊息體
- 檢查你的請求和響應的資料
- 傳送帶身份驗證的請求
- 配置你的請求來避免阻塞或減慢你的應用程式
雖然我試圖包含儘可能多的資訊來理解本文中包含的功能和示例,但閱讀此文需要對HTTP有基礎的瞭解。
現在讓我們深入瞭解如何在你的應用程式中使用請求!
開始使用 requests
讓我們首先安裝 requests
庫。 為此,請執行以下命令:
pip install requests
如果你喜歡使用 Pipenv 管理Python包,你可以執行下面的命令:
pipenv install requests
一旦安裝了 requests
,你就可以在應用程式中使用它。像這樣匯入 requests
:
import requests
現在你已經都準備完成了,那麼是時候開始使用 requests
的旅程了。 你的第一個目標是學習如何發出GET請求。
GET 請求
HTTP方法 (如GET和POST)決定當發出HTTP請求時嘗試執行的操作。 除了GET和POST之外,還有其他一些常用的方法,你將在本教程的後面部分使用到。
最常見的HTTP方法之一是GET。 GET方法表示你正在嘗試從指定資源獲取或檢索資料。 要傳送GET請求,請呼叫 requests.get()
。
你可以通過下面方式來向GitHub的 Root REST API 發出GET請求:
>>> requests.get('https://api.github.com') <Response [200]>
恭喜! 你發出了你的第一個請求。 接下來讓我們更深入地瞭解該請求的響應。
響應
Response
是檢查請求結果的強有力的物件。 讓我們再次發出相同的請求,但這次將返回值儲存在一個變數中,以便你可以仔細檢視其屬性和方法:
>>> response = requests.get('https://api.github.com')
在此示例中,你捕獲了 get()
的返回值,該值是 Response
的例項,並將其儲存在名為 response
的變數中。 你現在可以使用 response
來檢視有關GET請求結果的全部資訊。
狀態碼
您可以從 Response
獲取的第一部分資訊是狀態碼。 狀態碼會展示你請求的狀態。
例如, 200 OK
狀態表示你的請求成功,而 404 NOT FOUND
狀態表示找不到你要查詢的資源。 還有許多 其它的狀態碼 ,可以為你提供關於你的請求所發生的具體情況。
通過訪問 .status_code
,你可以看到伺服器返回的狀態碼:
>>> response.status_code 200
.status_code
返回 200
意味著你的請求是成功的,並且伺服器返回你要請求的資料。
有時,你可能想要在程式碼中使用這些資訊來做判斷:
if response.status_code == 200: print('Success!') elif response.status_code == 404: print('Not Found.')
按照這個邏輯,如果伺服器返回 200
狀態碼,你的程式將列印 Success!
如果結果是 404
,你的程式將列印 Not Found.
。
requests
更進一步為你簡化了此過程。 如果在條件表示式中使用 Response
例項,則在狀態碼介於 200
和 400
之間時將被計算為為 True
,否則為 False
。
因此,你可以通過重寫 if
語句來簡化上一個示例:
if response: print('Success!') else: print('An error has occurred.')
技術細節: 因為 __ bool __()
是 Response
上的 過載方法 ,因此 真值測試 才成立。
這意味著重新定義了 Response
的預設行為,用來在確定物件的真值時考慮狀態碼。
請記住,此方法 不會驗證
狀態碼是否等於 200
。原因是 200
到 400
範圍內的其他狀態程式碼,例如 204 NO CONTENT
和 304 NOT MODIFIED
,就意義而言也被認為是成功的響應。
例如, 204
告訴你響應是成功的,但是下訊息體中沒有返回任何內容。
因此,通常如果你想知道請求是否成功時,請確保使用這方便的簡寫,然後在必要時根據狀態碼適當地處理響應。
假設你不想在 if
語句中檢查響應的狀態碼。 相反,如果請求不成功,你希望丟擲一個異常。 你可以使用 .raise_for_status()
執行此操作:
import requests from requests.exceptions import HTTPError for url in ['https://api.github.com', 'https://api.github.com/invalid']: try: response = requests.get(url) # If the response was successful, no Exception will be raised response.raise_for_status() except HTTPError as http_err: print(f'HTTP error occurred: {http_err}')# Python 3.6 except Exception as err: print(f'Other error occurred: {err}')# Python 3.6 else: print('Success!')
如果你呼叫 .raise_for_status()
,將針對某些狀態碼引發 HTTPError
異常。 如果狀態碼指示請求成功,則程式將繼續進行而不會引發該異常。
進一步閱讀:如果你不熟悉Python 3.6的 f-strings ,我建議你使用它們,因為它們是簡化格式化字串的好方法。
現在,你對於如何處理從伺服器返回的響應的狀態碼瞭解了許多。 但是,當你發出GET請求時,你很少只關心響應的狀態碼。 通常,你希望看到更多。 接下來,你將看到如何檢視伺服器在響應正文中返回的實際資料。
響應內容
GET
請求的響應通常在訊息體中具有一些有價值的資訊,稱為有效負載。 使用 Response
的屬性和方法,你可以以各種不同的格式檢視有效負載。
要以 位元組 格式檢視響應的內容,你可以使用 .content
:
>>> response = requests.get('https://api.github.com') >>> response.content b'{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}","commit_search_url":"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}","emails_url":"https://api.github.com/user/emails","emojis_url":"https://api.github.com/emojis","events_url":"https://api.github.com/events","feeds_url":"https://api.github.com/feeds","followers_url":"https://api.github.com/user/followers","following_url":"https://api.github.com/user/following{/target}","gists_url":"https://api.github.com/gists{/gist_id}","hub_url":"https://api.github.com/hub","issue_search_url":"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}","issues_url":"https://api.github.com/issues","keys_url":"https://api.github.com/user/keys","notifications_url":"https://api.github.com/notifications","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_url":"https://api.github.com/orgs/{org}","public_gists_url":"https://api.github.com/gists/public","rate_limit_url":"https://api.github.com/rate_limit","repository_url":"https://api.github.com/repos/{owner}/{repo}","repository_search_url":"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}","current_user_repositories_url":"https://api.github.com/user/repos{?type,page,per_page,sort}","starred_url":"https://api.github.com/user/starred{/owner}{/repo}","starred_gists_url":"https://api.github.com/gists/starred","team_url":"https://api.github.com/teams","user_url":"https://api.github.com/users/{user}","user_organizations_url":"https://api.github.com/user/orgs","user_repositories_url":"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}","user_search_url":"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"}'
雖然 .content
允許你訪問響應有效負載的原始位元組,但你通常希望使用 UTF-8 等字元編碼將它們轉換為 字串 。 當你訪問 .text
時, response
將為你執行此操作:
>>> response.text '{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}"...}"}'
因為對 bytes
解碼到 str
需要一個編碼格式,所以如果你沒有指定,請求將嘗試根據響應頭來猜測編碼格式。 你也可以在訪問 .text
之前通過 .encoding
來顯式設定編碼:
>>> response.encoding = 'utf-8' # Optional: requests infers this internally >>> response.text '{"current_user_url":"https://api.github.com/user","current_user_authorizations_html_url":"https://github.com/settings/connections/applications{/client_id}","authorizations_url":"https://api.github.com/authorizations","code_search_url":"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}"...}"}'
如果你看看響應,你會發現它實際上是序列化的 JSON
內容。 要獲取字典內容,你可以使用 .text
獲取 str
並使用 json.loads()
對其進行反序列化。 但是,完成此任務的更簡單方法是使用 .json()
:
>>> response.json() {'current_user_url': 'https://api.github.com/user', 'current_user_authorizations_html_url': 'https://github.com/settings/connections/applications{/client_id}'...}'}
.json()
返回值的型別是字典型別,因此你可以使用鍵值對的方式訪問物件中的值。
你可以使用狀態碼和訊息體做許多事情。 但是,如果你需要更多資訊,例如有關 response
本身的元資料,則需要檢視響應頭部。
響應頭部
響應頭部可以為你提供有用的資訊,例如響應有效負載的內容型別以及快取響應的時間限制。 要檢視這些頭部,請訪問 .headers
:
>>> response.headers {'Server': 'GitHub.com', 'Date': 'Mon, 10 Dec 2018 17:49:54 GMT', 'Content-Type': 'application/json; charset=utf-8',...}
.headers
返回類似字典的物件,允許你使用鍵來獲取頭部中的值。 例如,要檢視響應有效負載的內容型別,你可以訪問 Content-Type
:
>>> response.headers['Content-Type'] 'application/json; charset=utf-8'
但是,這個類似於字典的頭部物件有一些特別之處。 HTTP規範定義頭部不區分大小寫,這意味著我們可以訪問這些頭資訊而不必擔心它們的大小寫:
>>> response.headers['content-type'] 'application/json; charset=utf-8'
無論您使用 'content-type'
還是 'Content-Type'
,你都將獲得相同的值。
現在,你已經學習了有關 Response
的基礎知識。 你已經看到了它最有用的屬性和方法。 讓我們退後一步,看看自定義 GET
請求時你的響應如何變化。
查詢字串引數
自定義 GET
請求的一種常用方法是通過URL中的 查詢字串 引數傳遞值。 要使用 get()
執行此操作,請將資料傳遞給 params
。 例如,你可以使用GitHub的 Search API來查詢 requests
庫:
import requests # Search GitHub's repositories for requests response = requests.get( 'https://api.github.com/search/repositories', params={'q': 'requests+language:python'}, ) # Inspect some attributes of the `requests` repository json_response = response.json() repository = json_response['items'][0] print(f'Repository name: {repository["name"]}')# Python 3.6+ print(f'Repository description: {repository["description"]}')# Python 3.6+
通過將字典 {'q':'requests + language:python'}
傳遞給 .get()
的 params
引數,你可以修改從Search API返回的結果。
你可以像你剛才那樣以字典的形式或以元組列表形式將 params
傳遞給 get()
:
>>> requests.get( ...'https://api.github.com/search/repositories', ...params=[('q', 'requests+language:python')], ... ) <Response [200]>
你甚至可以傳 bytes
作為值:
>>> requests.get( ...'https://api.github.com/search/repositories', ...params=b'q=requests+language:python', ... ) <Response [200]>
查詢字串對於引數化GET請求很有用。 你還可以通過新增或修改傳送的請求的頭部來自定義你的請求。
請求頭
要自定義請求頭,你可以使用 headers
引數將HTTP頭部組成的字典傳遞給 get()
。 例如,你可以通過 Accept
中指定文字匹配媒體型別來更改以前的搜尋請求,以在結果中突出顯示匹配的搜尋字詞:
import requests response = requests.get( 'https://api.github.com/search/repositories', params={'q': 'requests+language:python'}, headers={'Accept': 'application/vnd.github.v3.text-match+json'}, ) # View the new `text-matches` array which provides information # about your search term within the results json_response = response.json() repository = json_response['items'][0] print(f'Text matches: {repository["text_matches"]}')
Accept
告訴伺服器你的應用程式可以處理哪些內容型別。 由於你希望突出顯示匹配的搜尋詞,所以使用的是 application / vnd.github.v3.text-match + json
,這是一個專有的GitHub的 Accept
標頭,其內容為特殊的JSON格式。
在你瞭解更多自定義請求的方法之前,讓我們通過探索其他HTTP方法來拓寬視野。
其他HTTP方法
除了 GET
之外,其他流行的HTTP方法包括 POST
,` PUT
, DELETE
, HEAD
, PATCH
和 OPTIONS
。 requests
為每個HTTP方法提供了一個方法,與 get()
`具有類似的結構:
>>> requests.post('https://httpbin.org/post', data={'key':'value'}) >>> requests.put('https://httpbin.org/put', data={'key':'value'}) >>> requests.delete('https://httpbin.org/delete') >>> requests.head('https://httpbin.org/get') >>> requests.patch('https://httpbin.org/patch', data={'key':'value'}) >>> requests.options('https://httpbin.org/get')
呼叫每個函式使用相應的HTTP方法向httpbin服務發出請求。 對於每種方法,你可以像以前一樣檢視其響應:
>>> response = requests.head('https://httpbin.org/get') >>> response.headers['Content-Type'] 'application/json' >>> response = requests.delete('https://httpbin.org/delete') >>> json_response = response.json() >>> json_response['args'] {}
每種方法的響應中都會返回頭部,響應正文,狀態碼等。 接下來,你將進一步瞭解 POST
,` PUT
和 PATCH
方法,並瞭解它們與其他請求型別的區別。
訊息體
根據HTTP規範, POST
,` PUT
和不太常見的 PATCH
請求通過訊息體而不是通過查詢字串引數傳遞它們的資料。 使用 requests
,你將有效負載傳遞給相應函式的 data
引數。
data
接收字典,元組列表,位元組或類檔案物件。 你需要將在請求正文中傳送的資料調整為與你互動的服務的特定格式。
例如,如果你的請求的內容型別是 application / x-www-form-urlencoded
,則可以將表單資料作為字典傳送:
>>> requests.post('https://httpbin.org/post', data={'key':'value'}) <Response [200]>
你還可以將相同的資料作為元組列表傳送:
>>> requests.post('https://httpbin.org/post', data=[('key', 'value')]) <Response [200]>
但是,如果需要傳送JSON資料,則可以使用 json
引數。 當你通過 json
傳遞JSON資料時, requests
將序列化你的資料併為你新增正確的 Content-Type
標頭。
httpbin.org 是 requests
作者 Kenneth Reitz 建立的一個很好的資源。 它是一種接收測試請求並響應有關請求資料的服務。 例如,你可以使用它來檢查基本的POST請求:
>>> response = requests.post('https://httpbin.org/post', json={'key':'value'}) >>> json_response = response.json() >>> json_response['data'] '{"key": "value"}' >>> json_response['headers']['Content-Type'] 'application/json'
你可以從響應中看到伺服器在你傳送請求時收到了請求資料和標頭。 requests
還以 PreparedRequest
的形式向你提供此資訊。
檢查你的請求
當你發出請求時, requests
庫會在將請求實際傳送到目標伺服器之前準備該請求。 請求準備包括像驗證頭資訊和序列化JSON內容等。
你可以通過訪問 .request
來檢視 PreparedRequest
:
>>> response = requests.post('https://httpbin.org/post', json={'key':'value'}) >>> response.request.headers['Content-Type'] 'application/json' >>> response.request.url 'https://httpbin.org/post' >>> response.request.body b'{"key": "value"}'
通過檢查 PreparedRequest
,你可以訪問有關正在進行的請求的各種資訊,例如有效負載,URL,頭資訊,身份驗證等。
到目前為止,你已經發送了許多不同型別的請求,但它們都有一個共同點:它們是對公共API的未經身份驗證的請求。 你遇到的許多服務可能都希望你以某種方式進行身份驗證。
身份驗證
身份驗證可幫助服務瞭解你的身份。 通常,你通過將資料傳遞到 Authorization
頭資訊或服務定義的自定義頭資訊來向伺服器提供憑據。 你在此處看到的所有請求函式都提供了一個名為 auth
的引數,允許你傳遞憑據。
需要身份驗證的一個示例API的是GitHub的 Authenticated User API。 此端點提供有關經過身份驗證的使用者配置檔案的資訊。 要向 Authenticated User API
發出請求,你可以將你的GitHub的使用者名稱和密碼以元組傳遞給 get()
:
>>> from getpass import getpass >>> requests.get('https://api.github.com/user', auth=('username', getpass())) <Response [200]>
如果你在元組中傳遞給 auth
的憑據有效,則請求成功。 如果你嘗試在沒有憑據的情況下發出此請求,你將看到狀態程式碼為 401 Unauthorized
:
>>> requests.get('https://api.github.com/user') <Response [401]>
當你以元組形式吧使用者名稱和密碼傳遞給 auth
引數時, rqeuests
將使用HTTP的基本訪問認證方案來應用憑據。
因此,你可以通過使用 HTTPBasicAuth
傳遞顯式的基本身份驗證憑據來發出相同的請求:
>>> from requests.auth import HTTPBasicAuth >>> from getpass import getpass >>> requests.get( ...'https://api.github.com/user', ...auth=HTTPBasicAuth('username', getpass()) ... ) <Response [200]>
雖然你不需要明確進行基本身份驗證,但你可能希望使用其他方法進行身份驗證。 requests
提供了開箱即用的其他身份驗證方法,例如 HTTPDigestAuth
和 HTTPProxyAuth
。
你甚至可以提供自己的身份驗證機制。 為此,你必須首先建立AuthBase的子類。 然後,實現 __call __()
:
import requests from requests.auth import AuthBase class TokenAuth(AuthBase): """Implements a custom authentication scheme.""" def __init__(self, token): self.token = token def __call__(self, r): """Attach an API token to a custom auth header.""" r.headers['X-TokenAuth'] = f'{self.token}'# Python 3.6+ return r requests.get('https://httpbin.org/get', auth=TokenAuth('12345abcde-token'))
在這裡,你自定義的 TokenAuth
接收一個令牌,然後在你的請求頭中的 X-TokenAuth
頭中包含該令牌。
錯誤的身份驗證機制可能會導致安全漏洞,因此,除非服務因某種原因需要自定義身份驗證機制,否則你始終希望使用像 Basic
或 OAuth
這樣經過驗證的身份驗證方案。
在考慮安全性時,讓我們考慮使用 requests
處理SSL證書。
SSL證書驗證
每當你嘗試傳送或接收的資料都很敏感時,安全性就很重要。 通過HTTP與站點安全通訊的方式是使用SSL建立加密連線,這意味著驗證目標伺服器的SSL證書至關重要。
好訊息是 requests
預設為你執行此操作。 但是,在某些情況下,你可能希望更改此行為。
如果要禁用SSL證書驗證,請將 False
傳遞給請求函式的 verify
引數:
>>> requests.get('https://api.github.com', verify=False) 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) <Response [200]>
當你提出不安全的請求時, requests
甚至會發出警告來幫助你保護資料安全。
效能
在使用 requests
時,尤其是在生產應用程式環境中,考慮效能影響非常重要。 超時控制,會話和重試限制等功能可以幫助你保持應用程式平穩執行。
超時控制
當你向外部服務發出請求時,系統將需要等待響應才能繼續。 如果你的應用程式等待響應的時間太長,則可能會阻塞對你的服務的請求,你的使用者體驗可能會受到影響,或者你的後臺作業可能會掛起。
預設情況下, requests
將無限期地等待響應,因此你幾乎應始終指定超時時間以防止這些事情發生。 要設定請求的超時,請使用 timeout
引數。 timeout
可以是一個整數或浮點數,表示在超時之前等待響應的秒數:
>>> requests.get('https://api.github.com', timeout=1) <Response [200]> >>> requests.get('https://api.github.com', timeout=3.05) <Response [200]>
在第一個請求中,請求將在1秒後超時。 在第二個請求中,請求將在3.05秒後超時。
你還可以將元組傳遞給 timeout
,第一個元素是連線超時(它允許客戶端與伺服器建立連線的時間),第二個元素是讀取超時(一旦你的客戶已建立連線而等待響應的時間):
>>> requests.get('https://api.github.com', timeout=(2, 5)) <Response [200]>
如果請求在2秒內建立連線並在建立連線的5秒內接收資料,則響應將按原樣返回。 如果請求超時,則該函式將丟擲 Timeout
異常:
import requests from requests.exceptions import Timeout try: response = requests.get('https://api.github.com', timeout=1) except Timeout: print('The request timed out') else: print('The request did not time out')
你的程式可以捕獲 Timeout
異常並做出相應的響應。
Session物件
到目前為止,你一直在處理高階請求API,例如 get()
和 post()
。 這些函式是你發出請求時所發生的事情的抽象。 為了你不必擔心它們,它們隱藏了實現細節,例如如何管理連線。
在這些抽象之下是一個名為 Session
的類。 如果你需要微調對請求的控制方式或提高請求的效能,則可能需要直接使用 Session
例項。
Session
用於跨請求保留引數。 例如,如果要跨多個請求使用相同的身份驗證,則可以使用 session
:
import requests from getpass import getpass # By using a context manager, you can ensure the resources used by # the session will be released after use with requests.Session() as session: session.auth = ('username', getpass()) # Instead of requests.get(), you'll use session.get() response = session.get('https://api.github.com/user') # You can inspect the response just like you did before print(response.headers) print(response.json())
每次使用 session
發出請求時,一旦使用身份驗證憑據初始化,憑據將被保留。
session
的主要效能優化以持久連線的形式出現。 當你的應用程式使用 Session
建立與伺服器的連線時,它會在連線池中保持該連線。 當你的應用程式想要再次連線到同一伺服器時,它將重用池中的連線而不是建立新連線。
最大重試
請求失敗時,你可能希望應用程式重試相同的請求。 但是,預設情況下, requests
不會為你執行此操作。 要應用此功能,您需要實現自定義 Transport Adapter 。
通過 Transport Adapters
,你可以為每個與之互動的服務定義一組配置。 例如,假設你希望所有對於https://api.github.com的請求在最終丟擲 ConnectionError
之前重試三次。 你將構建一個 Transport Adapter
,設定其 max_retries
引數,並將其裝載到現有的 Session
:
import requests from requests.adapters import HTTPAdapter from requests.exceptions import ConnectionError github_adapter = HTTPAdapter(max_retries=3) session = requests.Session() # Use `github_adapter` for all requests to endpoints that start with this URL session.mount('https://api.github.com', github_adapter) try: session.get('https://api.github.com') except ConnectionError as ce: print(ce)
當您將 HTTPAdapter(github_adapter)
掛載到 session
時, session
將遵循其對https://api.github.com的每個請求的配置。
Timeouts
, Transport Adapters
和 Sessions
用於保持程式碼高效和應用程式的魯棒性。
總結
在學習Python中強大的 requests
庫方面,你已經走了很長的路。
你現在能夠:
- 使用各種不同的HTTP方法發出請求,例如GET,POST和PUT
- 通過修改請求頭,身份驗證,查詢字串和訊息體來自定義你的請求
- 檢查傳送到伺服器的資料以及伺服器發回給你的資料
- 使用SSL證書驗證
- 高效的使用
requests
通過使用max_retries
,timeout
,Sessions
和Transport Adapters
因為您學會了如何使用 requests
,所以你可以使用他們提供的迷人資料探索廣泛的Web服務世界並構建出色的應用程式了。
關注公眾號 <程式碼與藝術> ,學習更多國外精品技術文章。