Python Requests 库使用指南
Requests 库是用来发标准 HTTP 请求的包,将请求背后的复杂性抽象成一个漂亮,简单的 API,以便可以专注于与服务交互和在应用程序中使用数据。
一、Requests 安装
安装 requests 库,运行以下命令:
pip install requests
如果使用 Pipenv 管理 Python 包,可以运行下面的命令:
pipenv install requests
一旦安装了 requests
就可以在应用程序中像这样导入 requests
:
import requests
二、GET 请求
最常见的 HTTP 方法之一是 GET,GET 方法表示你正在尝试从指定资源获取或检索数据。 要发送GET请求,请调用 requests.get()
。
requests.get('<https://api.github.com>')
# <Response [200]>
OK,已经成功发出了第一个请求。
三、响应
Response
是检查请求结果的强有力的对象。再次发出相同的请求,但这次将返回值存储在一个变量中,以便可以仔细查看其属性和方法:
response = requests.get('<https://api.github.com>')
在此示例中捕获了 get()
的返回值,该值是 Response
的实例,并将其存储在名为 response
的变量中,现在可以使用 response
来查看有关GET请求结果的全部信息。
1. 状态码
状态码会展示你请求的状态。例如,200
状态表示你的请求成功,而 404
状态表示找不到你要查找的资源。
通过访问 .status_code
,可以看到服务器返回的状态码:
response.status_code
# 200
.status_code
返回 200
意味着你的请求是成功的,并且服务器返回你要请求的数据。有时你可能想要在代码中使用这些信息来做判断:
if response.status_code == 200:
print('Success!')
elif response.status_code == 404:
print('Not Found.')
requests
更进一步为你简化了此过程。如果在条件表达式中使用 Response
实例,则在状态码介于 200
和 400
之间时将被计算为为 True
,否则为 False
。
if response:
print('Success!')
else:
print('An error has occurred.')
请记住,此方法 不会验证
状态码是否等于 200
。原因在于 200
到 400
范围内的其他状态代码(例如 204 NO CONTENT
和 304 NOT MODIFIED
),就意义而言也被认为是成功的响应。因此通常如果你想知道请求是否成功时,请确保使用这方便的简写,然后在必要时根据状态码适当地处理响应。
假设你不想在 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!')
2. 响应内容
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","label_search_url":"https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}","notifications_url":"https://api.github.com/notifications","organization_url":"https://api.github.com/orgs/{org}","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_teams_url":"https://api.github.com/orgs/{org}/teams","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","topic_search_url":"https://api.github.com/search/topics?q={query}{&page,per_page}","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 = requests.get('<https://api.github.com>')
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}","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","label_search_url":"https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}","notifications_url":"https://api.github.com/notifications","organization_url":"https://api.github.com/orgs/{org}","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_teams_url":"https://api.github.com/orgs/{org}/teams","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","topic_search_url":"https://api.github.com/search/topics?q={query}{&page,per_page}","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}>"}'
因为对 bytes
解码到 str
需要一个编码格式,所以如果你没有指定,请求将尝试根据响应头来猜测编码格式。 你也可以在访问 .text
之前通过 .encoding
来显式设置编码:
response = requests.get('<https://api.github.com>')
# Optional: requests infers this internally
response.encoding = 'utf-8'
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}","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","label_search_url":"https://api.github.com/search/labels?q={query}&repository_id={repository_id}{&page,per_page}","notifications_url":"https://api.github.com/notifications","organization_url":"https://api.github.com/orgs/{org}","organization_repositories_url":"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}","organization_teams_url":"https://api.github.com/orgs/{org}/teams","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","topic_search_url":"https://api.github.com/search/topics?q={query}{&page,per_page}","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}>"}'
看看响应你会发现它实际上是序列化的 JSON
内容。 要获取字典内容,你可以使用 .text
获取 str
并使用json.loads()
对其进行反序列化。 但是,完成此任务的更简单方法是使用 .json()
:
response = requests.get('<https://api.github.com>')
response.json()
.json()
返回值的类型是字典类型,因此你可以使用键值对的方式访问对象中的值。
3. 响应头部
响应头部可以为你提供有用的信息,例如响应有效负载的内容类型以及缓存响应的时间限制。 要查看这些头部,请访问 .headers
:
response = requests.get('<https://api.github.com>')
response.headers
# {'Server': 'GitHub.com', 'Date': 'Thu, 24 Mar 2022 18:31:46 GMT', 'Cache-Control': 'public, max-age=60, s-maxage=60', 'Vary': 'Accept, Accept-Encoding, Accept, X-Requested-With', 'ETag': '"4f825cc84e1c733059d46e76e6df9db557ae5254f9625dfe8e1b09499c449438"', 'Access-Control-Expose-Headers': 'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset', 'Access-Control-Allow-Origin': '*', 'Strict-Transport-Security': 'max-age=31536000; includeSubdomains; preload', 'X-Frame-Options': 'deny', 'X-Content-Type-Options': 'nosniff', 'X-XSS-Protection': '0', 'Referrer-Policy': 'origin-when-cross-origin, strict-origin-when-cross-origin', 'Content-Security-Policy': "default-src 'none'", 'Content-Type': 'application/json; charset=utf-8', 'X-GitHub-Media-Type': 'github.v3; format=json', 'Content-Encoding': 'gzip', 'X-RateLimit-Limit': '60', 'X-RateLimit-Remaining': '49', 'X-RateLimit-Reset': '1648148765', 'X-RateLimit-Resource': 'core', 'X-RateLimit-Used': '11', 'Accept-Ranges': 'bytes', 'Content-Length': '530', 'X-GitHub-Request-Id': '8F80:0352:67EFFC:6FA41B:623CB913'}
.headers
返回类似字典的对象,允许使用键来获取头部中的值。 例如要查看响应有效负载的内容类型,你可以访问 Content-Type
:
response = requests.get('<https://api.github.com>')
response.headers['Content-Type']
# 'application/json; charset=utf-8'
但是,这个类似于字典的头部对象有一些特别之处。 HTTP规范定义头部不区分大小写,这意味着我们可以访问这些头信息而不必担心它们的大小写:
response = requests.get('<https://api.github.com>')
response.headers['content-type']
# 'application/json; charset=utf-8'
现在已经学习了有关 Response
的基础知识,已经看到了它最有用的属性和方法。 让我们退后一步,看看自定义 GET
请求时你的响应如何变化。
四、查询字符串参数
自定义 GET
请求的一种常用方法是通过URL中的查询字符串参数传递值。 要使用 get()
执行此操作,请将数据传递给 params
。 例如,你可以使用 GitHub 的 Search API 来查找 requests
库:
# Search GitHub's repositories for requests
response = requests.get(
'<https://api.github.com/search/repositories>',
params={'q': 'requests+language:python'}
)
response.json()
通过将字典 {'q':'requests + language:python'}
传递给 .get()
的 params
参数,你可以修改从 Search API 返回的结果。
也可以像刚才那样以字典的形式或以元组列表形式将 params
传递给 get()
:
# Search GitHub's repositories for requests
response = requests.get(
'<https://api.github.com/search/repositories>',
params=[('q', 'requests+language:python')]
)
response.json()
甚至可以传 bytes
作为值:
# Search GitHub's repositories for requests
response = requests.get(
'<https://api.github.com/search/repositories>',
params=b'q=requests+language:python'
)
response.json()
查询字符串对于参数化 GET 请求很有用,还可以通过添加或修改发送请求的头部来自定义请求。
五、请求头
要自定义请求头,你可以使用 headers
参数将HTTP头部组成的字典传递给 get()
。 例如,你可以通过 Accept
中指定文本匹配媒体类型来更改以前的搜索请求,以在结果中突出显示匹配的搜索字词:
response = requests.get(
'<https://api.github.com/search/repositories>',
params={'q': 'requests+language:python'},
headers={'Accept': 'application/vnd.github.v3.text-match+json'}
)
Accept
告诉服务器你的应用程序可以处理哪些内容类型。 由于你希望突出显示匹配的搜索词,所以使用的是 application / vnd.github.v3.text-match + json
,这是一个专有的GitHub的 Accept
标头,其内容为特殊的JSON格式。
六、其他HTTP方法
除了 GET
之外,其他流行的 HTTP 方法包括 POST
,PUT
,DELETE
,HEAD
,PATCH
和 OPTION
,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'
七、消息体
根据 HTTP 规范,POST
,PUT
和不太常见的PATCH
请求通过消息体而不是通过查询字符串参数传递它们的数据。 使用 requests
你将有效负载传递给相应函数的 data
参数。
data
接收字典、元组、列表、字节或类文件对象,你需要在请求正文中发送的数据调整为与你交互的服务的特定格式。例如,如果你的请求的内容类型是 application / x-www-form-urlencoded
,则可以将表单数据作为字典发送:
requests.post('<https://httpbin.org/post>', data={'key':'value'})
你还可以将相同的数据作为元组列表发送:
requests.post('<https://httpbin.org/post>', data=[('key', 'value')])
但是,如果需要发送 JSON 数据则可以使用 json
参数,当你通过 json
传递JSON数据时,requests
将序列化你的数据并为你添加正确的 Content-Type
标头。
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,头信息,身份验证等。
九、身份验证
身份验证可帮助服务了解你的身份。 通常你通过将数据传递到 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()))
如果你在元组中传递给 auth
的凭据有效,则请求成功。 如果你尝试在没有凭据的情况下发出此请求,你将看到状态代码为 401 Unauthorized
。
当你以元组形式把用户名和密码传递给 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
。
十、SSL证书验证
每当你尝试发送或接收的数据都很敏感时,安全性就很重要。 通过HTTP与站点安全通信的方式是使用 SSL 建立加密连接,这意味着验证目标服务器的SSL证书至关重要。
好消息是 requests
默认为你执行此操作。 但是在某些情况下,你可能希望更改此行为。如果要禁用SSL证书验证,请将 False
传递给请求函数的 verify
参数:
requests.get('<https://api.github.com>', verify=False)
当你提出不安全的请求时,requests
甚至会发出警告来帮助你保护数据安全。
十一、性能
在使用 requests 时,尤其是在生产应用程序环境中考虑性能影响非常重要。 超时控制,会话和重试限制等功能可以帮助你保持应用程序平稳运行。
1. 超时控制
当你向外部服务发出请求时,系统将需要等待响应才能继续。 如果你的应用程序等待响应的时间太长,则可能会阻塞对你的服务的请求,你的用户体验可能会受到影响,或者你的后台作业可能会挂起。
默认情况下,requests
将无限期地等待响应,因此你应始终指定超时时间以防止这些事情发生。 要设置请求的超时,请使用 timeout
参数。 timeout
可以是一个整数或浮点数,表示在超时之前等待响应的秒数:
requests.get('<https://api.github.com>', timeout=1)
你还可以将元组传递给 timeout
,第一个元素是连接超时(它允许客户端与服务器建立连接的时间),第二个元素是读取超时(一旦你的客户已建立连接而等待响应的时间):
requests.get('<https://api.github.com>', timeout=(2, 5))
如果请求在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')
2. Session对象
到目前为止你一直在处理高级请求API,例如 get()
和 post()
。 这些函数是你发出请求时所发生的事情的抽象,因此你不必担心其中的实现细节。
在这些抽象之下是一个名为 Session
的类,如果你需要微调对请求的控制方式或提高请求的性能,则可能需要直接使用 Session
实例。
Session
用于跨请求保留参数。 例如如果要跨多个请求使用相同的身份验证,则可以使用session
:
import requests
from getpass import getpass
with requests.Session() as session:
session.auth = ('username', getpass())
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
建立与服务器的连接时,它会在连接池中保持该连接。 当你的应用程序想要再次连接到同一服务器时,它将重用池中的连接而不是建立新连接。
3. 最大重试
请求失败时你可能希望应用程序重试相同的请求。 但是默认情况下,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()
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 的每个请求的配置。