Timeout per le richieste di Python. Completa risposta


169

Sto raccogliendo statistiche su un elenco di siti Web e sto usando le richieste per semplicità. Ecco il mio codice:

data=[]
websites=['http://google.com', 'http://bbc.co.uk']
for w in websites:
    r= requests.get(w, verify=False)
    data.append( (r.url, len(r.content), r.elapsed.total_seconds(), str([(l.status_code, l.url) for l in r.history]), str(r.headers.items()), str(r.cookies.items())) )

Ora voglio requests.gettimeout dopo 10 secondi in modo che il loop non si blocchi.

Anche questa domanda è stata interessante prima, ma nessuna delle risposte è chiara. Metterò un po 'di grazia su questo per ottenere una bella risposta.

Ho sentito che forse non usare le richieste è una buona idea, ma poi come dovrei ottenere le cose belle richieste. (quelli nella tupla)


1
Che tipo di risposta stai cercando? (o, in altre parole, perché le risposte attuali non sono sufficienti per te?)
yuvi

Siamo nel periodo di grazia della generosità. È ora di scegliere una risposta?
Totokaka,

Sto ancora decidendo tra la soluzione dell'evento e i segnali. Stasera assegnerò la domanda.
Kiarash


Risposte:


137

Che dire dell'utilizzo di eventlet? Se vuoi scadere la richiesta dopo 10 secondi, anche se i dati vengono ricevuti, questo frammento funzionerà per te:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)

115
Sicuramente questo è inutilmente complicato.
holdenweb,

7
Grazie. Ora capisco la superiorità tecnica della tua soluzione (che hai affermato in modo piuttosto conciso all'inizio della tua risposta) e l'ho votata. Il problema con i moduli di terze parti non è quello di importarli, ma assicurarsi che siano lì per essere importati, quindi la mia preferenza per l'uso della libreria standard, ove possibile.
Holdenweb,

9
È eventlet.monkey_patch()richiesto?
Utente

3
Sì, il socketmodulo deve essere patchato dalla scimmia, quindi almeno avrai bisogno dieventlet.monkey_patch(socket=True)
Alvaro il

53
A partire dal 2018 questa risposta è obsoleta. Usarequests.get('https://github.com', timeout=5)
CONvid19

313

Imposta il parametro di timeout :

r = requests.get(w, verify=False, timeout=10) # 10 seconds

Se non si imposta stream=Truetale richiesta, ciò causerà il requests.get()timeout della chiamata se la connessione impiega più di dieci secondi o se il server non invia dati per più di dieci secondi.



1
Sì, lo è, in alcune circostanze. Una di queste circostanze sembra essere la tua. =) Ti invito a guardare il codice se non sei convinto.
Lukasa,

quali sono le circostanze?
Kiarash,

1
Ho appena controllato questo e non si è mai fermato: r = request.get (' ipv4.download.thinkbroadband.com/1GB.zip ', timeout = 20)
Kiarash

5
Ah, scusa, ho capito male cosa intendevi quando hai detto "l'intera risposta". Sì, hai ragione: non è un limite massimo per il tempo totale di attesa.
Lukasa,

85

AGGIORNAMENTO: https://requests.readthedocs.io/en/master/user/advanced/#timeouts

Nella nuova versione di requests:

Se si specifica un singolo valore per il timeout, in questo modo:

r = requests.get('https://github.com', timeout=5)

Il valore di timeout verrà applicato sia connectal readtimeout che al timeout. Specificare una tupla se si desidera impostare i valori separatamente:

r = requests.get('https://github.com', timeout=(3.05, 27))

Se il server remoto è molto lento, puoi dire alle Richieste di attendere per sempre una risposta, passando Nessuno come valore di timeout e quindi recuperando una tazza di caffè.

r = requests.get('https://github.com', timeout=None)

La mia vecchia risposta (probabilmente obsoleta) (che è stata pubblicata molto tempo fa):

Esistono altri modi per superare questo problema:

1. Utilizzare la TimeoutSauceclasse interna

Da: https://github.com/kennethreitz/requests/issues/1928#issuecomment-35811896

import requests from requests.adapters import TimeoutSauce

