Skip to content

requests

requests 是 Python 下著名的 HTTP 请求库,他可以轻松的构建 HTTP/1.1 协议的请求用户无需关心如何构建查询字符串以及 post 编码等问题。

requests 是构建在 urllib3 之上的,提供了自动 HTTP 持久化连接、国际域名、Cookie 持久会话、自动解码内容,优雅的基于字典的 Cookie 和参数设置等非常人性化的功能。

安装

Bash
pip install requests

conda install requests

request

requests 库中最常用的方法就是 requests.request 方法,他用于构造和发送一个请求:

Python
def request(
    method: str | bytes,
    url: str | bytes,
    params: _Params | None = ...,
    data: _Data | None = ...,
    headers: _HeadersMapping | None = ...,
    cookies: RequestsCookieJar | _TextMapping | None = ...,
    files: _Files | None = ...,
    auth: _Auth | None = ...,
    timeout: _Timeout | None = ...,
    allow_redirects: bool = ...,
    proxies: _TextMapping | None = ...,
    hooks: _HooksInput | None = ...,
    stream: bool | None = ...,
    verify: _Verify | None = ...,
    cert: _Cert | None = ...,
    json: Incomplete | None = ...,
) -> Response: ...
  • method: 决定了请求的方法,requests 支持常见的 7 中方法: "GET" "POST" "DELETE" "HEAD" "OPTIONS" "PUT" "PATCH" 其中最常用的就是前两个。并且 request 方法还提供了他们的便捷方式允许 requests.get() 这样的形式来发起对应的请求
  • urlparams: 用于构造 URL,其中 params 可以是字典形式并且其中可以是字符串(Unicode)、数字,他们会自动被转义。可以不指定 params 而完全使用 url 来构造此时通常需要自己转义(实际上 requests 会自动转义,不过最好还是使用 params)
  • headers: 定义request header
  • cookies: Cookies
  • timeout: 超时时间(s),requests 并不会为我们处理超时,如果服务器没有响应会一直阻塞下去,因此通常我们应当指定他。注意超时时间并不决定通信时间,他通常是指两个时间以下两个时间,该参数可以任意浮点数,也可以是 (3.05, 10) 这样分别定义(connect, read):
    • connect time: 他是客户端与服务器建立连接的时间,通常设置为略大于 3 或倍数的一个数字
    • read timeout: 他是客户端等待服务器发送第一个字节的时间
  • allow_redirects: 是否重定向(默认 True)
  • proxies: 代理
  • stream: 流式读取,如果为 True 提供了一种手段来流式访问响应。(默认 False 即直接下载响应)
  • datajson: 主要用于 post 请求的请求体,其中 data 对应了 application/x-www-form-urlencoded,而 json 对应了 application/jsontips: 理论上只要指定了 Content-Type 上传可以是任何类型,但是只有这两种被 HTTP 服务器广泛支持
  • files: 用于以表单的形式上传文件,对应了multipart/form-datatips: data 同样支持文件上传,他们主要区别还是 Content-Type 的不同, data 会进行编码而 files 并不会。files 特指控件的文件上传,不过具体使用哪一个还是需要看具体支持哪一个

流式读取

通过 stream=True 来支持流式读取响应,此时 requests.request(url, stream=True) 直接返回,直到调用 Response.content 属性时才会流式下载响应。

注意这并不意味着他是异步的,因为 requests.request(url, stream=True) 并不会在后台默默为我们下载数据,只有调用 Response.connect 后才进行数据传输,他只是将传输延迟了,不过他会下载响应头,并且保持连接打开(注意有些服务器会在几秒后断开这个连接,所以应当尽快读取):

Python
tarball_url = 'https://github.com/psf/requests/tarball/main'
r = requests.get(tarball_url, stream=True)

# 会下载响应头,不过响应体需要调用 r.connect 才会下载
if int(r.headers['content-length']) < TOO_LONG:
  content = r.content

# 必须显式关闭
r.close()

requests 还提供了 Response.iter_content()Response.iter_lines() 方法让流式传输更加的有意义,并且 stream=True 的请求还支持 with 语句(实际上所有请求都支持,不过没啥意义)来自动帮我们关闭连接:

Python
with requests.get('https://httpbin.org/get', stream=True) as r:
    for line in r.iter_lines():
        print(line)

Tips

requests 的流在执行速度上没有什么优势,它主要是如果需要请求大文件时能够减少内存的使用。

Tips

只有在读取所有正文数据后,连接才会被释放到连接池,Keep-Alive 才有意义。因此 stream=True 时要尽快读取数据。

自动解码

在发送的请求头中能够定义 Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5 用于将客户端能够处理的编码(通常是压缩算法)发送给服务器,服务器会通过 Content-Encoding 来返回协商后的结果来告诉服务器当前的客户端支持哪一种压缩方法。服务器在接收到请求后会添加 Content-Type 这样的形式告诉客户端传输的数据是通过哪种算法压缩的,这样客户端就能够根据对应的算法来解压他们。

这整个过程在 requests 中是自动完成的。也就是说 Response.content 获取的是解压后的数据。当然如果不希望 requests 为我们自动解压,可以调用 Response.raw.data 来获取原始字符串但是要注意: 必须在 stream=True 即流传输的模式下才能够调用 Response.raw:

Python
import requests
import gzip

with requests.Session() as session:
    session.headers.update({
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.188"
    })
    with session.get("https://www.appinn.com/", stream=True) as stream:
        data = stream.raw
        dt = gzip.decompress(data.data)
        print(dt)

Note

