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


181

Python的请求模块既简单又优雅,但有一件事困扰着我。有可能得到一个 requests.exception.ConnectionError有这样的消息:

Max retries exceeded with url: ...

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

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


9
在2.x的请求上对此有任何更新吗?会喜欢一个request.get(url,max_retries = num_max_retries))实现。
paragbaxi 2014年

11
@paragbaxi:甚至更好requests.get(url, max_retries=num_max_retries, dely_between_retries=3))
-WoJ

1
@WoJ我以您的示例为现实;)在github.com/kootenpv/justjust.get及其内部just.post
PascalVKooten

2
关于重试请求的有用文章:peterbe.com/plog/best-practice-with-retries-with-requests
Gokul

Answers:


161

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__()


9
如果不需要,则不必为每个站点都指定此选项。您可以这样做session.mount('http://', HTTPAdapter(max_retries=10))将对所有HTTP连接有效。与https相同的内容将适用于所有https连接。
user136036 2015年

1
@ user136036:是的,通过最长的前缀匹配来查找适配器;如果您希望将其应用于所有网址,http://并且https://是使用的最少前缀,请参阅答案链接的文档。
马丁·彼得斯

1
请注意,HTTPAdapter(max_retries=5)这仅适用于某些情况。从请求文档中Note, this applies only to failed DNS lookups, socket connections and connection timeouts, never to requests where data has made it to the server. By default, Requests does not retry failed connections.要强制重试任何状态代码,请参见下面的@datashaman答案。
史蒂文·徐

@StevenXu:是的,您可以配置Retry()更改重试哪些失败方案。
马丁·彼得斯

224

这不仅会更改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')

根据文档说明Retry:如果backoff_factor为0.1,则sleep()将在重试之间睡眠[0.1s,0.2s,0.4s,...]。这也将迫使重试,如果返回的状态代码是500502503504

各种其他选择 Retry可以进行更精细的控制:

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

注意raise_on_status相对较新,尚未将其发布到urllib3或请求中。 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,所有这些都并不少见上网络和(可能)在足够大的退避期后可以恢复。

编辑Retry直接从urllib3导入类。


1
我正在尝试实现您的逻辑,但我不知道它是否有效,因为即使res状态为503,日志也只显示一个请求。我如何知道重试是否有效?参见代码:pastebin.com/rty4bKTw
Danilo Oliveira

1
附带的代码按预期方式工作。诀窍是status_forcelist参数。这告诉urllib3包重试特定的状态代码。代码:pastebin.com/k2bFbH7Z
datashaman

1
urllib3不(也不应该)认为状态503是一个异常(默认情况下)。
datashaman '16

1
@Connor不,适配器已连接到会话。
datashaman

1
urlib3.Retry不再是请求的一部分。这必须直接导入。建议编辑
user2390183 '19

59

请注意,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))

22
不错的解决方案,但请注意重试之间没有延迟。如果您想在两次尝试之间入睡,则需要自己动手。
nofinator 2013年

18

在为这里的一些答案苦苦挣扎之后,我找到了一个名为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()

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


1
很棒的图书馆,谢谢!除了以外requests,我还需要此功能,所以效果很好!
丹尼斯·哥洛马佐夫

3

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

我在这里创建了相同的文件: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():
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.