class MyTimeout(TimeoutSauce):
    def __init__(self, *args, **kwargs):
        connect = kwargs.get('connect', 5)
        read = kwargs.get('read', connect)
        super(MyTimeout, self).__init__(connect=connect, read=read)

requests.adapters.TimeoutSauce = MyTimeout

Questo codice dovrebbe indurci a impostare il timeout di lettura uguale al timeout di connessione, che è il valore di timeout che passi sulla tua chiamata Session.get (). (Nota che non ho ancora testato questo codice, quindi potrebbe essere necessario un rapido debug, l'ho appena scritto direttamente nella finestra di GitHub.)

2. Utilizzare un fork di richieste da kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

Dalla sua documentazione: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Se si specifica un singolo valore per il timeout, in questo modo:

r = requests.get('https://github.com', timeout=5)

Il valore di timeout verrà applicato sia al timeout di connessione sia a quello di lettura. Specificare una tupla se si desidera impostare i valori separatamente:

r = requests.get('https://github.com', timeout=(3.05, 27))

kevinburke ha richiesto che venga unito al progetto delle richieste principali, ma non è stato ancora accettato.


l'opzione 1 non funziona. se continui a leggere quel thread, altre persone hanno detto "questo non funzionerà per il tuo caso d'uso, temo. La funzione di timeout di lettura è nell'ambito di una singola chiamata recv () socket, in modo che se il server interrompe l'invio di dati per un periodo di tempo superiore al timeout di lettura che interromperemo. "
Kiarash,

C'è un'altra bella soluzione in quel thread usando Signal, che non funzionerebbe neanche per me, perché io uso Windows e signal.alarm è solo Linux.
Kiarash,

@Kiarash Non l'ho ancora testato. Tuttavia, come capisco quando ha detto Lukasa this won't work for you use-case. Voleva dire che non funziona con il flusso mp3 che è voluto dall'altro ragazzo.
Hieu

1
@Hieu - questo è stato unito in un'altra richiesta pull - github.com/kennethreitz/requests/pull/…
yprez

timeout = Nessuno non sta bloccando la chiamata.
crazydan,

49

timeout = int(seconds)

Da allora requests >= 2.4.0, puoi usare l' timeoutargomento, cioè:

requests.get('https://duckduckgo.com/', timeout=10)

Nota:

timeoutnon è un limite di tempo per il download dell'intera risposta; piuttosto, exceptionviene generato se il server non ha emesso una risposta per secondi di timeout (più precisamente, se non sono stati ricevuti byte sul socket sottostante per secondi di timeout). Se non viene specificato esplicitamente alcun timeout, le richieste non scadono.


Quale versione delle richieste ha il nuovo parametro di timeout?
Rusty,

1
Sembra essere dalla versione 2.4.0: supporto per i timeout di connessione! Il timeout ora accetta una tupla (connessione, lettura) che viene utilizzata per impostare i timeout di connessione e lettura individuali . pypi.org/project/requests/2.4.0
CONvid19

23

Per creare un timeout è possibile utilizzare i segnali .

Il modo migliore per risolvere questo caso è probabilmente

  1. Impostare un'eccezione come gestore per il segnale di allarme
  2. Chiamare il segnale di allarme con un ritardo di dieci secondi
  3. Chiamare la funzione all'interno di a try-except-finally blocco.
  4. Il blocco di eccezione viene raggiunto se la funzione è scaduta.
  5. Nel blocco finalmente interrompi l'allarme, quindi non verrà più cantato in seguito.

Ecco un esempio di codice:

import signal
from time import sleep

class TimeoutException(Exception):
    """ Simple Exception to be called on timeouts. """
    pass

def _timeout(signum, frame):
    """ Raise an TimeoutException.

    This is intended for use as a signal handler.
    The signum and frame arguments passed to this are ignored.

    """
    # Raise TimeoutException with system default timeout message
    raise TimeoutException()

# Set the handler for the SIGALRM signal:
signal.signal(signal.SIGALRM, _timeout)
# Send the SIGALRM signal in 10 seconds:
signal.alarm(10)

try:    
    # Do our code:
    print('This will take 11 seconds...')
    sleep(11)
    print('done!')
except TimeoutException:
    print('It timed out!')
finally:
    # Abort the sending of the SIGALRM signal:
    signal.alarm(0)

Ci sono alcuni avvertimenti a questo:

  1. Non è thread-safe, i segnali vengono sempre inviati al thread principale, quindi non puoi inserirlo in nessun altro thread.
  2. Vi è un leggero ritardo dopo la programmazione del segnale e l'esecuzione del codice effettivo. Ciò significa che l'esempio andrebbe in timeout anche se dormisse solo per dieci secondi.

Ma è tutto nella libreria standard di Python! Fatta eccezione per l'importazione della funzione sleep è solo una importazione. Se hai intenzione di utilizzare timeout in molti luoghi Puoi facilmente inserire TimeoutException, _timeout e il singaling in una funzione e chiamarlo semplicemente. Oppure puoi creare un decoratore e metterlo su funzioni, vedi la risposta collegata di seguito.

Puoi anche impostarlo come "gestore di contesto" in modo da poterlo utilizzare con l' withistruzione:

import signal
class Timeout():
    """ Timeout for use with the `with` statement. """

    class TimeoutException(Exception):
        """ Simple Exception to be called on timeouts. """
        pass

    def _timeout(signum, frame):
        """ Raise an TimeoutException.

        This is intended for use as a signal handler.
        The signum and frame arguments passed to this are ignored.

        """
        raise Timeout.TimeoutException()

    def __init__(self, timeout=10):
        self.timeout = timeout
        signal.signal(signal.SIGALRM, Timeout._timeout)

    def __enter__(self):
        signal.alarm(self.timeout)

    def __exit__(self, exc_type, exc_value, traceback):
        signal.alarm(0)
        return exc_type is Timeout.TimeoutException

# Demonstration:
from time import sleep

print('This is going to take maximum 10 seconds...')
with Timeout(10):
    sleep(15)
    print('No timeout?')
print('Done')

Un possibile lato negativo di questo approccio al gestore del contesto è che non puoi sapere se il codice è scaduto o meno.

Fonti e letture consigliate:


3
I segnali vengono consegnati solo nel thread principale, quindi sicuramente non funzionerà in altri thread, probabilmente non .
Dima Tisnek,

1
Il pacchetto timeout-decorator fornisce un decoratore timeout che utilizza segnali (o opzionalmente multiprocessing).
Christian Long,

13

Prova questa richiesta con timeout e gestione degli errori:

import requests
try: 
    url = "http://google.com"
    r = requests.get(url, timeout=10)
except requests.exceptions.Timeout as e: 
    print e

5

Imposta stream=Truee usa r.iter_content(1024). Sì, in eventlet.Timeoutqualche modo non funziona per me.

try:
    start = time()
    timeout = 5
    with get(config['source']['online'], stream=True, timeout=timeout) as r:
        r.raise_for_status()
        content = bytes()
        content_gen = r.iter_content(1024)
        while True:
            if time()-start > timeout:
                raise TimeoutError('Time out! ({} seconds)'.format(timeout))
            try:
                content += next(content_gen)
            except StopIteration:
                break
        data = content.decode().split('\n')
        if len(data) in [0, 1]:
            raise ValueError('Bad requests data')
except (exceptions.RequestException, ValueError, IndexError, KeyboardInterrupt,
        TimeoutError) as e:
    print(e)
    with open(config['source']['local']) as f:
        data = [line.strip() for line in f.readlines()]

La discussione è qui https://redd.it/80kp1h


è una richiesta vergognosa non supporta i parametri maxtime, questa soluzione è l'unica che ha funzionato con asyncio
wukong

4

Questo può essere eccessivo, ma la coda di attività distribuita di Celery ha un buon supporto per i timeout.

In particolare, è possibile definire un limite di tempo flessibile che solleva solo un'eccezione nel processo (in modo da poter ripulire) e / o un limite di tempo difficile che termina l'attività quando il limite di tempo è stato superato.

Sotto le copertine, questo utilizza lo stesso approccio di segnali come indicato nel tuo post "prima", ma in un modo più utilizzabile e gestibile. E se l'elenco dei siti Web che stai monitorando è lungo, potresti trarre vantaggio dalla sua caratteristica principale - tutti i tipi di modi per gestire l'esecuzione di un gran numero di attività.


Questa potrebbe essere una buona soluzione. Il problema del timeout totale non è direttamente correlato python-requestsma a httplib(utilizzato dalle richieste per Python 2.7). Il pacchetto passa tutto ciò che riguarda timeoutdirettamente a httplib. Penso che nulla possa essere risolto su richiesta perché il processo può rimanere a lungo in httplib.
Hynekcer,

@hynekcer, penso che tu abbia ragione. Questo è il motivo per cui rilevare i timeout fuori processo e farli rispettare uccidendo in modo pulito i processi, come fa il sedano, può essere un buon approccio.
Chris Johnson,

3

Credo che tu possa usare multiprocessinge non dipendere da un pacchetto di terze parti:

import multiprocessing
import requests

def call_with_timeout(func, args, kwargs, timeout):
    manager = multiprocessing.Manager()
    return_dict = manager.dict()

    # define a wrapper of `return_dict` to store the result.
    def function(return_dict):
        return_dict['value'] = func(*args, **kwargs)

    p = multiprocessing.Process(target=function, args=(return_dict,))
    p.start()

    # Force a max. `timeout` or wait for the process to finish
    p.join(timeout)

    # If thread is still active, it didn't finish: raise TimeoutError
    if p.is_alive():
        p.terminate()
        p.join()
        raise TimeoutError
    else:
        return return_dict['value']

call_with_timeout(requests.get, args=(url,), kwargs={'timeout': 10}, timeout=60)

Il timeout passato kwargsè il timeout per ottenere qualsiasi risposta dal server, l'argomento timeoutè il timeout per ottenere la risposta completa .


Questo può essere migliorato con un generico try / tranne nella funzione privata che rileva tutti gli errori e li inserisce in return_dict ['errore']. Quindi alla fine, prima di tornare, controlla se 'errore' in return_dict e poi sollevalo. Rende molto più facile anche il test.
dialt0ne,

2

timeout = (timeout di connessione, timeout di lettura dati) o fornire un singolo argomento (timeout = 1)

import requests

try:
    req = requests.request('GET', 'https://www.google.com',timeout=(1,1))
    print(req)
except requests.ReadTimeout:
    print("READ TIME OUT")

1

questo codice funziona per socketError 11004 e 10060 ......

# -*- encoding:UTF-8 -*-
__author__ = 'ACE'
import requests
from PyQt4.QtCore import *
from PyQt4.QtGui import *


class TimeOutModel(QThread):
    Existed = pyqtSignal(bool)
    TimeOut = pyqtSignal()

    def __init__(self, fun, timeout=500, parent=None):
        """
        @param fun: function or lambda
        @param timeout: ms
        """
        super(TimeOutModel, self).__init__(parent)
        self.fun = fun

        self.timeer = QTimer(self)
        self.timeer.setInterval(timeout)
        self.timeer.timeout.connect(self.time_timeout)
        self.Existed.connect(self.timeer.stop)
        self.timeer.start()

        self.setTerminationEnabled(True)

    def time_timeout(self):
        self.timeer.stop()
        self.TimeOut.emit()
        self.quit()
        self.terminate()

    def run(self):
        self.fun()


bb = lambda: requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip")

a = QApplication([])

z = TimeOutModel(bb, 500)
print 'timeout'

a.exec_()

Voto per la creatività
JSmyth

1

Nonostante la domanda riguardi le richieste, lo trovo molto facile da fare con il ciclo CURLOPT_TIMEOUT o CURLOPT_TIMEOUT_MS.

Nessun threading o segnalazione richiesta:

import pycurl
import StringIO

url = 'http://www.example.com/example.zip'
timeout_ms = 1000
raw = StringIO.StringIO()
c = pycurl.Curl()
c.setopt(pycurl.TIMEOUT_MS, timeout_ms)  # total timeout in milliseconds
c.setopt(pycurl.WRITEFUNCTION, raw.write)
c.setopt(pycurl.NOSIGNAL, 1)
c.setopt(pycurl.URL, url)
c.setopt(pycurl.HTTPGET, 1)
try:
    c.perform()
except pycurl.error:
    traceback.print_exc() # error generated on timeout
    pass # or just pass if you don't want to print the error

1

Nel caso in cui tu stia usando l'opzione stream=Truepuoi farlo:

r = requests.get(
    'http://url_to_large_file',
    timeout=1,  # relevant only for underlying socket
    stream=True)

with open('/tmp/out_file.txt'), 'wb') as f:
    start_time = time.time()
    for chunk in r.iter_content(chunk_size=1024):
        if chunk:  # filter out keep-alive new chunks
            f.write(chunk)
        if time.time() - start_time > 8:
            raise Exception('Request took longer than 8s')

