Kann ich max_retries für request.request festlegen?

180

Das Python-Anforderungsmodul ist einfach und elegant, aber eines nervt mich. Es ist möglich, eine request.exception.ConnectionError mit einer Nachricht wie der folgenden zu erhalten:

Max retries exceeded with url: ...

Dies bedeutet, dass Anforderungen mehrmals versuchen können, auf die Daten zuzugreifen. Diese Möglichkeit wird jedoch nirgendwo in den Dokumenten erwähnt. Beim Betrachten des Quellcodes habe ich keinen Ort gefunden, an dem ich den Standardwert (vermutlich 0) ändern könnte.

Ist es also möglich, die maximale Anzahl von Wiederholungsversuchen für Anforderungen festzulegen?

Kirill Zaitsev
quelle
9
Irgendwelche Updates dazu mit Anfragen bei 2.x? Würde eine Implementierung von request.get (url, max_retries = num_max_retries)) lieben.
Paragbaxi
11
@ Paragbaxi: und noch besser einrequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
WoJ
1
@WoJ Ich nahm Ihre Beispiele und machte es Wirklichkeit;) in just.getund just.postin github.com/kootenpv/just
PascalVKooten
2
Nützlicher Artikel über Wiederholungsversuche mit Anfragen: peterbe.com/plog/best-practice-with-retries-with-requests
Gokul

Antworten:

160

Es ist die zugrunde liegende urllib3Bibliothek, die die Wiederholung durchführt. Verwenden Sie alternative Transportadapter, um eine andere maximale Anzahl von Wiederholungsversuchen festzulegen :

from requests.adapters import HTTPAdapter

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

Das max_retriesArgument nimmt eine Ganzzahl oder ein Retry()Objekt an . Letzteres gibt Ihnen eine genaue Kontrolle darüber, welche Arten von Fehlern wiederholt werden (ein ganzzahliger Wert wird in eine Retry()Instanz umgewandelt, die nur Verbindungsfehler behandelt; Fehler nach dem Herstellen einer Verbindung werden standardmäßig nicht behandelt, da dies zu Nebenwirkungen führen kann). .


Alte Antwort vor der Freigabe von Anfragen 1.2.1 :

Die requestsBibliothek macht dies nicht wirklich konfigurierbar und beabsichtigt dies auch nicht (siehe diese Pull-Anfrage ). Derzeit (Anforderungen 1.1) ist die Anzahl der Wiederholungsversuche auf 0 festgelegt. Wenn Sie den Wert wirklich auf einen höheren Wert festlegen möchten, müssen Sie diesen Wert global festlegen:

import requests

requests.adapters.DEFAULT_RETRIES = 5

Diese Konstante ist nicht dokumentiert; Verwenden Sie es auf eigene Gefahr, da zukünftige Versionen die Vorgehensweise ändern können.

Update : und das hat sich geändert; In Version 1.2.1 wurde die Option zum Festlegen des max_retriesParameters für die HTTPAdapter()Klasse hinzugefügt, sodass Sie jetzt alternative Transportadapter verwenden müssen (siehe oben). Der Monkey-Patch-Ansatz funktioniert nicht mehr, es sei denn, Sie patchen auch die HTTPAdapter.__init__()Standardeinstellungen (sehr wenig empfohlen).

Martijn Pieters
quelle
9
Sie müssen dies nicht für jede Site angeben, wenn dies nicht benötigt wird. Sie können dies einfach session.mount('http://', HTTPAdapter(max_retries=10))für alle http-Verbindungen tun . Das gleiche mit https funktioniert dann für alle https-Verbindungen.
user136036
1
@ user136036: Ja, Adapter werden nach der längsten Präfixübereinstimmung gesucht. Wenn Sie möchten, dass dies auf alle URLs angewendet wird http://und https://die minimalen Präfixe sind, lesen Sie die Dokumentation, auf die die Antwort verweist .
Martijn Pieters
1
Beachten Sie, dass dies HTTPAdapter(max_retries=5)nur für bestimmte Szenarien funktioniert. Von Anfragen 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.um Kraft Wiederholungs für alle Statuscodes, siehe @ datashaman Antwort unten.
Steven Xu
@StevenXu: Ja, Sie können konfigurieren Retry(), um zu ändern, welche Fehlerszenarien wiederholt werden.
Martijn Pieters
224

Dies ändert nicht nur die max_retries, sondern ermöglicht auch eine Backoff-Strategie, die Anforderungen an alle http: // Adressen für einen bestimmten Zeitraum in den Ruhezustand versetzt, bevor sie es erneut versuchen (insgesamt 5 Mal):

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

Gemäß DokumentationRetry : Wenn der backoff_factor ist 0,1 , dann sleep () wird tief und für [0,1s, 0,2s, 0,4s, ...] zwischen den Wiederholungen. Es wird auch ein erneuter Versuch erzwungen, wenn der zurückgegebene Statuscode 500 , 502 , 503 oder 504 lautet .

