Timeout pour les requêtes python.obtenir la réponse entière

169

Je collecte des statistiques sur une liste de sites Web et j'utilise des demandes pour cela par souci de simplicité. Voici mon code:

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

Maintenant, je veux requests.getexpirer après 10 secondes pour que la boucle ne reste pas bloquée.

Cette question a été d'intérêt avant aussi , mais aucune des réponses sont propres. Je mettrai un peu de prix là-dessus pour obtenir une bonne réponse.

J'ai entendu dire que ne pas utiliser les demandes est peut-être une bonne idée, mais comment puis-je obtenir les bonnes choses que les demandes offrent. (ceux du tuple)

Kiarash
la source
1
Quel genre de réponse recherchez-vous? (ou, en d'autres termes, pourquoi les réponses actuelles ne vous suffisent-elles pas?)
yuvi
Nous sommes dans la période de grâce de la prime. Il est temps de choisir une réponse?
totokaka
Je décide toujours entre la solution eventlet et les signaux. Je vais attribuer la question d'ici ce soir.
Kiarash

Réponses:

137

Qu'en est-il de l'utilisation de Eventlet? Si vous souhaitez expirer la demande après 10 secondes, même si des données sont reçues, cet extrait fonctionnera pour vous:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)
Alvaro
la source
115
C'est sûrement inutilement compliqué.
holdenweb
7
Je vous remercie. Je comprends maintenant la supériorité technique de votre solution (que vous avez énoncée assez succinctement au début de votre réponse) et je l'ai votée. Le problème avec les modules tiers n'est pas de les importer mais de s'assurer qu'ils sont là pour être importés, d'où ma propre préférence pour l'utilisation de la bibliothèque standard lorsque cela est possible.
holdenweb
9
Est eventlet.monkey_patch()nécessaire?
Utilisateur
3
Oui, le socketmodule doit être monkey patché, donc au moins vous aurez besoin d'uneventlet.monkey_patch(socket=True)
Alvaro
53
À partir de 2018, cette réponse est obsolète. Utilisationrequests.get('https://github.com', timeout=5)
CONvid19
315

Définissez le paramètre de délai :

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

Tant que vous ne définissez pas stream=Truecette demande, cela entraînera l' requests.get()expiration de l'appel à si la connexion prend plus de dix secondes ou si le serveur n'envoie pas de données pendant plus de dix secondes.

Lukasa
la source
31
Ce n'est pas pour toute la réponse. demandes.readthedocs.org/en/latest/user/quickstart/#timeouts
Kiarash
1
Oui, dans certaines circonstances. L'une de ces circonstances est la vôtre. =) Je vous invite à regarder le code si vous n'êtes pas convaincu.
Lukasa
quelles sont les circonstances?
Kiarash
1
Je viens de vérifier cela et cela ne s'est jamais arrêté: r = requests.get (' ipv4.download.thinkbroadband.com/1GB.zip ', timeout = 20)
Kiarash
5
Ah, désolé, j'ai mal compris ce que vous vouliez dire quand vous avez dit «toute la réponse». Oui, vous avez raison: ce n'est pas une limite supérieure du temps total à attendre.
Lukasa
86

MISE À JOUR: https://requests.readthedocs.io/en/master/user/advanced/#timeouts

Dans la nouvelle version de requests:

Si vous spécifiez une valeur unique pour le délai d'expiration, comme ceci:

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

La valeur du délai d'expiration sera appliquée à la fois au connectet aux readdélais d' expiration . Spécifiez un tuple si vous souhaitez définir les valeurs séparément:

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

Si le serveur distant est très lent, vous pouvez dire aux requêtes d'attendre indéfiniment une réponse, en passant None comme valeur de délai d'expiration, puis en récupérant une tasse de café.

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

Ma vieille réponse (probablement obsolète) (qui a été publiée il y a longtemps):

Il existe d'autres moyens de surmonter ce problème:

1. Utilisez la TimeoutSauceclasse interne

De: 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

Ce code devrait nous amener à définir le délai de lecture comme égal au délai de connexion, qui est la valeur du délai que vous passez lors de votre appel Session.get (). (Notez que je n'ai pas réellement testé ce code, il peut donc nécessiter un débogage rapide, je l'ai juste écrit directement dans la fenêtre GitHub.)

2. Utilisez une fourchette de requêtes de kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

De sa documentation: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Si vous spécifiez une valeur unique pour le délai d'expiration, comme ceci:

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

La valeur du délai d'expiration sera appliquée à la fois aux délais de connexion et de lecture. Spécifiez un tuple si vous souhaitez définir les valeurs séparément:

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

kevinburke a demandé qu'il soit fusionné dans le projet de requêtes principal, mais il n'a pas encore été accepté.

