第 11 章 · 异步客户端 AsyncClient
本章目标:掌握 httpx.AsyncClient 的用法,理解 asyncio 并发模型,写出高效的异步 HTTP 客户端。
11.1 异步 HTTP 的价值
同步代码在 I/O 等待时阻塞整个线程。异步 I/O 允许单线程同时处理数千个连接:
python
import asyncio
import httpx
async def fetch_repo(client: httpx.AsyncClient, owner: str, repo: str):
"""获取单个仓库信息"""
resp = await client.get(f'/repos/{owner}/{repo}')
resp.raise_for_status()
data = resp.json()
return {
'name': data['full_name'],
'stars': data['stargazers_count']
}
async def main():
async with httpx.AsyncClient(base_url="https://api.github.com") as client:
# 并发获取多个仓库
tasks = [
fetch_repo(client, 'encoding', 'httpx'),
fetch_repo(client, 'psf', 'requests'),
fetch_repo(client, 'pytest-dev', 'pytest')
]
results = await asyncio.gather(*tasks)
for r in results:
print(f"{r['name']}: {r['stars']} ⭐")
asyncio.run(main())11.2 异步 vs 并发对比
python
import time
import httpx
# 同步串行:10 个请求 × 300ms = 3000ms
start = time.time()
for i in range(10):
httpx.get('https://httpbin.org/delay/0.3')
print(f'同步串行: {time.time() - start:.1f}s')
# 异步并发:10 个请求 ~ 300ms + overhead
async def async_benchmark():
async with httpx.AsyncClient() as client:
tasks = [client.get('https://httpbin.org/delay/0.3') for _ in range(10)]
await asyncio.gather(*tasks)
start = time.time()
asyncio.run(async_benchmark())
print(f'异步并发: {time.time() - start:.1f}s')11.3 异步上下文管理器
python
import httpx
import asyncio
async def process_data():
async with httpx.AsyncClient() as client:
# 客户端在整个 with 块内共享
resp1 = await client.get('/api/users')
resp2 = await client.post('/api/data', json={'key': 'value'})
# 自动关闭所有连接
# 退出 with 后连接已释放11.4 错误处理
python
import httpx
import asyncio
async def safe_request(url: str):
try:
async with httpx.AsyncClient() as client:
resp = await client.get(url, timeout=10.0)
resp.raise_for_status()
return resp.json()
except httpx.HTTPStatusError as e:
print(f'HTTP 错误 {e.response.status_code}: {e.request.url}')
except httpx.ConnectError as e:
print(f'连接失败: {e}')
except httpx.TimeoutException:
print('请求超时')
return None11.5 本章小结
asyncio.gather()并发执行多个异步请求,显著降低总耗时;- 始终用
async with httpx.AsyncClient()管理客户端生命周期; - 区分 HTTPStatusError(4xx/5xx)、ConnectError(网络层)、TimeoutException(超时)。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. asyncio.gather() 的主要作用是?
2. httpx.AsyncClient 必须用 async with 管理吗?
3. HTTP 404 在 httpx 中会抛出什么异常?
4. 异步 HTTP 客户端适合哪些场景?
🛠️ 动手实践
- 用 asyncio.gather 并发获取 5 个 GitHub 用户信息,对比串行实现耗时。
- 实现一个带重试的异步请求函数,失败后指数退避重试。
- 编写异步健康检查器,并发检测多个 URL 的可用性。