Verschiedene andere Optionen Retryfür eine detailliertere Steuerung:

  • total - Gesamtzahl der zulässigen Wiederholungsversuche.
  • connect - Wie viele verbindungsbezogene Fehler müssen wiederholt werden?
  • read - Wie oft müssen Lesefehler wiederholt werden?
  • Weiterleitung - Wie viele Weiterleitungen müssen ausgeführt werden?
  • method_whitelist - Satz von HTTP-Methodenverben in Großbuchstaben, die wir erneut versuchen sollten.
  • status_forcelist - Eine Reihe von HTTP-Statuscodes, für die ein sollte.
  • backoff_factor - Ein Backoff-Faktor, der zwischen Versuchen angewendet werden soll .
  • raise_on_redirect - Gibt an , ob bei erschöpfter Anzahl von Weiterleitungen MaxRetryErroreine Antwort ausgelöst oder eine Antwort mit einem Antwortcode im Bereich 3xx zurückgegeben werden soll.
  • raise_on_status - Ähnliche Bedeutung wie raise_on_redirect : Gibt an, ob eine Ausnahme ausgelöst oder eine Antwort zurückgegeben werden soll, wenn der Status in den Bereich status_forcelist fällt und die Wiederholungsversuche erschöpft sind.

NB : raise_on_status ist relativ neu und hat es noch nicht in eine Version von urllib3 oder Anfragen geschafft. DasSchlüsselwortargument raise_on_status scheint es in Python Version 3.6 höchstens in die Standardbibliothek geschafft zu haben.

Verwenden Sie status_forcelist, um Anforderungen für bestimmte HTTP-Statuscodes erneut auszuführen . Beispielsweise versucht status_forcelist = [503] den Statuscode 503 erneut (Dienst nicht verfügbar).

Standardmäßig wird der Wiederholungsversuch nur unter folgenden Bedingungen ausgelöst:

  • Es konnte keine Verbindung aus dem Pool hergestellt werden.
  • TimeoutError
  • HTTPExceptionausgelöst (von http.client in Python 3 sonst httplib ). Dies scheinen HTTP-Ausnahmen auf niedriger Ebene zu sein, z. B. URL oder Protokoll, die nicht korrekt gebildet wurden.
  • SocketError
  • ProtocolError

Beachten Sie, dass dies alles Ausnahmen sind, die den Empfang einer regulären HTTP-Antwort verhindern. Wenn eine regelmäßige Antwort generiert wird, wird kein erneuter Versuch durchgeführt. Ohne die status_forcelist zu verwenden wird auch eine Antwort mit Status 500 nicht wiederholt.

Um es auf eine Weise zu verhalten, die für die Arbeit mit einer Remote-API oder einem Webserver intuitiver ist, würde ich das obige Code-Snippet verwenden, das Wiederholungsversuche für die Status 500 , 502 , 503 und 504 erzwingt , die alle auf dem nicht ungewöhnlich sind Web und (möglicherweise) wiederherstellbar bei einer ausreichend großen Backoff-Periode.

BEARBEITET : RetryKlasse direkt aus urllib3 importieren .

Datashaman
quelle
1
Ich versuche, Ihre Logik zu implementieren, aber ich weiß nicht, ob sie funktioniert, da im Protokoll nur eine Anforderung angezeigt wird, selbst wenn der Res-Status 503 lautet. Wie kann ich feststellen, ob die Wiederholung funktioniert? Siehe den Code: pastebin.com/rty4bKTw
Danilo Oliveira
1
Der angehängte Code funktioniert wie erwartet. Der Trick ist der Parameter status_forcelist . Dies weist das urllib3-Paket an, bestimmte Statuscodes erneut zu versuchen. Code: pastebin.com/k2bFbH7Z
datashaman
1
urllib3 glaubt nicht (und sollte nicht), dass der Status 503 eine Ausnahme ist (standardmäßig).
Datashaman
1
@Connor nein, der Adapter ist an die Sitzung angeschlossen.
Datashaman
1
urlib3.Retry ist nicht mehr Teil von Anfragen. diese müssen direkt importiert werden. Vorgeschlagene Bearbeitung
user2390183
59

Seien Sie vorsichtig, Martijn Pieters Antwort ist nicht für Version 1.2.1+ geeignet. Sie können es nicht global festlegen, ohne die Bibliothek zu patchen.

Sie können dies stattdessen tun:

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
quelle
22
Gute Lösung, aber beachten Sie, dass es keine Verzögerung zwischen den erneuten Versuchen gibt. Wenn Sie zwischen den Versuchen schlafen möchten, müssen Sie Ihre eigenen rollen.
Nofinator
18

Nachdem ich ein bisschen mit einigen der Antworten hier zu kämpfen hatte, fand ich eine Bibliothek namens Backoff , die für meine Situation besser funktionierte. Ein einfaches Beispiel:

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

Ich würde weiterhin empfehlen, die native Funktionalität der Bibliothek zu testen. Wenn Sie jedoch auf Probleme stoßen oder eine umfassendere Kontrolle benötigen, ist Backoff eine Option.

Brad Koch
quelle
1
tolle Bibliothek, danke! Ich brauchte diese Funktionalität für etwas anderes als requests, also funktioniert das perfekt!
Dennis Golomazov
3

Ein sauberer Weg, um eine höhere Kontrolle zu erlangen, könnte darin bestehen, das Wiederholungsmaterial in eine Funktion zu packen und diese Funktion mithilfe eines Dekorateurs abrufbar zu machen und die Ausnahmen auf die Whitelist zu setzen.

Ich habe das gleiche hier erstellt: http://www.praddy.in/retry-decorator-whitelisted-exceptions/

Wiedergabe des Codes in diesem Link:

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
quelle