La soluzione non necessita di segnali o multiprocessing.


1

Solo un'altra soluzione (ottenuta da http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads )

Prima del caricamento puoi scoprire le dimensioni del contenuto:

TOO_LONG = 10*1024*1024  # 10 Mb
big_url = "http://ipv4.download.thinkbroadband.com/1GB.zip"
r = requests.get(big_url, stream=True)
print (r.headers['content-length'])
# 1073741824  

if int(r.headers['content-length']) < TOO_LONG:
    # upload content:
    content = r.content

Ma fai attenzione, un mittente può impostare un valore errato nel campo di risposta "lunghezza contenuto".


Grazie. Soluzione pulita e semplice. Per me va bene.
Petezurich,

0

In questo caso, crea un thread del watchdog che rovini lo stato interno delle richieste dopo 10 secondi, ad esempio:

  • chiude il socket sottostante e idealmente
  • attiva un'eccezione se le richieste ritentano l'operazione

Si noti che a seconda delle librerie di sistema potrebbe non essere possibile impostare la scadenza per la risoluzione DNS.


0

Bene, ho provato molte soluzioni in questa pagina e ho ancora affrontato instabilità, blocchi casuali, scarse prestazioni delle connessioni.

Ora sto usando Curl e sono davvero contento della sua funzionalità "tempo massimo" e delle prestazioni globali, anche con un'implementazione così scarsa:

