Posso impostare max_retries per request.request?


182

Il modulo richieste di Python è semplice ed elegante, ma una cosa mi dà fastidio. È possibile ottenere un request.exception.ConnectionError con un messaggio come:

Max retries exceeded with url: ...

Ciò implica che le richieste possono tentare di accedere ai dati più volte. Ma non c'è una sola menzione di questa possibilità da nessuna parte nei documenti. Guardando il codice sorgente non ho trovato alcun posto in cui avrei potuto modificare il valore predefinito (presumibilmente 0).

Quindi è possibile in qualche modo impostare il numero massimo di tentativi per le richieste?


9
Qualche aggiornamento su questo con richieste alla 2.x? Mi piacerebbe un'implementazione di request.get (url, max_retries = num_max_retries)).
Paragbaxi,

11
@paragbaxi: e ancora meglio arequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
WoJ,

1
@WoJ Ho preso i tuoi esempi e li ho resi realtà;) in just.gete just.postin github.com/kootenpv/just
PascalVKooten

2
Articolo utile sui tentativi con Richieste: peterbe.com/plog/best-practice-with-retries-with-requests
Gokul

Risposte:


161

È la urllib3libreria sottostante che esegue il nuovo tentativo. Per impostare un numero massimo di tentativi diverso, utilizzare adattatori di trasporto alternativi :

from requests.adapters import HTTPAdapter

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

