我可以为requests.request设置max_retries吗?

时间:2013-03-15 11:19:03

标签: python python-requests

Python请求模块简单而优雅,但有一点让我感到困惑。 可以使用以下消息获取 requests.exception.ConnectionError

Max retries exceeded with url: ...

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

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

6 个答案:

答案 0 :(得分:162)

这不仅会更改 max_retries ,还会启用退避策略,该策略会使所有 http:// 地址的请求在重试前暂停一段时间(到共5次):

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

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')

根据documentation for Retry:如果backoff_factor 0.1 ,则sleep()将在重试之间休眠[0.1s,0.2s,0.4s,...]。如果返回的状态代码为 500 502 503 504 ,它也会强制重试。

Retry的各种其他选项可以实现更精细的控制:

  • 总计 - 允许的重试总次数。
  • connect - 要重试的与连接相关的错误数。
  • 读取 - 重试读取错误的次数。
  • 重定向 - 要执行的重定向次数。
  • method_whitelist - 我们应该重试的大写HTTP方法动词集。
  • status_forcelist - 我们应该强制重试的一组HTTP状态代码。
  • backoff_factor - 在尝试之间应用的退避因子。
  • raise_on_redirect - 是否,如果重定向次数已用尽,则引发MaxRetryError,或返回带有 3xx 中的响应代码的响应范围。
  • raise_on_status - 与 raise_on_redirect 类似:如果状态属于 status_forcelist 范围并且重试已用尽,我们是应该引发异常还是返回响应。

NB raise_on_status 相对较新,尚未将其发送到urllib3或请求版本。 raise_on_status 关键字参数似乎在python版本3.6中最多进入标准库。

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

默认情况下,重试仅针对以下条件触发:

  • 无法从池中获取连接。
  • TimeoutError
  • HTTPException引发(来自Python中的 http.client ,其他 httplib )。 这似乎是低级HTTP异常,如URL或协议 形成正确。
  • SocketError
  • ProtocolError

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

为了让它以更直观的方式使用远程API或Web服务器,我会使用上面的代码片段,强制重试状态 500 502 503 504 ,所有这些在网络上并不罕见,并且(可能)在可以恢复的情况下具有足够大的退避时间。

已编辑:直接从 urllib3 导入Retry课程。

答案 1 :(得分:132)

执行重试的是基础urllib3库。要设置不同的最大重试次数,请使用alternative transport adapters

from requests.adapters import HTTPAdapter

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

max_retries参数采用整数或Retry() object;后者为您提供了对重试类型失败的细粒度控制(整数值转换为仅处理连接失败的Retry()实例;默认情况下不会处理连接后的错误,因为这些错误可导致副作用)。


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

requests库并没有真正使这个可配置,也不打算(见this pull request)。目前(请求1.1),重试次数设置为0.如果您确实要将其设置为更高的值,则必须全局设置:

import requests

requests.adapters.DEFAULT_RETRIES = 5

此常量未记录;因为未来版本可能会改变处理方式,所以使用它会让你自担风险。

更新:此更改;在版本1.2.1 the option to set the max_retries parameter上添加了HTTPAdapter() class,因此现在您必须使用备用传输适配器,请参见上文。猴子补丁方法不再有效,除非您还修补HTTPAdapter.__init__()默认值(非常不推荐)。

答案 2 :(得分:56)

小心,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))

答案 3 :(得分:11)

在对这里的一些答案进行了一些挣扎之后,我发现了一个名为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()

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

答案 4 :(得分:4)

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

我在这里创建了相同的内容: 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():

答案 5 :(得分:-5)

    while page is None:
        try:
            page = requests.get(url, timeout=5,proxies=proxies)
        except Exception:
            page = None