content=commands.getoutput('curl -m6 -Ss "http://mywebsite.xyz"')

Qui, ho definito un parametro di tempo massimo di 6 secondi, includendo sia il tempo di connessione che di trasferimento.

Sono sicuro che Curl abbia un bel binding in pitone, se preferisci attenersi alla sintassi pitonica :)


0

Esiste un pacchetto chiamato timeout-decorator che è possibile utilizzare per il timeout di qualsiasi funzione Python.

@timeout_decorator.timeout(5)
def mytest():
    print("Start")
    for i in range(1,10):
        time.sleep(1)
        print("{} seconds have passed".format(i))

Utilizza l'approccio dei segnali suggerito da alcune risposte qui. In alternativa, puoi dirgli di usare il multiprocessing invece dei segnali (ad es. Se ti trovi in ​​un ambiente multi-thread).


0

Sto usando le richieste 2.2.1 e eventlet non ha funzionato per me. Invece sono stato in grado di usare il timeout di gevent invece poiché gevent è usato nel mio servizio per gunicorn.

import gevent
import gevent.monkey
gevent.monkey.patch_all(subprocess=True)
try:
    with gevent.Timeout(5):
        ret = requests.get(url)
        print ret.status_code, ret.content
except gevent.timeout.Timeout as e:
    print "timeout: {}".format(e.message)

Si noti che gevent.timeout.Timeout non è intercettato dalla gestione generale delle eccezioni. Quindi, catturare gevent.timeout.Timeout o passare in modo esplicito un'eccezione diversa da utilizzare in questo modo: with gevent.Timeout(5, requests.exceptions.Timeout):sebbene nessun messaggio venga passato quando viene sollevata questa eccezione.


-1

Ho trovato una soluzione più diretta che è certamente brutta ma risolve il vero problema. Va un po 'così:

resp = requests.get(some_url, stream=True)
resp.raw._fp.fp._sock.settimeout(read_timeout)
# This will load the entire response even though stream is set
content = resp.content

Puoi leggere la spiegazione completa qui


3
1- perché puoi passare il timeoutparametro arequests.get() senza brutte soluzioni alternative 2- sebbene entrambi non limitino il timeout totale a differenza dieventlet.Timeout(10)
jfs
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.