Могу ли я установить max_retries для request.request?

Модуль запросов Python прост и элегантен, но меня беспокоит одна вещь. Можно получить request.exception.ConnectionError с сообщением типа:

Max retries exceeded with url: ...

Это означает, что запросы могут пытаться получить доступ к данным несколько раз. Но нигде в документах не упоминается об этой возможности. Если посмотреть на исходный код, я не нашел места, где я мог бы изменить значение по умолчанию (предположительно 0).

Так можно ли как-то установить максимальное количество попыток для запросов?

Ответ 1

Это базовая библиотека urllib3, которая выполняет повторную попытку. Чтобы установить другой максимальный счетчик повторов, используйте альтернативные транспортные адаптеры:

from requests.adapters import HTTPAdapter

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

Аргумент max_retries принимает целое число или объект Retry(); последний дает вам мелкомасштабный контроль над тем, какие виды сбоев повторяются (целочисленное значение превращается в экземпляр Retry(), который обрабатывает только сбои соединения; ошибки после подключения выполняются по умолчанию, не обрабатываются, так как это может привести к сбою -effects).


Старый ответ, предшествующий выпуску запросов 1.2.1:

Библиотека requests на самом деле не делает эту конфигурацию и не намеревается (см. этот запрос на перенос). В настоящее время (запросы 1.1) счетчик попыток устанавливается равным 0. Если вы действительно хотите установить его на более высокое значение, вам нужно установить это глобально:

import requests

requests.adapters.DEFAULT_RETRIES = 5

Эта константа не документирована; используйте его на свой страх и риск, поскольку будущие выпуски могут изменить способ обработки.

Обновить: и это изменилось; в версии 1.2.1 возможность установить параметр max_retries на HTTPAdapter() class, так что теперь вам нужно использовать альтернативные транспортные адаптеры, см. выше. Подход обезьяны-патча больше не работает, если вы также не исправляете значения по умолчанию HTTPAdapter.__init__() (очень не рекомендуется).

Ответ 2

Это не только изменит 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,...] между повторными попытками. Также будет произведена повторная попытка, если возвращен код состояния 500, 502, 503 или 504.

Различные другие варианты Retry позволяют более детальный контроль:

  • итого - общее количество повторных попыток.
  • connect - Сколько ошибок, связанных с подключением, нужно повторить.
  • read - сколько раз повторить попытку чтения.
  • redirect - сколько перенаправлений выполнить.
  • method_whitelist - Набор прописных глаголов метода HTTP, к которым мы должны повторить попытку.
  • status_forcelist - набор кодов состояния HTTP, которые мы должны принудительно повторить.
  • backoff_factor - Коэффициент отката, применяемый между попытками.
  • подъем_он_редакта - следует ли, если количество перенаправлений исчерпано, вызвать MaxRetryError или вернуть ответ с кодом ответа в диапазоне 3xx.
  • повышение_он_статуса - значение, аналогичное повышению_он_редакта: следует ли нам вызывать исключение или возвращать ответ, если состояние падает в диапазоне состояния_соглашения и повторные попытки были исчерпаны.

NB: повышение_он_стата является относительно новым и еще не превратило его в выпуск urllib3 или запросов. Похоже, что аргумент ключевого слова rise_on_status вошел в стандартную библиотеку максимум в версии Python 3.6.

Чтобы повторять запросы на определенные коды состояния HTTP, используйте status_forcelist. Например, status_forcelist = [503] будет повторять попытку с кодом состояния 503 (услуга недоступна).

По умолчанию повтор запускается только для следующих условий:

  • Не удалось получить соединение из пула.
  • TimeoutError
  • HTTPException (из http.client в Python 3 или httplib). Похоже, что это низкоуровневые исключения HTTP, такие как URL или протокол сформированы неправильно.
  • SocketError
  • ProtocolError

Обратите внимание, что все это исключения, которые препятствуют получению регулярного ответа HTTP. Если генерируется какой-либо регулярный ответ, повтор не выполняется. Без использования status_forcelist, даже ответ со статусом 500 не будет повторен.

Чтобы заставить его вести себя более интуитивно для работы с удаленным API или веб-сервером, я бы использовал приведенный выше фрагмент кода, который заставляет повторять попытки для состояний 500, 502, 503 и 504, которые все нередки на сеть и (возможно) восстанавливаемый, учитывая достаточно большой период отсрочки.

РЕДАКТИРОВАНИЕ: Импорт класса Retry напрямую из urllib3.

Ответ 3

Будьте осторожны, ответ 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))

Ответ 4

Немного поразмыслив с некоторыми из ответов, я нашел библиотеку под названием 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()

Я бы по-прежнему рекомендовал дать возможность нативной функциональности библиотеки, но если у вас возникнут какие-либо проблемы или вам понадобится более широкий контроль, можно воспользоваться откатом.

Ответ 5

Более чистый способ получить более высокий контроль может заключаться в том, чтобы упаковать вещи повтора в функцию и сделать эту функцию возвращенной с помощью декоратора и исключить белые списки исключений.

Я создал то же самое здесь: 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():

Ответ 6

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