如何在python请求库中实现重试机制?

如何在python请求库中实现重试机制?,python,http,python-requests,Python,Http,Python Requests,我想在python请求库中添加一个重试机制,这样使用它的脚本将重试非致命错误 现在我考虑三种错误是可恢复的: HTTP返回代码502、503、504 找不到主机(现在不太重要) 请求超时 在第一阶段,我确实希望每分钟重试指定的5xx请求 我希望能够透明地添加此功能,而不必手动实现从这些使用python请求的脚本或库中进行的每个HTTP调用的恢复。这是我用于重试使用urllib2进行请求的代码片段。也许您可以将其用于您的目的: retries = 1 success = False while

我想在python请求库中添加一个重试机制,这样使用它的脚本将重试非致命错误

现在我考虑三种错误是可恢复的:

  • HTTP返回代码502、503、504
  • 找不到主机(现在不太重要)
  • 请求超时
在第一阶段,我确实希望每分钟重试指定的5xx请求


我希望能够透明地添加此功能,而不必手动实现从这些使用python请求的脚本或库中进行的每个HTTP调用的恢复。

这是我用于重试使用urllib2进行请求的代码片段。也许您可以将其用于您的目的:

retries = 1
success = False
while not success:
    try:
        response = urllib2.urlopen(request)
        success = True
    except Exception as e:
        wait = retries * 30;
        print 'Error! Waiting %s secs and re-trying...' % wait
        sys.stdout.flush()
        time.sleep(wait)
        retries += 1

等待时间会逐渐增加,以避免被禁止进入服务器。

通过扩展
请求。会话
类,我能够获得所需的可靠性级别

这是密码

编辑该代码是:

from requests import Session
from requests.exceptions import ConnectionError
import logging
import time


class ResilientSession(Session):

    """
    This class is supposed to retry requests that do return temporary errors.

    At this moment it supports: 502, 503, 504
    """

    def __recoverable(self, error, url, request, counter=1):
        if hasattr(error,'status_code'):
            if error.status_code in [502, 503, 504]:
                error = "HTTP %s" % error.status_code
            else:
                return False
        DELAY = 10 * counter
        logging.warn("Got recoverable error [%s] from %s %s, retry #%s in %ss" % (error, request, url, counter, DELAY))
        time.sleep(DELAY)
        return True


    def get(self, url, **kwargs):
        counter = 0
        while True:
            counter += 1
            try:
                r = super(ResilientSession, self).get(url, **kwargs)
            except ConnectionError as e:
                r = e.message
            if self.__recoverable(r, url, 'GET', counter):
                continue
            return r

    def post(self, url, **kwargs):
        counter = 0
        while True:
            counter += 1
            try:
                r = super(ResilientSession, self).post(url, **kwargs)
            except ConnectionError as e:
                r = e.message
            if self.__recoverable(r, url, 'POST', counter):
                continue
            return r

    def delete(self, url, **kwargs):
        counter = 0
        while True:
            counter += 1
            try:
                r = super(ResilientSession, self).delete(url, **kwargs)
            except ConnectionError as e:
                r = e.message
            if self.__recoverable(r, url, 'DELETE', counter):
                continue
            return r

    def put(self, url, **kwargs):
        counter = 0
        while True:
            counter += 1
            try:
                r = super(ResilientSession, self).put(url, **kwargs)
            except ConnectionError as e:
                r = e.message

            if self.__recoverable(r, url, 'PUT', counter):
                continue
            return r

    def head(self, url, **kwargs):
        counter = 0
        while True:
            counter += 1
            try:
                r = super(ResilientSession, self).head(url, **kwargs)
            except ConnectionError as e:
                r = e.message
            if self.__recoverable(r, url, 'HEAD', counter):
                continue
            return r

    def patch(self, url, **kwargs):
        counter = 0
        while True:
            counter += 1
            try:
                r = super(ResilientSession, self).patch(url, **kwargs)
            except ConnectionError as e:
                r = e.message

            if self.__recoverable(r, url, 'PATCH', counter):
                continue
            return r

    def options(self, url, **kwargs):
        counter = 0
        while True:
            counter += 1
            try:
                r = super(ResilientSession, self).options(url, **kwargs)
            except ConnectionError as e:
                r = e.message

            if self.__recoverable(r, url, 'OPTIONS', counter):
                continue
            return r

这段代码将使来自同一会话的所有HTTP请求总共重试5次,在两次重试之间休眠,并增加0、2、4、8、16秒的退避(第一次重试立即完成)。它将重试基本连接问题(包括DNS查找失败)和HTTP状态代码502、503和504

import logging
import requests

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

logging.basicConfig(level=logging.DEBUG)

s = requests.Session()
retries = Retry(total=5, backoff_factor=1, status_forcelist=[ 502, 503, 504 ])
s.mount('http://', HTTPAdapter(max_retries=retries))