Hieu
la source
l'option 1 ne fonctionne pas. si vous continuez à lire ce fil, d'autres personnes ont dit "cela ne fonctionnera pas pour votre cas d'utilisation, j'en ai bien peur. La fonction de délai de lecture est à la portée d'un appel de socket individuel recv (), de sorte que si le serveur arrête d'envoyer des données pendant plus que le délai de lecture que nous abandonnerons. "
Kiarash
Il existe une autre solution intéressante dans ce thread utilisant Signal, qui ne fonctionnerait pas non plus pour moi, car j'utilise Windows et signal.alarm est uniquement Linux.
Kiarash
@Kiarash Je ne l'ai pas encore testé. Cependant, comme je comprends quand Lukasa a dit this won't work for you use-case. Il voulait dire que cela ne fonctionnait pas avec le flux mp3 recherché par l'autre gars.
Hieu
1
@Hieu - cela a été fusionné dans une autre pull request - github.com/kennethreitz/requests/pull
...
timeout = None ne bloque pas l'appel.
crazydan
49

timeout = int(seconds)

Depuis requests >= 2.4.0, vous pouvez utiliser l' timeoutargument, à savoir:

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

Remarque:

timeoutn'est pas une limite de temps pour tout le téléchargement de la réponse; au lieu de cela, un exceptionest déclenché si le serveur n'a pas émis de réponse pendant les secondes de timeout (plus précisément, si aucun octet n'a été reçu sur le socket sous-jacent pendant les secondes de timeout). Si aucun délai n'est spécifié explicitement, les demandes n'expirent pas.

CONvid19
la source
Quelle version des demandes a le nouveau paramètre de délai d'expiration?
Rusty
1
Semble être depuis la version 2.4.0: prise en charge des délais de connexion! Timeout accepte désormais un tuple (connexion, lecture) qui est utilisé pour définir des délais de connexion et de lecture individuels . pypi.org/project/requests/2.4.0
CONvid19
23

Pour créer un délai, vous pouvez utiliser des signaux .

La meilleure façon de résoudre ce cas est probablement de

  1. Définir une exception comme gestionnaire du signal d'alarme
  2. Appelez le signal d'alarme avec un délai de dix secondes
  3. Appelez la fonction à l'intérieur d'un try-except-finallybloc.
  4. Le bloc sauf est atteint si la fonction a expiré.
  5. Dans le bloc final, vous abandonnez l'alarme, elle n'est donc pas signalée plus tard.

Voici un exemple de code:

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)

Il y a quelques mises en garde à ceci:

  1. Ce n'est pas threadsafe, les signaux sont toujours livrés au thread principal, vous ne pouvez donc pas le mettre dans un autre thread.
  2. Il y a un léger retard après la programmation du signal et l'exécution du code réel. Cela signifie que l'exemple expirait même s'il ne dormait que dix secondes.

Mais tout est dans la bibliothèque python standard! À l'exception de l'importation de la fonction de veille, il ne s'agit que d'une seule importation. Si vous comptez utiliser des délais d'attente à de nombreux endroits, vous pouvez facilement mettre TimeoutException, _timeout et le chant dans une fonction et l'appeler simplement. Ou vous pouvez créer un décorateur et le mettre sur des fonctions, voir la réponse ci-dessous.

Vous pouvez également le configurer en tant que "gestionnaire de contexte" afin de pouvoir l'utiliser avec l' withinstruction:

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 inconvénient possible avec cette approche de gestionnaire de contexte est que vous ne pouvez pas savoir si le code a réellement expiré ou non.

Sources et lectures recommandées:

totokaka
la source
3
Les signaux ne sont livrés que dans le thread principal, donc cela ne fonctionnera certainement pas dans d'autres threads, pas probablement .
Dima Tisnek
1
Le package timeout-decorator fournit un décorateur de timeout qui utilise des signaux (ou éventuellement un multi-traitement).
Christian Long
13

Essayez cette demande avec le délai d'expiration et la gestion des erreurs:

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

Définir stream=Trueet utiliser r.iter_content(1024). Oui, eventlet.Timeoutça ne marche pas pour moi.

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 discussion est ici https://redd.it/80kp1h

Polv
la source
c'est une honte que la demande ne supporte pas les paramètres maxtime, cette solution est la seule qui fonctionne avec asyncio
wukong
4

Cela peut être excessif, mais la file d'attente des tâches distribuées Celery prend en charge les délais d'expiration.

En particulier, vous pouvez définir une limite de temps souple qui déclenche simplement une exception dans votre processus (afin que vous puissiez nettoyer) et / ou une limite de temps ferme qui met fin à la tâche lorsque la limite de temps a été dépassée.

