跳至内容

requests、httpx 与 aiohttp

三种 HTTP 客户端的传参、响应处理与异步调用说明。

  • requests
  • aiohttp
  • httpx
  • HTTP
  • 异步编程
  • Python
库调用方式适用场景
requests同步调用,等待期间阻塞当前线程顺序执行的脚本、已有同步接口封装
httpx同时提供同步与异步客户端同一套项目需要两种调用方式,或需要 HTTP/2
aiohttp基于 asyncio 的异步客户端已有异步调用链,或需要内置 WebSocket 客户端

HTTP/1.1、HTTP/2 是 HTTP 协议的不同版本,本文示例默认使用 HTTP/1.1。HTTP/2 支持在同一条 TCP 连接上交错传输多个请求和响应;GET、POST 在两种版本中都可以使用。

1. 安装与示例接口

代码块 Shell
1
python -m pip install requests httpx aiohttp

示例使用 httpbin 公共回显服务,返回收到的参数、请求头和请求体;无需账号或密钥。公共服务可能暂时不可用,只提交示例数据。

验证环境:Python 3.13.11、requests 2.32.5、httpx 0.28.1、aiohttp 3.13.3。

2. requests:同步请求

2.1 GET:查询参数与请求头

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import requests


def fetch_page(page: int, size: int):
    url = "https://httpbin.org/get"
    params = {"page": page, "size": size}
    headers = {"Accept": "application/json", "X-Demo-Client": "orthant"}
    response = requests.get(url, params=params, headers=headers, timeout=15)
    response.raise_for_status()
    return response.json()


result = fetch_page(1, 2)
print(result)
运行结果 Text
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
	'args': {
		'page': '1',
		'size': '2'
	},
	'headers': {
		'Accept': 'application/json',
		'Accept-Encoding': 'gzip, deflate, br',
		'Host': 'httpbin.org',
		'User-Agent': 'python-requests/2.32.5',
		'X-Amzn-Trace-Id': 'Root=1-6ad71c80-33546f5c1ff7a1ea12b46a69',
		'X-Demo-Client': 'orthant'
	},
	'origin': '134.195.101.83',
	'url': 'https://httpbin.org/get?page=1&size=2'
}
  1. requests.get():发送请求,返回响应对象。
  2. raise_for_status():检查状态码,4xx、5xx 时抛出异常。
  3. response.json():将响应体解析为 Python 对象。

params 对 GET、POST 都适用,始终放在 URL 查询字符串中。 POST 可以同时用 params 传查询参数,用 json 或 data 传请求体。

requests 的超时分为连接超时和读取超时,单位都是秒。

  • 统一设置:timeout=15,连接超时和读取超时都设为 15 秒,也可以传小数。
  • 分别设置:timeout=(5, 15),按 (连接超时, 读取超时) 排列,分别设为 5 和 15 秒。

读取超时限制等待下一段响应数据的时间,不等于整个请求的总时限。

2.2 POST:发送 JSON

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import json
import requests


def submit_item(item_id: int, quantity: int):
    url = "https://httpbin.org/post"
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    payload = {"item_id": item_id, "quantity": quantity}
    response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=15)
    response.raise_for_status()
    result = response.json()
    return result["json"]


print(submit_item(101, 2))
print(submit_item(102, 1))
运行结果 Text
1
2
# {'item_id': 101, 'quantity': 2}
# {'item_id': 102, 'quantity': 1}
  • 请求体:data=json.dumps(payload) 先将字典转为 JSON 文本,再发送。
  • 发送类型:Content-Type: application/json 声明请求体是 JSON。
  • 接收类型:Accept: application/json 表示希望收到 JSON 响应。

也可以直接用 json=payload,由 requests 完成序列化并设置 JSON 内容类型;

注意: 不要写成 json=json.dumps(payload),否则会重复编码。

3. aiohttp:异步请求

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import asyncio
import aiohttp


async def submit_item(session: aiohttp.ClientSession, item_id: int, quantity: int):
    payload = {"item_id": item_id, "quantity": quantity}
    async with session.post("https://httpbin.org/post", json=payload) as response:
        response.raise_for_status()
        result = await response.json()
        return result["json"]


async def main():
    headers = {"Accept": "application/json", "X-Demo-Client": "orthant"}
    timeout = aiohttp.ClientTimeout(total=20, connect=5)
    async with aiohttp.ClientSession(headers=headers, timeout=timeout) as session:
        print(await submit_item(session, 101, 2))
        print(await submit_item(session, 102, 1))


asyncio.run(main())
运行结果 Text
1
2
# {'item_id': 101, 'quantity': 2}
# {'item_id': 102, 'quantity': 1}

外层 async with 负责关闭 Session,内层负责释放本次响应。

aiohttp 取得响应对象时,响应体可能尚未读完,因此 response.json() 需要 await。 raise_for_status() 只检查状态码,不需要 await。

