1

上一篇中我们使用了 HTTPAdapter 中的 max_retries 参数对超时类的原因进行了重试。本篇文章中我们将要使用 Retry 类对重试的原因和过程进行精细化控制。

import logging

import requests
from requests.adapters import HTTPAdapter

# 开启 urllib3 的日志,方便查看重试过程
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
urllib3_logger = logging.getLogger('urllib3')
urllib3_logger.setLevel(logging.DEBUG)
http_adapter = HTTPAdapter(max_retries=3)
# 使用 session 发送请求
session = requests.session()
# 打印 adapters
print(session.adapters)
session.mount('https://', http_adapter)
session.mount('http://', http_adapter)
try:
    print(session.get('https://www.baidu.com', timeout=0.01).text[:100])
except Exception as e:
    print(e)
    print(type(e))

以上为之前的代码进行了一些小修改,在初始化 HTTPAdapter 时,传入了 max_retries 参数,意思是在网络超时的时候重试三次。那如果不超时只是服务器返回了不正常的响应,应该如何进行更加方便的重试呢?

image-20241003182539696

image-20241003183246461

查看 HTTPAdapter 类的初始化代码可以发现,max_retries 参数最终会被赋值为 Retry 的实例,并且将我们的参数传入。

那么如果我们想控制重试的时机以及过程,只需要传入一个自定义的 Retry 类就好了。

来看一下 Retry 的文档

1. total

  • 功能:控制总的重试次数。如果设置为 None,则禁用总重试次数。
  • 默认值10
  • 说明:这是总的重试次数,无论错误原因是什么。包括连接失败、超时等。

2. connect

  • 功能:针对连接错误(如 DNS 解析错误、TCP 连接失败等)重试的次数。
  • 默认值None
  • 说明:如果为 None,则使用 total 的值。

3. read

  • 功能:控制在读取数据时(比如在连接成功后但在获取响应时出错)重试的次数。
  • 默认值None
  • 说明:如果为 None,则使用 total 的值。

4. redirect

  • 功能:重定向时允许的最大次数。
  • 默认值None
  • 说明:如果为 None,默认使用 total 的值。设置为 0 禁止重定向。

5. status

  • 功能:针对特定 HTTP 响应状态码(例如 500、502、503、504)重试的次数。
  • 默认值None
  • 说明:如果为 None,使用 total 的值。

6. method_whitelist

  • 功能:一个允许重试的 HTTP 方法的列表。
  • 默认值frozenset(['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'])
  • 说明:如果 HTTP 方法不在此列表中,则不进行重试。

7. status_forcelist

  • 功能:定义需要强制重试的 HTTP 状态码列表。
  • 默认值None
  • 说明:如果服务器返回此列表中的状态码,则强制重试。

8. backoff_factor

  • 功能:控制重试之间的等待时间,等待时间的增长因子(用于指数回退)。
  • 默认值0
  • 说明:重试间隔时间为 backoff_factor * (2 ** (retry次数 - 1))

9. raise_on_redirect

  • 功能:如果设置为 True,在重定向时抛出 MaxRetryError
  • 默认值True
  • 说明:用于控制是否在重定向次数超过限制时抛出异常。

10. raise_on_status

  • 功能:如果设置为 True,当重试次数超过最大值且遇到特定状态码时抛出异常。
  • 默认值False
  • 说明:当遇到 status_forcelist 中的状态码并超出重试次数时,抛出异常。

11. history

  • 功能:保存历史重试请求的列表,包含过去的所有重试信息。
  • 默认值None
  • 说明:可以跟踪重试的历史记录。

12. respect_retry_after_header

  • 功能:是否尊重服务器返回的 Retry-After 头信息。
  • 默认值True
  • 说明:如果服务器在响应中返回 Retry-After 头部,设置 True 时将等待指定的时间后再进行重试。

13. remove_headers_on_redirect

  • 功能:在重定向时移除的 HTTP 头信息列表。
  • 默认值frozenset(['Authorization'])
  • 说明:重定向时可以选择移除敏感的头部信息,如 Authorization

14. method_whitelist / allowed_methods (新的版本中使用 allowed_methods

  • 功能:允许重试的 HTTP 方法。
  • 默认值frozenset(['HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'])
  • 说明:控制哪些 HTTP 请求方法允许进行重试。

15. retry_on_exception

  • 功能:自定义逻辑,指定哪些异常可以触发重试。
  • 默认值None
  • 说明:接受一个回调函数,返回 True 则重试。

16. retry_on_status

  • 功能:自定义逻辑,指定哪些 HTTP 状态码可以触发重试。
  • 默认值None
  • 说明:接受一个回调函数,返回 True 则重试。

实战

以下示例演示了如何应用自定义的重试策略来处理特定的 HTTP 状态码。

import logging

import requests
from requests.adapters import HTTPAdapter
from urllib3 import Retry

# 开启 urllib3 的日志,方便查看重试过程
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
urllib3_logger = logging.getLogger('urllib3')
urllib3_logger.setLevel(logging.DEBUG)

retry_strategy = Retry(
    total=5,  # 总共重试 5 次
    connect=3,  # 连接错误时重试 3 次
    read=2,  # 读取错误时重试 2 次
    redirect=3,  # 重定向时最多重试 3 次
    status_forcelist=[500, 502, 503, 504],  # 对于这些 HTTP 状态码强制重试
    backoff_factor=1,  # 等待时间因子:指数递增,如 1s, 2s, 4s...
    raise_on_status=False  # 达到最大重试次数后不抛异常
)
http_adapter = HTTPAdapter(max_retries=retry_strategy)
# 使用 session 发送请求
session = requests.session()
# 打印 adapters
print(session.adapters)
session.mount('https://', http_adapter)
session.mount('http://', http_adapter)
# 发送请求
try:
    response = session.get("https://httpbin.org/status/500")  # 故意发送到一个会返回 500 错误的 URL
    print(response.status_code)
except requests.exceptions.RetryError as e:
    print(f"达到最大重试次数,重试失败: {e}")
except requests.exceptions.RequestException as e:
    print(f"请求失败: {e}")

在上面的代码中,定义了一个自定义的 Retry 类,当我们访问 https://httpbin.org/status/500(它返回 500 错误状态码),请求会自动按照指定策略进行重试,并打印重试过程中的日志,运行结果为:

image-20241003184720906

查看运行结果,总共请求了 6 次,其中 5 次是由于状态码 500 进行的重试,每次重试的间隔时间按设定的因子指数增长。

并且每次重试后,总重试次数都在减少,最终减少到 0。

大家可以按照自己的需求来初始化 Retry 类,可以更加方便的进行重试。

如果以上参数都不能满足要求,可以使用 retry_on_status 或 retry_on_exception 在发生指定异常或者指定状态码时执行自定义函数,可以更加灵活的实现重试逻辑。

在实际开发中,利用 Requests 库的 Retry 类可以灵活应对非网络问题的重试需求。大家可以根据项目的具体情况,自定义重试参数。

retry_strategy = Retry(
    # 自行定义参数,默认不重试
)
http_adapter = HTTPAdapter(max_retries=retry_strategy)

LLLibra146
35 声望6 粉丝

会修电脑的程序员