L' max_retriesargomento accetta un numero intero o un Retry()oggetto ; quest'ultimo ti dà un controllo approfondito su quali tipi di errori vengono riprovati (un valore intero viene trasformato in Retry()un'istanza che gestisce solo gli errori di connessione; gli errori dopo aver effettuato una connessione non vengono gestiti per impostazione predefinita in quanto potrebbero causare effetti collaterali) .


Vecchia risposta, precedente al rilascio delle richieste 1.2.1 :

La requestslibreria non lo rende realmente configurabile, né intende (vedere questa richiesta pull ). Attualmente (richieste 1.1), il conteggio dei tentativi è impostato su 0. Se si desidera davvero impostarlo su un valore più alto, è necessario impostarlo a livello globale:

import requests

requests.adapters.DEFAULT_RETRIES = 5

Questa costante non è documentata; usalo a tuo rischio e pericolo poiché le versioni future potrebbero cambiare la modalità di gestione.

Aggiornamento : e questo è cambiato; nella versione 1.2.1 è stata aggiunta l'opzione per impostare il max_retriesparametro sulla HTTPAdapter()classe , quindi ora è necessario utilizzare adattatori di trasporto alternativi, vedere sopra. L'approccio della patch di scimmia non funziona più, a meno che non si correggano anche le HTTPAdapter.__init__()impostazioni predefinite (molto sconsigliato).


9
Non è necessario specificarlo per ogni sito se non è necessario. Puoi solo fare session.mount('http://', HTTPAdapter(max_retries=10))questo funzionerà per tutte le connessioni http. Lo stesso con https funzionerà quindi per tutte le connessioni https.
user136036,

1
@utente136036: sì, gli adattatori vengono cercati dalla corrispondenza del prefisso più lunga; se vuoi che questo si applichi a tutti gli URL http://e che https://siano i prefissi minimi da utilizzare, consulta la documentazione a cui rimanda la risposta.
Martijn Pieters

1
Nota che HTTPAdapter(max_retries=5)funzionerà solo per determinati scenari. Da doc richieste , 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.Per forzare un nuovo tentativo per qualsiasi codice di stato, vedere la risposta di @ datashaman di seguito.
Steven Xu,

@StevenXu: sì, è possibile configurare Retry()per modificare quali scenari di errore vengono ritentati.
Martijn Pieters

226

Questo non solo cambierà i max_retries ma abiliterà anche una strategia di backoff che fa dormire tutte le richieste a tutti gli indirizzi http: // prima di riprovare (per un totale di 5 volte):

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

Come da documentazione perRetry : se backoff_factor è 0.1 , sleep () dormirà per [0.1s, 0.2s, 0.4s, ...] tra i tentativi. Forzerà anche un nuovo tentativo se il codice di stato restituito è 500 , 502 , 503 o 504 .

Varie altre opzioni per Retryconsentire un controllo più granulare:

  • totale : numero totale di tentativi da consentire.
  • connect - Quanti errori relativi alla connessione su cui riprovare.
  • read : quante volte riprovare a leggere errori.
  • reindirizzamento : quanti reindirizzamenti eseguire.
  • method_whitelist - Set di verbi del metodo HTTP maiuscoli su cui dovremmo riprovare.
  • status_forcelist : un set di codici di stato HTTP su cui dovremmo forzare un nuovo tentativo.
  • backoff_factor - Un fattore di backoff da applicare tra i tentativi.
  • raise_on_redirect - Indica se, se il numero di reindirizzamenti è esaurito, per aumentare un MaxRetryErroro per restituire una risposta con un codice di risposta nell'intervallo 3xx .
  • raise_on_status - Significato simile a raise_on_redirect : se dovremmo sollevare un'eccezione o restituire una risposta, se lo stato rientra nell'intervallo status_forcelist e i tentativi sono stati esauriti.

NB : raise_on_status è relativamente nuovo e non è ancora diventato una versione di urllib3 o richieste. L'argomento della parola chiave raise_on_status sembra essere arrivato nella libreria standard al massimo nella versione 3.6 di Python.

Per fare di nuovo richieste su specifici codici di stato HTTP, utilizzare status_forcelist . Ad esempio, status_forcelist = [503] riproverà con il codice di stato 503 (servizio non disponibile).

Per impostazione predefinita, il nuovo tentativo si attiva solo per queste condizioni:

  • Impossibile ottenere una connessione dal pool.
  • TimeoutError
  • HTTPExceptiongenerato (da http.client in Python 3 altro httplib ). Sembra che si tratti di eccezioni HTTP di basso livello, come URL o protocollo non formati correttamente.
  • SocketError
  • ProtocolError

Si noti che queste sono tutte eccezioni che impediscono la ricezione di una risposta HTTP normale. Se qualsiasi viene generato risposta regolare, nessun nuovo tentativo è fatto. Senza usare status_forcelist , anche una risposta con stato 500 non verrà ripetuta.

Per farlo funzionare in modo più intuitivo per lavorare con un'API o un server Web remoti, utilizzare lo snippet di codice sopra riportato, che forza i tentativi sugli stati 500 , 502 , 503 e 504 , che non sono rari nel web e (possibilmente) recuperabili dato un periodo di backoff abbastanza grande.

EDITED : importa la Retryclasse direttamente da urllib3 .


1
Sto cercando di implementare la tua logica, ma non so se funziona perché il registro mostra solo una richiesta anche se lo stato di res è 503. Come posso sapere se il tentativo sta funzionando? Vedi il codice: pastebin.com/rty4bKTw
Danilo Oliveira,

1
Il codice allegato funziona come previsto. Il trucco è il parametro status_forcelist . Questo dice al pacchetto urllib3 di riprovare codici di stato specifici. Codice: pastebin.com/k2bFbH7Z
datashaman

1
urllib3 non (e non dovrebbe) pensare che lo stato 503 sia un'eccezione (per impostazione predefinita).
datashaman,

1
@Connor no, l'adattatore è collegato alla sessione.
datashaman,

1
urlib3.Retry non fa più parte delle richieste. questo deve essere importato direttamente. Modifica suggerita
user2390183

59

Fai attenzione, la risposta di Martijn Pieters non è adatta per la versione 1.2.1+. Non è possibile impostarlo a livello globale senza applicare patch alla libreria.

Puoi farlo invece:

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
Bella soluzione ma nota che non ci sono ritardi tra i tentativi. Se vuoi dormire tra i tentativi, dovrai farlo da solo.
nofinator

18

Dopo aver lottato un po 'con alcune delle risposte qui, ho trovato una libreria chiamata backoff che ha funzionato meglio per la mia situazione. Un esempio di base:

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

Consiglio comunque di dare una possibilità alla funzionalità nativa della libreria, ma se riscontri problemi o hai bisogno di un controllo più ampio, il backoff è un'opzione.


1
grande biblioteca, grazie! Avevo bisogno di questa funzionalità per qualcos'altro requests, quindi funziona perfettamente!
Dennis Golomazov,

3

Un modo più pulito per ottenere un maggiore controllo potrebbe essere quello di impacchettare le cose dei tentativi in ​​una funzione e renderla recuperabile usando un decoratore e autorizzare le eccezioni.

Ho creato lo stesso qui: http://www.praddy.in/retry-decorator-whitelisted-exceptions/

Riproduzione del codice in quel 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():
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.