4. httpx:同步与异步请求

4.1 同步版本

Client 管理公共配置和连接池:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import httpx


def submit_item(client: httpx.Client, item_id: int, quantity: int):
    url = "https://httpbin.org/post"
    payload = {"item_id": item_id, "quantity": quantity}
    response = client.post(url, json=payload)
    response.raise_for_status()
    result = response.json()
    return result["json"]


headers = {"Accept": "application/json", "X-Demo-Client": "orthant"}
timeout = httpx.Timeout(15, connect=5)
with httpx.Client(headers=headers, timeout=timeout) as client:
    print(submit_item(client, 101, 2))
    print(submit_item(client, 102, 1))
运行结果 Text
1
2
# {'item_id': 101, 'quantity': 2}
# {'item_id': 102, 'quantity': 1}

4.2 异步版本

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import asyncio
import httpx


async def submit_item(client: httpx.AsyncClient, item_id: int, quantity: int):
    url = "https://httpbin.org/post"
    payload = {"item_id": item_id, "quantity": quantity}
    response = await client.post(url, json=payload)
    response.raise_for_status()
    # 非流式请求返回前已读完响应体,解析 JSON 不需要 await。
    result = response.json()
    return result["json"]


async def main():
    headers = {"Accept": "application/json", "X-Demo-Client": "orthant"}
    timeout = httpx.Timeout(15, connect=5)
    async with httpx.AsyncClient(headers=headers, timeout=timeout) as client:
        print(await submit_item(client, 101, 2))
        print(await submit_item(client, 102, 1))


asyncio.run(main())
运行结果 Text
1
2
# {'item_id': 101, 'quantity': 2}
# {'item_id': 102, 'quantity': 1}
  • 流式响应:用 aiter_bytes() 逐块读取,边接收边处理;await response.aread() 则读完整个响应体。
  • JSON 文本:已经序列化的数据用 content= 发送,并设置 Content-Type: application/json。
  • HTTP/2:安装 httpx[http2],创建客户端时设置 http2=True;服务端也须支持,可用 response.http_version 查看实际协议。

5. 批量并发请求

三个请求共享一个 Session,最多同时执行两个:

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import asyncio
import aiohttp


async def submit_item(session: aiohttp.ClientSession, item_id: int, semaphore: asyncio.Semaphore):
    payload = {"item_id": item_id, "quantity": 1}
    async with semaphore:
        async with session.post("https://httpbin.org/post", json=payload) as response:
            response.raise_for_status()
            result = await response.json()
            return result["json"]


async def main():
    item_ids = [101, 102, 103]
    semaphore = asyncio.Semaphore(2)
    timeout = aiohttp.ClientTimeout(total=20, connect=5)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        calls = [submit_item(session, item_id, semaphore) for item_id in item_ids]
        results = await asyncio.gather(*calls, return_exceptions=True)

    for item_id, result in zip(item_ids, results):
        if isinstance(result, Exception):
            print(f"{item_id} 请求失败:{type(result).__name__}")
        else:
            print(result)


asyncio.run(main())

全部请求成功时:

运行结果 Text
1
2
3
# {'item_id': 101, 'quantity': 1}
# {'item_id': 102, 'quantity': 1}
# {'item_id': 103, 'quantity': 1}
  • gather(*calls):并发调度协程,结果按输入顺序排列。
  • Semaphore(2):限制同时进行的请求数,不限制每秒请求数。
  • return_exceptions=True:将普通请求异常放入结果列表,其余请求继续完成。

httpx 的 AsyncClient 也可用同样方式调度,客户端在这一批请求之间共享。

6. 用法对照

6.1 响应属性和读取方式

操作requestshttpxaiohttp
状态码response.status_coderesponse.status_coderesponse.status
JSONresponse.json()response.json()await response.json()
文本response.textresponse.textawait response.text()
字节response.contentresponse.contentawait response.read()

6.2 超时设置对照

库本文设置含义
requeststimeout=(5, 15)连接超时 5 秒,读取等待超时 15 秒
httpxhttpx.Timeout(15, connect=5)连接超时 5 秒;读取、写入、等待连接池分别按 15 秒限制
aiohttpaiohttp.ClientTimeout(total=20, connect=5)整次请求总超时 20 秒;获取连接超时 5 秒,包含连接池排队和新连接建立

requests、httpx 按阶段限制超时,aiohttp 的 total 限制整次请求;total 不包含等待信号量的时间。

默认值:requests 不设超时,httpx 各阶段 5 秒,aiohttp 总超时 300 秒。

6.3 HTTP 状态异常与网络异常

HTTP 状态异常由 raise_for_status() 触发。表中异常类属于对应客户端库,asyncio.TimeoutError 除外:

情况requestshttpxaiohttp
状态检查失败HTTPError,针对 4xx、5xxHTTPStatusError,针对非 2xxClientResponseError,针对状态码 ≥ 400
连接错误ConnectionErrorConnectErrorClientConnectionError
超时TimeoutTimeoutExceptionasyncio.TimeoutError,也能捕获总超时

requests 2.32.5 读取响应体超时时也可能抛 ConnectionError,只捕获 Timeout 无法覆盖这一情况。

requests、aiohttp 的 GET/POST 默认跟随重定向;httpx 默认不跟随,需要时设置 follow_redirects=True,否则 302 响应也会触发状态异常。

6.4 其他注意事项

  • verify=False、ssl=False 会跳过 HTTPS 证书校验;证书错误应通过证书链或可信 CA 配置解决。
  • 在异步函数中直接调用 requests 仍会阻塞事件循环;可改用异步客户端,或交给 asyncio.to_thread()。
  • POST 超时后,服务端可能已执行成功;重试前需确认接口的幂等或去重机制。

7. 捕获异常

具体异常写在前面,父类异常写在后面。 请求发送、状态检查和 JSON 解析都放进 try,分别处理失败原因。

以下脚本使用返回 404 的测试接口;换成返回 JSON 的正常接口后,会打印解析结果。异常在 main() 入口处理,前面用于返回数据的请求函数仍可让调用方处理异常。

7.1 requests

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import requests


def main():
    try:
        response = requests.post("https://httpbin.org/status/404", timeout=15)
        response.raise_for_status()
        print(response.json())
    except requests.HTTPError as exc:
        print(f"HTTP 状态错误:{exc.response.status_code}")
    except requests.Timeout:
        print("请求超时")
    except requests.exceptions.JSONDecodeError:
        print("响应体不是有效的 JSON")
    except requests.RequestException as exc:
        print(f"请求失败:{type(exc).__name__}")


main()
运行结果 Text
1
# HTTP 状态错误:404

RequestException 包括连接失败等请求异常,也包括前面几个异常,因此放在最后。

7.2 httpx

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import asyncio
import json
import httpx


async def main():
    async with httpx.AsyncClient(timeout=15) as client:
        try:
            response = await client.post("https://httpbin.org/status/404")
            response.raise_for_status()
            print(response.json())
        except httpx.HTTPStatusError as exc:
            print(f"HTTP 状态错误:{exc.response.status_code}")
        except httpx.TimeoutException:
            print("请求超时")
        except httpx.RequestError as exc:
            print(f"请求失败:{type(exc).__name__}")
        except json.JSONDecodeError:
            print("响应体不是有效的 JSON")


asyncio.run(main())
运行结果 Text
1
# HTTP 状态错误:404

同步 Client 使用相同的异常类型。

7.3 aiohttp

代码块 Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import asyncio
import json
import aiohttp


async def main():
    timeout = aiohttp.ClientTimeout(total=15)
    async with aiohttp.ClientSession(timeout=timeout) as session:
        try:
            async with session.post("https://httpbin.org/status/404") as response:
                response.raise_for_status()
                print(await response.json())
        except (aiohttp.ContentTypeError, json.JSONDecodeError):
            print("响应内容类型或 JSON 格式错误")
        except aiohttp.ClientResponseError as exc:
            print(f"HTTP 状态错误:{exc.status}")
        except asyncio.TimeoutError:
            print("请求超时")
        except aiohttp.ClientError as exc:
            print(f"请求失败:{type(exc).__name__}")


asyncio.run(main())
运行结果 Text
1
# HTTP 状态错误:404

ContentTypeError 是 ClientResponseError 的子类,需优先捕获;asyncio.TimeoutError 用于覆盖总超时,不能只捕获 ClientError。

8. 常见 HTTP 错误状态码

8.1 4xx:客户端错误

状态码含义
400请求有误,服务器无法或拒绝处理
401缺少有效的身份凭据,例如未提供或已失效的 Token
403服务器理解请求,但因权限或其他策略拒绝执行
404未找到目标资源,或服务器不愿透露其存在
405目标资源不支持当前请求方法
408服务器未在期限内收到完整请求
409请求与资源当前状态冲突,例如更新版本冲突
415请求体格式不受支持,例如接口要求 JSON,却提交了表单
422内容类型、语法正确,但语义无法处理,例如字段值不符合要求
429请求过于频繁,触发限流

8.2 5xx:服务端错误

状态码含义
500服务器内部发生意外错误
502网关或代理收到上游服务器的无效响应
503服务因临时过载或维护而不可用
504网关或代理等待上游服务器响应超时

408、504 是服务端返回的 HTTP 状态码;客户端抛出 Timeout 时,可能根本没有收到 HTTP 响应。

评论

使用 GitHub 登录后参与讨论,评论将公开保存在 GitHub。

阅读到这里时加载评论。