在流模式下 Response.contentResponse.iter_linesResponse.iter_content 都会自动对他们解码。只有 Response.raw 是不解码的返回。他的返回是一个对象,而不是原始字符串,需要调用 Response.raw.data 来返回原始字符串

Tips

br 压缩需要 brotli 包的支持

代理

requests 默认情况下会从运行进程的环境变量中获取 http_proxyhttps_proxyall_proxy 的设置来作为请求的代理:

Bash
# 大小写都无所谓
export HTTP_PROXY="http://10.10.1.10:3128"
export HTTPS_PROXY="http://10.10.1.10:1080"
export ALL_PROXY="socks5://10.10.1.10:3434"

当然也可以使用 proxies 来指定:

Python
import requests

proxies = {
  'http': 'http://10.10.1.10:3128',
  'https': 'http://10.10.1.10:1080',
}
requests.request.get('http://example.org')

有些商业代理会使用 HTTP Basic Auth 来认证,这在 requests 中能够自动处理只需要设置代理为:

Python
proxies = {
    'http': 'http://user:passwd@host:port',
    'https': 'http://user:passwd@host:port'
}

Note

其中的用户名和密码会被添加到Proxy-Authorization: 中来为用户代理提供给代理服务器的用于身份验证的凭证请求头中。

Session

Session 被称为会话对象,可以跨请求持久化某些参数,并且会在会话实例发出的所有请求中 Cookie 持久化保持,同时会使用连接池(由 urllib3 提供支持),连接池的底层 TCP 连接是可复用的 HTTP 持久化连接。

Note

会话对象支持 with 上下文管理

持久化参数

requests.request() 的大部分参数都可以被持久化到 Session 对象上:

Python
s = requests.Session()
# 持久化参数
s.headers.update({'x-test': 'true'})
s.timeout = 5
s.allow_redirects = False

这样每次请求都会携带同样的参数,当然我们可以在特定的 session 中覆盖他:

Python
# 覆盖持久化中的 header
# 需要注意方法级参数永远不会跨方法来同一个 session 中传递
s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})

Note

session.proxies 的工作可能跟预想的不太一样,环境变量的代理设置会覆盖 session 中共享的设置。因此官方的建议是给每个请求添加 proxies 而不是由 session 共享。

cookies持久化保持

这个也是 Session 对象的一大优势,在 Session 的一个请求链之间如果有响应头提供了set-cookies : Cookies,那么他们都会在 Session.cookies 中被保持并在下一个请求中自动传递。

Note

当然你也完全可以使用 Session.cookies 手动构造自己的 Cookies

Response

无论是 request 还是 Session 最终都是为了获取响应,他被包装成 Response 对象:

Python
class Response:
    def close():
    """将连接返回线程池,通常不需要显式调用"""
        pass
    # 内容返回,最常用的方法
    encoding: str # 访问 text 时解码器编码
    content: bytes  # 响应内容,注意是 bytes 单位
    text: str # 响应内容, str, content.encode(encoding)
    def json(): ... # 返回 json 编码的内容, Response.headers['Content-Type'] = 'application/json' 时有效

    # 响应属性
    url: str # 响应的最终 URL
    request: Request # 响应的请求体
    cookies: CookieJar # 服务器发回的 Cookie
    headers: dict # 响应头
    history: list[Response] # 重定向经过的网页的响应列表
    is_permanent_redirect: bool # 如果该响应是重定向结果返回 True
    is_redirect: bool # 如果池响应是格式正确的 HTTP 重定向则为 True
    status_code: int # 响应的 HTTP 状态码
    ok: bool # 如果 status_code < 400 返回 True 否则返回 False
    reason: str #  HTTP 响应状态文本表示,例如 Not Found -> 404 OK -> 200

    # 需要 stream=True 启用流式传输
    def iter_content(chunk_size=1): ... # 流式返回 chunk_size 指定的数据
    def iter_lines(chunk_size=512): ... # 返回一行
    raw: any # 响应的原始数据,甚至他不会自动解码 gzip/deflate 等,是套接字的原始数据

最常见的使用方法就是通过 text 或者 json() 来返回最终的数据。

Tips

如果 JSON 解析失败,会引发 JSONDecodeError 异常

如果是二进制内容(通常就是图片等非文本请求)可以使用 content:

Python
from PIL import Image
from io import BytesIO

i = Image.open(BytesIO(r.content))

注意content 并不会直接返回 gzip 或 deflate 传输过程中压缩的字节流,即他会自动帮助我们解码。如果希望从服务器获取原始套接字响应,可以访问 raw 属性,但是他要求请求必须设置为 stream=True:

Python
r = requests.get('https://api.github.com/events', stream=True)
r.raw
# <urllib3.response.HTTPResponse object at 0x101194810>
r.raw.read(10)
# b'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

异常

requests 中定义了如下异常:

  • RequestException() : requests 所有显式引发的异常的父类,也就说如果不确定 requests 会引发什么异常指定他就行
  • ConnectionError() : 连接异常,通常网络问题例如 DNS 故障、拒绝连接等
  • HTTPError() : 如果 HTTP 请求返回不成功的状态码,则调用 Response.raise_for_status() 将引发该异常(2xx 的状态码算作成功)
  • TooManyRedirects() : 太多的重定向异常
  • ConnectionTimeout() : 连接超时异常
  • ReadTimeout() : 读取超时异常
  • Timeout() : 包含 ConnectionTimeout 和 ReadTimeout
  • JSONDecodeError() : json 解析异常

Tips

这里面需要注意的是 HTTPError 它通常是用户通过 Response.raise_for_status() 方法自行抛出的