web-dev-qa-db-fra.com

Délai d'expiration pour les requêtes python. Obtenir la réponse complète

Je rassemble des statistiques sur une liste de sites Web et j'utilise les demandes pour plus 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 que requests.get expire au bout de 10 secondes pour que la boucle ne reste pas bloquée. 

Cette question a eu un intérêt avant aussi mais aucune des réponses n'est nette. Je vais mettre une prime sur cela pour obtenir une bonne réponse.

J'entends dire que peut-être que ne pas utiliser les demandes est une bonne idée, mais ensuite, comment puis-je obtenir l'offre de demandes de Nice Things (ceux du tuple)

85
Kiarash

Qu'en est-il d'utiliser Eventlet? Si vous souhaitez que la requête expire au bout de 10 secondes, même si des données sont en cours de réception, 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)
92
Alvaro

Définissez le paramètre timeout :

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

Tant que vous ne définissez pas stream=True sur cette demande, l'appel de requests.get() expire si la connexion prend plus de dix secondes ou si le serveur n'envoie pas de données pendant plus de dix secondes.

129
Lukasa

UPDATE: http://docs.python-requests.org/fr/master/user/advanced/#timeouts

Dans la nouvelle version de requests:

Si vous spécifiez une seule valeur pour le délai d'attente, procédez comme suit:

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

La valeur de délai d'attente sera appliquée à la fois aux délais d'expiration connect et read. 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 indiquer aux demandes d'attendre une réponse pour toujours, en transmettant Aucune comme valeur de délai d'attente, puis en récupérant une tasse de café.

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

Mon ancienne réponse (probablement obsolète) (qui a été postée il y a longtemps):

Il existe d'autres moyens de résoudre ce problème:

1. Utilisez la classe TimeoutSauce internal

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 doit nous amener à définir le délai de lecture comme étant égal à connect timeout, qui correspond à la valeur de timeout que vous transmettez à votre Session.get () appelle. (Notez que je n'ai pas vraiment testé ce code, donc Il peut nécessiter un débogage rapide, je l'ai simplement écrit directement dans la fenêtre GitHub.)

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

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

Si vous spécifiez une seule valeur pour le délai d'attente, procédez comme suit:

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

La valeur de délai d'attente sera appliquée à la fois à la connexion et à la lecture délais d'attente. 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é il doit être intégré au projet principal, mais il n’a pas encore été accepté. 

45
Hieu

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

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

  1. Définir une exception en tant que gestionnaire pour le signal d'alarme
  2. Appeler le signal d'alarme avec un délai de dix secondes
  3. Appelez la fonction dans un bloc try-except-finally.
  4. Le bloc except est atteint si la fonction a expiré.
  5. Dans le bloc finally, vous annulez l'alarme, elle ne sera donc pas annulé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 à cela:

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

Mais tout se trouve dans la bibliothèque standard Python! À l'exception de l'importation de la fonction de veille, il ne s'agit que d'une importation. Si vous comptez utiliser des timeouts à plusieurs endroits, vous pouvez facilement insérer TimeoutException, _timeout et le singaling dans une fonction et simplement appeler cela. Ou vous pouvez faire un décorateur et le mettre sur des fonctions, voir la réponse liée ci-dessous.

Vous pouvez également configurer cela en tant que "gestionnaire de contexte" afin que vous puissiez l'utiliser avec l'instruction with:

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 = 10
        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 expiré ou non.

Sources et lectures recommandées:

19
totokaka

À partir de janvier 2019, vous pouvez utiliser l'argument timeout de requests , à savoir:

requests.get(url, timeout=10)

Remarque:

timeout n'est pas une limite de temps pour le téléchargement complet de la réponse; plutôt, une exception est déclenchée si le serveur n'a pas émis de réponse pour timeout secondes (plus précisément, si aucun octet n'a été reçu sur le socket sous-jacent pendant les secondes écoulées). Si aucun délai n'est spécifié explicitement, les demandes ne pas expirer.

16
Pedro Lobito

C'est peut-être excessif, mais la file d'attente des tâches distribuées du céleri prend en charge les délais d'expiration.

Vous pouvez notamment définir une limite temporelle souple qui ne déclenche qu'une exception dans votre processus (afin que vous puissiez la nettoyer) et/ou une limite temporelle dure qui termine la tâche lorsque la limite temporelle est dépassée.

Sous les couvertures, cela utilise la même approche de signaux que celle mentionnée dans votre message "avant", mais d'une manière plus utilisable et plus facile à gérer. Et si la liste des sites Web que vous surveillez est longue, vous pourrez tirer parti de sa fonctionnalité principale - toutes sortes de moyens de gérer l'exécution d'un grand nombre de tâches.

4
Chris Johnson

Je crois que vous pouvez utiliser multiprocessing et 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 d'attente passé à kwargs est le délai d'attente pour obtenir la réponse toute du serveur, l'argument timeout est le délai d'attente pour obtenir la réponse complète.

2
Jorge Leitão

Essayez cette requête avec timeout et traitement des erreurs:

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

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_()
1
ACEE

Bien que la question concerne les demandes, je trouve cela très facile à faire avec pycurlCURLOPT_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
1
John Smith

Si cela se produit, créez un watchdog thread qui perturbe l’état interne des requêtes après 10 secondes, par exemple:

  • ferme la prise sous-jacente, et idéalement
  • déclenche une exception si les requêtes relancent l'opération

Notez que, selon les bibliothèques système, il peut être impossible de définir une échéance pour la résolution DNS.

0
Dima Tisnek

Si vous utilisez l'option stream=True, vous 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 ni de multitraitement.

0
ub_marco

Définissez stream=True et utilisez r.iter_content(1024). Oui, eventlet.Timeout ne fonctionne tout simplement 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

0
Polv

timeout = (délai de connexion, délai 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")
0
Fayzan qureshi

Eh bien, j'ai essayé de nombreuses solutions sur cette page et je faisais face à des instabilités, des blocages aléatoires, des performances de connexion médiocres.

J'utilise maintenant Curl et je suis vraiment content de sa fonctionnalité "max time" et de ses performances globales, même avec une mise en œuvre 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 liaison Nice en python, si vous préférez vous en tenir à la syntaxe Pythonic :)

0
technico

Il existe un paquet appelé timeout-decorator que vous pouvez utiliser pour mettre fin à toute 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 certaines réponses suggèrent ici. Vous pouvez également lui dire d’utiliser le multitraitement au lieu de signaux (par exemple, si vous êtes dans un environnement multi-thread).

0
Christian Long

excusez-moi, mais je me demande pourquoi personne n'a suggéré la solution plus simple suivante. : -o

## request
requests.get('www.mypage.com', timeout=20)
0
comiventor

Juste une autre solution (obtenue 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

Attention, un expéditeur peut définir une valeur incorrecte dans le champ de réponse 'longueur du contenu'.

0
Denis Kuzin