关于python:我可以为requests.request设置max_retries吗?

Can I set max_retries for requests.request?

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

1
Max retries exceeded with url: ...

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
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,...]。如果返回的状态代码为500,502,503或504,它也会强制重试。

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

  • total - 允许的重试总次数。
  • connect - 要重试的与连接相关的错误数。
  • 读取 - 重试读取错误的次数。
  • 重定向 - 要执行的重定向数。
  • 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服务器,我将使用上面的代码片段,它强制重试状态500,502,503和504,所有这些都不常见于在足够大的退避期间,网络和(可能)可恢复。

编辑:直接从urllib3导入Retry类。


它是基础urllib3库进行重试。要设置不同的最大重试计数,请使用备用传输适配器:

1
2
3
4
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.如果您确实要将其设置为更高的值,则必须全局设置:

1
2
3
import requests

requests.adapters.DEFAULT_RETRIES = 5

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

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


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

你可以这样做:

1
2
3
4
5
6
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))


在对这里的一些答案进行了一些努力之后,我找到了一个名为backoff的库,它对我的情况更有效。 一个基本的例子:

1
2
3
4
5
6
7
8
9
10
11
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()

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


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

我在这里创建了同样的东西:

Retry decorator with whitelisted exceptions in Python

重现该链接中的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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():

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