262

Python requests 模块简单而优雅,但有一件事让我很烦恼。可以通过如下消息获取requests.exception.ConnectionError

Max retries exceeded with url: ...

这意味着请求可以尝试多次访问数据。但是文档中的任何地方都没有提到这种可能性。查看源代码,我没有找到可以更改默认值(大概为 0)的任何地方。

那么是否有可能以某种方式设置请求的最大重试次数?

4

6 回答 6

351

这不仅会改变max_retries还会启用退避策略,使对所有http://地址的请求在重试之前休眠一段时间(总共 5 次):

import requests

from requests.adapters import HTTPAdapter, Retry

s = requests.Session()

retries = Retry(total=5,
                backoff_factor=0.1,
                status_forcelist=[ 500, 502, 503, 504 ])

s.mount('http://', HTTPAdapter(max_retries=retries))

s.get('http://httpstat.us/500')

根据文档Retry:如果 backoff_factor 为0.1,则 sleep() 将在重试之间休眠 [0.05s, 0.1s, 0.2s, 0.4s, ...] 。如果返回的状态码是500502503504 ,它也会强制重试。

Retry允许更精细控制的各种其他选项:

  • total – 允许的重试总数。
  • connect – 重试多少个与连接相关的错误。
  • read – 重试读取错误的次数。
  • redirect – 要执行多少次重定向。
  • method_whitelist – 我们应该重试的一组大写 HTTP 方法动词。
  • status_forcelist – 我们应该强制重试的一组 HTTP 状态代码。
  • backoff_factor – 在尝试之间应用的退避因子。
  • raise_on_redirect – 如果重定向次数用尽,是否引发 aMaxRetryError或返回响应代码在3xx范围内的响应。
  • raise_on_status - 与 raise_on_redirect 类似的含义:如果状态落在status_forcelist范围内并且重试已用尽,我们是否应该引发异常或返回响应。

注意raise_on_status相对较新,还没有发布到 urllib3 或 requests 中。raise_on_status关键字参数似乎最多在 python 3.6 版中进入了标准库

要在特定 HTTP 状态代码上重试请求,请使用status_forcelist。例如,status_forcelist=[503]将重试状态码503(服务不可用)。

默认情况下,重试仅在以下情况下触发:

  • 无法从池中获得连接。
  • TimeoutError
  • HTTPException提出(来自Python 3 中的http.client其他httplib)。这似乎是低级 HTTP 异常,例如 URL 或协议格式不正确。
  • SocketError
  • ProtocolError

请注意,这些都是阻止接收常规 HTTP 响应的所有异常。如果生成任何常规响应,则不进行重试。如果不使用status_forcelist,即使是状态为 500 的响应也不会被重试。

为了使其以更直观的方式与远程 API 或 Web 服务器一起工作,我将使用上面的代码片段,它强制重试状态500502503504,所有这些在web 并且(可能)在足够大的退避期的情况下可恢复。

于 2016-02-19T11:50:58.540 回答
208

重试是底层urllib3库。要设置不同的最大重试次数,请使用替代传输适配器

from requests.adapters import HTTPAdapter

s = requests.Session()
s.mount('http://stackoverflow.com', HTTPAdapter(max_retries=5))

参数采用max_retries整数或Retry()对象;后者使您可以对重试哪些类型的故障进行细粒度控制(整数值转换为Retry()仅处理连接故障的实例;默认情况下不处理连接后的错误,因为这些可能会导致副作用) .


旧答案,早于请求 1.2.1 的发布

requests库并没有真正做到这一点,也没有打算这样做(参见这个拉取请求)。当前(请求 1.1),重试次数设置为 0。如果您真的想将其设置为更高的值,则必须全局设置:

import requests

requests.adapters.DEFAULT_RETRIES = 5

这个常数没有记录;使用它需要您自担风险,因为未来的版本可能会改变处理方式。

更新:这确实改变了;在 1.2.1 版本中,添加了在类上设置max_retries参数的选项,因此现在您必须使用替代传输适配器,见上文。猴子补丁方法不再有效,除非您也修补默认值(非常不推荐)。HTTPAdapter()HTTPAdapter.__init__()

于 2013-03-15T11:33:22.577 回答
68

请注意,Martijn Pieters 的答案不适合 1.2.1+ 版本。如果不修补库,则无法全局设置它。

你可以这样做:

import requests
from requests.adapters import HTTPAdapter

s = requests.Session()
s.mount('http://www.github.com', HTTPAdapter(max_retries=5))
s.mount('https://www.github.com', HTTPAdapter(max_retries=5))
于 2013-08-12T15:03:10.123 回答
30

在与这里的一些答案苦苦挣扎之后,我发现了一个名为backoff的库,它更适合我的情况。一个基本的例子:

import backoff

@backoff.on_exception(
    backoff.expo,
    requests.exceptions.RequestException,
    max_tries=5,
    giveup=lambda e: e.response is not None and e.response.status_code < 500
)
def publish(self, data):
    r = requests.post(url, timeout=10, json=data)
    r.raise_for_status()

我仍然建议尝试一下库的本机功能,但如果您遇到任何问题或需要更广泛的控制,退避是一种选择。

于 2017-11-06T21:52:16.843 回答
6

获得更高控制权的一种更简洁的方法可能是将重试内容打包到一个函数中,并使用装饰器使该函数可重试,并将异常列入白名单。

我在这里创建了相同的:http: //www.praddy.in/retry-decorator-whitelisted-exceptions/

复制该链接中的代码:

def retry(exceptions, delay=0, times=2):
"""
A decorator for retrying a function call with a specified delay in case of a set of exceptions

Parameter List
-------------
:param exceptions:  A tuple of all exceptions that need to be caught for retry
                                    e.g. retry(exception_list = (Timeout, Readtimeout))
:param delay: Amount of delay (seconds) needed between successive retries.
:param times: no of times the function should be retried


"""
def outer_wrapper(function):
    @functools.wraps(function)
    def inner_wrapper(*args, **kwargs):
        final_excep = None  
        for counter in xrange(times):
            if counter > 0:
                time.sleep(delay)
            final_excep = None
            try:
                value = function(*args, **kwargs)
                return value
            except (exceptions) as e:
                final_excep = e
                pass #or log it

        if final_excep is not None:
            raise final_excep
    return inner_wrapper

return outer_wrapper

@retry(exceptions=(TimeoutError, ConnectTimeoutError), delay=0, times=3)
def call_api():
于 2017-02-15T11:46:32.457 回答
1

您可以使用 requests 库一次性完成所有操作。如果您收到 429,500,502,503 或 504 状态码,以下代码将重试 3 次,每次都通过“backoff_factor”设置更长的延迟。有关不错的教程,请参阅https://findwork.dev/blog/advanced-usage-python-requests-timeouts-retries-hooks/

from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[429, 500, 502, 503, 504],
    method_whitelist=["HEAD", "GET", "OPTIONS"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
http = requests.Session()
http.mount("https://", adapter)
http.mount("http://", adapter)

response = http.get("https://en.wikipedia.org/w/api.php")
于 2021-12-13T14:43:49.657 回答