第 9 章 · 异常处理与最佳实践
本章目标:掌握 requests 和 httpx 的异常层次,编写健壮的错误处理代码。
9.1 异常层次结构
requests 的异常继承关系清晰:
requests.exceptions.RequestException
├── ConnectionError # 网络连接失败
│ ├── ProxyError # 代理连接错误
│ └── SSLError # SSL 证书错误
├── HTTPError # HTTP 状态码错误(4xx/5xx)
├── Timeout # 请求超时
│ ├── ConnectTimeout
│ └── ReadTimeout
├── TooManyRedirects # 重定向过多
└── MissingSchema # URL 缺少协议python
import requests
try:
resp = requests.get('https://api.example.com/data', timeout=5)
resp.raise_for_status() # 检查 HTTP 错误
except requests.exceptions.HTTPError as e:
print(f'HTTP 错误: {e.response.status_code}')
except requests.exceptions.ConnectionError as e:
print(f'连接失败: {e}')
except requests.exceptions.Timeout as e:
print(f'超时: {e}')
except requests.exceptions.RequestException as e:
print(f'其他错误: {e}')9.2 httpx 异常体系
httpx 的异常命名更直观:
python
import httpx
try:
with httpx.Client() as client:
resp = client.get('https://api.example.com/data', timeout=5)
resp.raise_for_status()
except httpx.ConnectTimeout:
print('连接超时')
except httpx.ReadTimeout:
print('读取超时')
except httpx.HTTPStatusError as e:
print(f'HTTP {e.response.status_code}')
except httpx.RequestError as e:
print(f'请求错误: {e}')9.3 统一异常处理工具函数
生产代码建议封装统一的处理逻辑:
python
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
def create_session(retries=3, backoff=1) -> requests.Session:
"""创建带重试策略的 session"""
session = requests.Session()
retry = Retry(
total=retries,
backoff_factor=backoff,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
def safe_get(url: str, **kwargs) -> requests.Response:
"""带异常处理的 GET 请求"""
session = create_session()
try:
resp = session.get(url, timeout=10, **kwargs)
resp.raise_for_status()
return resp
except requests.exceptions.Timeout:
print(f'超时: {url}')
except requests.exceptions.HTTPError as e:
print(f'HTTP 错误 {e.response.status_code}: {url}')
except requests.exceptions.ConnectionError:
print(f'连接失败: {url}')
finally:
session.close()9.4 上下文管理器与资源释放
重要:始终使用 with 语句或手动关闭响应/客户端:
python
import requests
# 方式一:with 语句(推荐)
with requests.Session() as session:
resp = session.get('https://api.example.com')
data = resp.json()
# 方式二:手动关闭
session = requests.Session()
try:
resp = session.get('https://api.example.com')
data = resp.json()
finally:
session.close()
# httpx 同样需要关闭
import httpx
with httpx.Client() as client:
resp = client.get('https://api.example.com')未正确关闭连接会导致 TCP 连接泄漏,最终耗尽文件描述符。
9.5 最佳实践清单
- 总是设置超时:
timeout=(connect, read); - 使用 Session:复用连接,提升性能;
- 启用重试:对幂等请求(GET)启用自动重试;
- 捕获具体异常:避免裸
except Exception; - 检查状态码:调用
raise_for_status(); - 资源清理:使用
with语句或try/finally。
9.6 本章小结
- requests 异常继承自
RequestException,按类别细分; - httpx 异常命名更直观,如
ConnectTimeout、HTTPStatusError; - 始终使用上下文管理器确保连接正确释放;
- 封装统一的错误处理工具函数,提升代码健壮性。
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. requests 中检查 HTTP 状态码错误的方法是?
2. 以下哪种做法会导致连接泄漏?
3. httpx 中捕获连接超时的异常类是?
4. 对幂等请求(如 GET)启用重试时,should_retry_on_5xx 应该设为?
🛠️ 动手实践
- 编写统一异常处理函数,捕获五种不同类型的异常并打印不同消息。
- 对比使用
with requests.Session()和不使用的资源泄漏情况(通过lsof观察文件描述符)。 - 封装一个带重试的
safe_get函数,测试 500 错误时的自动重试行为。
完成练习后,进入下一章:httpx 简介与同步 API。