Sous les couvertures, cela utilise la même approche de signaux que celle référencée dans votre article "avant", mais d'une manière plus utilisable et gérable. Et si la liste des sites Web que vous surveillez est longue, vous pouvez bénéficier de sa caractéristique principale - toutes sortes de façons de gérer l'exécution d'un grand nombre de tâches.

Chris Johnson
la source
Cela pourrait être une bonne solution. Le problème du timeout total n'est pas directement lié à python-requestsmais à httplib(utilisé par les requêtes pour Python 2.7). Le package transmet tout ce qui concerne timeoutdirectement à httplib. Je pense que rien ne peut être corrigé dans la demande car le processus peut rester longtemps dans httplib.
hynekcer
@hynekcer, je pense que vous avez raison. C'est pourquoi la détection des délais d'attente hors processus et la mise en œuvre en éliminant proprement les processus, comme le fait Celery, peut être une bonne approche.
Chris Johnson
3

Je pense que vous pouvez utiliser multiprocessinget ne pas dépendre d'un package tiers:

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)

Le délai passé à kwargsest le délai pour obtenir une réponse du serveur, l'argument timeoutest le délai pour obtenir la réponse complète .

Jorge Leitao
la source
Cela peut être amélioré avec un essai générique / sauf dans la fonction privée qui intercepte toutes les erreurs et les met dans return_dict ['error']. Ensuite, à la fin, avant de retourner, vérifiez si 'error' dans return_dict puis relevez-le. Cela facilite également le test.
dialt0ne
2

timeout = (timeout de connexion, timeout de lecture des données) ou donner un seul argument (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")
Fayzan qureshi
la source
1

ce code fonctionne pour socketError 11004 et 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_()
ACEE
la source
Vote positif pour la créativité
JSmyth
1

Bien que la question porte sur les demandes, je trouve cela très facile à faire avec pycurl CURLOPT_TIMEOUT ou CURLOPT_TIMEOUT_MS.

Aucun filetage ni signalisation requis:

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
John Smith
la source
1

Si vous utilisez l'option, stream=Truevous pouvez le faire:

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 solution n'a pas besoin de signaux ou de multi-traitement.

ub_marco
la source
1

Juste une autre solution (obtenue à partir de http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads )

Avant de télécharger, vous pouvez connaître la taille du contenu:

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

Mais attention, un expéditeur peut définir une valeur incorrecte dans le champ de réponse 'content-length'.

Denis Kuzin
la source
Merci. Solution propre et simple. Travaille pour moi.
petezurich
0

Si cela arrive, créez un thread de surveillance qui gâche l'état interne des requêtes après 10 secondes, par exemple:

  • ferme le socket sous-jacent, et idéalement
  • déclenche une exception si les requêtes relancent l'opération

Notez qu'en fonction des bibliothèques système, vous ne pourrez peut-être pas définir la date limite de résolution DNS.

Dima Tisnek
la source
0

Eh bien, j'ai essayé de nombreuses solutions sur cette page et j'ai toujours rencontré des instabilités, des blocages aléatoires, de mauvaises performances de connexion.

J'utilise maintenant Curl et je suis vraiment content de sa fonctionnalité "max time" et des performances globales, même avec une implémentation aussi médiocre:

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

Ici, j'ai défini un paramètre de temps maximum de 6 secondes, englobant à la fois le temps de connexion et le temps de transfert.

Je suis sûr que Curl a une belle liaison python, si vous préférez vous en tenir à la syntaxe pythonique :)

technico
la source
0

Il existe un package appelé timeout-decorator que vous pouvez utiliser pour expirer n'importe quelle fonction python.

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

Il utilise l'approche des signaux que suggèrent certaines réponses ici. Vous pouvez également lui dire d'utiliser le multitraitement au lieu des signaux (par exemple, si vous êtes dans un environnement multi-thread).

Christian Long
la source
0

J'utilise les requêtes 2.2.1 et eventlet n'a pas fonctionné pour moi. Au lieu de cela, j'ai pu utiliser gevent timeout à la place puisque gevent est utilisé dans mon service pour 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)

Veuillez noter que gevent.timeout.Timeout n'est pas intercepté par la gestion générale des exceptions. Donc, soit explicitement attraper, gevent.timeout.Timeout soit transmettre une exception différente à utiliser comme ceci: with gevent.Timeout(5, requests.exceptions.Timeout):bien qu'aucun message ne soit passé lorsque cette exception est déclenchée.

xsdf
la source
-1

J'ai trouvé une solution plus directe, certes moche, mais qui résout le vrai problème. Ça va un peu comme ça:

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

Vous pouvez lire l'explication complète ici

Réaliste
la source
3
1- parce que vous pouvez passer le timeoutparamètrerequests.get() sans solutions de contournement laides 2- bien que les deux ne limitent pas le délai total contrairement àeventlet.Timeout(10)
jfs