在Python开发中,网络请求是非常常见的操作,无论是调用第三方API、爬取网页数据还是和内部服务交互,都可能遇到请求失败的情况。合理的重试机制可以有效降低这类临时故障带来的影响,提升程序的可用性。

为什么不能直接用循环做重试
很多初学者实现重试时,会直接写一个for循环多次发送请求,这种方式的缺陷非常明显:
- 没有区分错误类型,比如请求参数错误导致的400状态码,重试多少次都不会成功,反而会浪费资源
- 没有设置重试间隔,连续快速重试可能会给服务端造成压力,甚至触发服务端的限流策略
- 没有限制最大重试次数,一旦遇到持续故障,程序会陷入无限重试的状态
- 没有处理重试过程中的异常累积,多次失败后无法正确抛出最终的异常信息
基于urllib3的Retry实现重试
requests库底层依赖urllib3,urllib3内置了Retry类,可以很方便地实现精细化的重试策略。我们可以通过配置Retry对象,指定重试的条件、次数、间隔等参数,再将其挂载到requests的适配器中。
基础重试配置示例
以下是一个针对GET请求设置重试的完整示例:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# 创建重试策略对象
retry_strategy = Retry(
total=3, # 最大重试次数
backoff_factor=1, # 重试间隔的基数,重试间隔为backoff_factor * (2 ** (重试次数-1))
status_forcelist=[429, 500, 502, 503, 504], # 需要触发重试的HTTP状态码
allowed_methods=["GET"] # 允许重试的请求方法,默认只有GET
)
# 创建HTTP适配器并挂载重试策略
adapter = HTTPAdapter(max_retries=retry_strategy)
# 创建session对象并挂载适配器
http = requests.Session()
http.mount("https://", adapter)
http.mount("http://", adapter)
try:
# 发送请求
response = http.get("http://ipipp.com/api/test", timeout=5)
print(f"请求成功,状态码:{response.status_code}")
print(f"响应内容:{response.text}")
except Exception as e:
print(f"请求最终失败,错误信息:{e}")
参数说明
上面示例中Retry的核心参数含义如下:
| 参数名 | 含义 |
|---|---|
| total | 最大重试次数,包括第一次请求,设置为3表示最多发送3次请求 |
| backoff_factor | 重试间隔的基数,实际重试间隔为backoff_factor * (2 ** (重试次数-1)),比如backoff_factor=1时,第一次重试间隔1秒,第二次2秒,第三次4秒 |
| status_forcelist | 触发重试的HTTP状态码列表,只有返回这些状态码时才会重试,避免对参数错误等永久性错误重试 |
| allowed_methods | 允许重试的请求方法,默认只有GET,POST等修改数据的请求默认不允许重试,避免重复提交数据 |
针对POST请求的重试注意事项
POST请求通常用于提交数据,默认情况下urllib3的重试策略不会对其重试,因为重复发送POST请求可能会导致重复创建数据、重复扣款等问题。如果确实需要为POST请求添加重试,需要满足两个条件:
- 接口是幂等的,也就是多次调用产生的效果和执行一次相同
- 在
allowed_methods参数中显式添加POST方法
示例配置如下:
retry_strategy = Retry(
total=2,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["GET", "POST"] # 显式允许POST方法重试
)
自定义异常重试逻辑
除了HTTP状态码触发重试,还可以针对特定的异常类型设置重试,比如连接超时、读取超时等网络异常。可以通过Retry的raise_on_status和自定义异常判断逻辑实现:
from urllib3.exceptions import ConnectTimeoutError, ReadTimeoutError
def is_retryable_exception(exception):
# 判断异常是否属于可重试的类型
return isinstance(exception, (ConnectTimeoutError, ReadTimeoutError))
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504],
# 自定义重试判断逻辑
retry=is_retryable_exception
)
常见误区提醒
不要对所有错误都开启重试,比如4xx状态码中的400、401、403等,通常是请求本身有问题,重试没有意义。
重试间隔不要设置得太短,避免对服务端造成不必要的压力,同时降低触发限流的概率。
如果请求涉及敏感操作,比如支付、数据删除,尽量不要开启重试,或者做好幂等性校验。
通过urllib3的Retry类结合requests的适配器机制,我们可以很灵活地实现符合业务需求的网络请求重试策略,避免简单循环重试带来的各种问题,让程序的网络请求部分更加健壮可靠。