s.get("http://httpstat.us/503")
有关详细信息,请参阅。

使用


如果在t1=1秒、t2=2秒、t3=4秒的时间间隔内发生异常,则重试某些逻辑的方法。 我们也可以增加/减少时间间隔

MAX_RETRY = 3
retries = 0

try:

    call_to_api() // some business logic goes here.

except Exception as exception:

    retries += 1
    if retries <= MAX_RETRY:
        print("ERROR=Method failed. Retrying ... #%s", retries)
        time.sleep((1 << retries) * 1) // retry happens after time as a exponent of 2
        continue
    else:
        raise Exception(exception)
MAX\u RETRY=3
重试次数=0
尝试:
调用\到\ api()//这里有一些业务逻辑。
例外情况除外:
重试次数+=1


如果重试确实有帮助?@thefourtheye:这只适用于传输级错误;套接字超时、SSL错误等。不包括500范围内的服务器返回代码。python请求是否处理状态代码429?遗憾的是,大多数网站在限制速率时发送不适当的代码(如503和404)。请求包括urllib3重试类的副本(在requests.packages.util.Retry.Retry中),这将允许细粒度控制,并包括用于重试的退避机制。对于基于状态的重试,请使用参数:status\u forcelist,该参数将强制根据所选策略重试特定的状态代码响应。@datashaman I已经实现了所谓的ResilientSession,它以透明的方式处理此问题。看看我在JIRA Python库中的实现,你应该把代码复制粘贴到这里。你不应该用非现场的链接来回答问题。另外,TBH,你的代码可能要短得多。我认为将它重组为一个decorator,您将拥有一个更短、更少重复的解决方案。仍然有一些重复(所有方法都有相同的模式),但它的工作原理完全相同,您只需在一个地方更改逻辑:一个更简洁的版本,对请求方法进行重试,删除代码中的所有重复:我知道这段代码已经有5年的历史了,但它的质量非常低。@datashaman的答案应该被接受,这样这个答案就不会受到太多的关注。你的答案是一个简单的try-except公式,没有调用不同的库和函数。我宁愿在循环中显式使用“continue”,默认情况下使用break。只是为了避免错误的无限循环。为了限制重试次数,我可以在不成功时使用
,重试次数<5:
根据文档,第一次退避是0,而不是1。所以它就像0s,2s,4s,8s,16s session.mount的第一个参数的意义是什么,
http://
?它有什么用途?@JamesWierzba它是一种连接到适配器的模式。因此,在本例中,所有以http://
开头的URL都将使用该适配器。您可以为
https://cnn.com
例如。当访问
https://cnn.com 
和其他所有方法的默认值。在Retry类中查找
方法\u白名单
选项,默认情况下,除了
['HEAD','TRACE','GET','PUT','OPTIONS','DELETE',它不会在http方法上重试
-这些方法预计不会有副作用set method_whitelist=False也会重试API后调用。例如,如果您正在与GraphQL端点通信,则所有调用都是POST。默认情况下,只重试['HEAD','TRACE','GET','PUT','OPTIONS','DELETE'],因为它们被视为安全/幂等操作。是否需要将
中的
self.session.GET
调用\u-to\u api()
包装在try块中?
MAX_RETRY = 3
retries = 0

try:

    call_to_api() // some business logic goes here.

except Exception as exception:

    retries += 1
    if retries <= MAX_RETRY:
        print("ERROR=Method failed. Retrying ... #%s", retries)
        time.sleep((1 << retries) * 1) // retry happens after time as a exponent of 2
        continue
    else:
        raise Exception(exception)
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry


MAX_RETRY = 2
MAX_RETRY_FOR_SESSION = 2
BACK_OFF_FACTOR = 0.3
TIME_BETWEEN_RETRIES = 1000
ERROR_CODES = (500, 502, 504)


def requests_retry_session(retries=MAX_RETRY_FOR_SESSION,
    back_off_factor=BACK_OFF_FACTOR,
    status_force_list=ERROR_CODES, 
    session=None):
       session = session  
       retry = Retry(total=retries, read=retries, connect=retries,
                     backoff_factor=back_off_factor,
                     status_forcelist=status_force_list,
                     method_whitelist=frozenset(['GET', 'POST']))
       adapter = HTTPAdapter(max_retries=retry)
       session.mount('http://', adapter)
       session.mount('https://', adapter)
       return session



class ConfigService:

   def __init__(self):
      self.session = requests_retry_session(session=requests.Session())

   def call_to_api():
      config_url = 'http://localhost:8080/predict/'
      headers = {
        "Content-Type": "application/json",
        "x-api-key": self.x_api_key
      } 
      response = self.session.get(config_url, headers=headers)
      return response