Чи можу я встановити 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: а ще краще arequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
WoJ

1
@WoJ Я взяв ваші приклади і зробив це реальністю;) в just.getі just.postв github.com/kootenpv/just
PascalVKooten

2
Корисна стаття про повторні спроби із запитами
Гокул

Відповіді:


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-з'єднань.
користувач136036

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()щоб змінити сценарії відмов, які будуть повторені.
Martijn Pieters

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 , режим сну () буде спати протягом [0,1s, 0,2s, 0,4s, ...] між повторними спробами. Також буде застосовано повторне повторне повторення, якщо повернутий код стану 500 , 502 , 503 або 504 .

Різні інші параметри Retryдля більш детального контролю:

  • total - загальна кількість повторних спроб.
  • connect - Скільки помилок, пов’язаних із підключенням, щоб повторити спробу.
  • read - Скільки разів повторити помилки читання.
  • переадресація - скільки переадресацій виконувати.
  • method_whitelist - набір верхніх значень дієслів методу HTTP, з якими нам слід спробувати.
  • status_forcelist - Набір кодів HTTP-статусу, на який нам слід змусити повторити.
  • backoff_factor - Коефіцієнт, що застосовується між спробами.
  • підвищити_on_redirect - чи вичерпано кількість переадресацій, підняти а MaxRetryErrorчи повернути відповідь з кодом відповіді в діапазоні 3xx .
  • підвищити_on_status - Подібне значення, як підвищити_on_redirect : чи слід підняти виняток, чи повернути відповідь, якщо статус потрапляє у діапазон status_forcelist і спроби вичерпані.

Примітка : підвищення_on_status є відносно новим, і він ще не перетворив його на випуск urllib3 або запитів. Raise_on_status аргумент ключового словаздається, зробили це в стандартну бібліотеку найбільш пітон версії 3.6.

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

За замовчуванням повторний повтор спрацьовує лише для цих умов:

  • Не вдалося отримати з'єднання з пулом.
  • TimeoutError
  • HTTPExceptionпідвищений (з http.client в Python 3 else httplib ). Це здається, що винятки HTTP низького рівня, наприклад, URL-адреса чи протокол, сформовані неправильно.
  • SocketError
  • ProtocolError

Зауважте, що це всі винятки, які не дозволяють отримувати регулярну відповідь HTTP. Якщо генерується якась регулярна відповідь, повторне повторення не робиться. Без використання status_forcelist навіть відповідь зі статусом 500 не буде повторена.

Щоб змусити його вести себе більш інтуїтивно зрозуміло для роботи з віддаленим API або веб-сервером, я б використав описаний вище фрагмент коду, який змушує повторно використовувати статуси 500 , 502 , 503 та 504 , і все це не рідкість у Інтернету та (можливо) відновлюваного з огляду на достатньо великий період часу.

ВЕДЕНО : Retryклас імпорту безпосередньо з urllib3 .


1
Я намагаюся реалізувати вашу логіку, але я не знаю, чи працює вона, тому що в журналі відображається лише один запит, навіть статус res є 503. Як я можу знати, чи працює повтор? Дивіться код: pastebin.com/rty4bKTw
Данило Олівейра

1
Доданий код працює як очікувалося. Трюк - це параметр status_forcelist . Це вказує пакунку urllib3 повторити певні коди статусу. Код: pastebin.com/k2bFbH7Z
datashaman

1
urllib3 не (і не повинен) вважати, що статус 503 є винятком (за замовчуванням).
datashaman

1
@Connor немає, адаптер приєднаний до сеансу.
datashaman

1
urlib3.Retry більше не є частиною запитів. це доведеться імпортувати безпосередньо. Рекомендовано редагувати
користувач2390183

59

Будьте уважні, відповідь Мартійна Пітерса не підходить для версії 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

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

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


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():
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.