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

182

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

Max retries exceeded with url: ...

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

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

Кирилл Зайцев
источник
9
Любое обновление по этому вопросу с запросами в 2.x? Хотелось бы реализовать реализацию request.get (url, max_retries = num_max_retries)).
Парагбакси
11
@paragbaxi: а еще лучшеrequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
WoJ
1
@WoJ Я взял твои примеры и воплотил их в жизнь;) в just.getи just.postна github.com/kootenpv/just
PascalVKooten
2
Полезные статьи о повторах с запросами: peterbe.com/plog/best-practice-with-retries-with-requests
Gokul

Ответы:

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()классе , так что теперь вам нужно использовать альтернативные транспортные адаптеры, см. выше. Подход monkey-patch больше не работает, если только вы не исправляете HTTPAdapter.__init__()значения по умолчанию (очень не рекомендуется).

Мартейн Питерс
источник
9
Вам не нужно указывать это для каждого сайта, если это не нужно. Вы можете просто сделать session.mount('http://', HTTPAdapter(max_retries=10))это будет работать для всех подключений http. То же самое с https будет работать для всех соединений https.
user136036
1
@ user136036: да, адаптеры ищутся по самому длинному совпадению префикса; если вы хотите, чтобы это относилось ко всем URL-адресам, http://и https://используйте минимальные префиксы, см. документацию, на которую ссылается ответ.
Мартин Питерс
1
Обратите внимание, что это HTTPAdapter(max_retries=5)будет работать только для определенного сценария. Из запросов doc , 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()изменение сценариев сбоя.
Мартин Питерс
228

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

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

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

NB : повышение_он_стата является относительно новым и еще не превратило его в выпуск urllib3 или запросов. Raise_on_status аргумент ключевого словакажется, сделали это в стандартную библиотеку наиболее питон версии 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 .

datashaman
источник
1
Я пытаюсь реализовать вашу логику, но я не знаю, работает ли она, потому что в журнале просто показан один запрос, даже если статус res равен 503. Как я могу узнать, работает ли повтор? Смотрите код: pastebin.com/rty4bKTw
Данило Оливейра
1
Прикрепленный код работает как положено. Хитрость заключается в параметре status_forcelist . Это говорит пакету urllib3 повторить определенные коды состояния. Код: pastebin.com/k2bFbH7Z
datashaman
1
urllib3 не считает (и не должен) считать, что статус 503 является исключением (по умолчанию).
Даташаман
1
@Connor no, адаптер подключен к сеансу.
Даташаман
1
urlib3.Retry больше не является частью запросов. это нужно импортировать напрямую.
Предложенное
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))
Gizmondo
источник
22
Хорошее решение, но обратите внимание, что между попытками нет задержки. Если вы хотите поспать между попытками, вам придется свернуть свои собственные.
нофинатор
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():
praddy
источник