Tiempo de espera para solicitudes de Python. Obtenga respuesta completa

169

Estoy recopilando estadísticas en una lista de sitios web y estoy usando solicitudes para simplificar. Aquí está mi código:

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

Ahora, quiero requests.getagotar el tiempo de espera después de 10 segundos para que el ciclo no se atasque.

Esta pregunta también ha sido de interés anteriormente, pero ninguna de las respuestas es clara. Voy a poner algo de recompensa en esto para obtener una buena respuesta.

Escuché que tal vez no usar solicitudes es una buena idea, pero entonces, ¿cómo debo obtener las cosas buenas que ofrecen las solicitudes? (los de la tupla)

Kiarash
fuente
1
¿Qué tipo de respuesta estás buscando? (o, en otras palabras, ¿por qué las respuestas actuales no son suficientes para usted?)
yuvi
Estamos en el período de gracia de la recompensa. ¿Hora de elegir una respuesta?
totokaka
Todavía estoy decidiendo entre la solución eventlet y las señales. Otorgaré la pregunta esta noche.
Kiarash

Respuestas:

137

¿Qué pasa con el uso de eventlet? Si desea agotar el tiempo de espera de la solicitud después de 10 segundos, incluso si se reciben datos, este fragmento funcionará para usted:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)
Alvaro
fuente
114
Seguramente esto es innecesariamente complicado.
holdenweb
77
Gracias. Ahora entiendo la superioridad técnica de su solución (que declaró de manera bastante sucinta al comienzo de su respuesta) y la votó. El problema con los módulos de terceros no es importarlos, sino garantizar que estén allí para ser importados, de ahí mi preferencia por usar la biblioteca estándar siempre que sea posible.
holdenweb
9
Se eventlet.monkey_patch()requiere?
Usuario
3
Sí, el socketmódulo debe ser parcheado, así que al menos necesitarás uneventlet.monkey_patch(socket=True)
Alvaro
52
A partir de 2018, esta respuesta está desactualizada. Usorequests.get('https://github.com', timeout=5)
CONvid19
312

Establezca el parámetro de tiempo de espera :

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

Siempre que no establezca stream=Trueesa solicitud, esto hará que la llamada se requests.get()agote si la conexión demora más de diez segundos o si el servidor no envía datos durante más de diez segundos.

Lukasa
fuente
31
Eso no es para toda la respuesta. requests.readthedocs.org/en/latest/user/quickstart/#timeouts
Kiarash
1
Sí lo es, en algunas circunstancias. Una de esas circunstancias es tuya. =) Te invito a mirar el código si no estás convencido.
Lukasa
cuales son las circunstancias
Kiarash
1
Acabo de comprobar esto y nunca se detuvo: r = request.get (' ipv4.download.thinkbroadband.com/1GB.zip ', tiempo de espera = 20)
Kiarash
55
Ah, lo siento, no entendí lo que querías decir cuando dijiste "toda la respuesta". Sí, tiene razón: no es un límite superior en la cantidad total de tiempo de espera.
Lukasa
85

ACTUALIZACIÓN: https://requests.readthedocs.io/en/master/user/advanced/#timeouts

En nueva versión de requests:

Si especifica un valor único para el tiempo de espera, así:

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

El valor del tiempo de espera se aplicará tanto a connectlos readtiempos de espera como a los tiempos de espera. Especifique una tupla si desea establecer los valores por separado:

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

Si el servidor remoto es muy lento, puede indicarle a Solicitudes que esperen una respuesta para siempre, pasando Ninguno como valor de tiempo de espera y luego recuperando una taza de café.

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

Mi respuesta anterior (probablemente desactualizada) (que se publicó hace mucho tiempo):

Hay otras formas de superar este problema:

1. Usa la TimeoutSauceclase interna

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

Este código debería hacer que establezcamos el tiempo de espera de lectura como igual al tiempo de espera de conexión, que es el valor de tiempo de espera que pasa en su llamada Session.get (). (Tenga en cuenta que en realidad no he probado este código, por lo que puede necesitar alguna depuración rápida, simplemente lo escribí directamente en la ventana de GitHub).

2. Use una bifurcación de solicitudes de kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

De su documentación: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Si especifica un valor único para el tiempo de espera, así:

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

El valor de tiempo de espera se aplicará a los tiempos de espera de conexión y lectura. Especifique una tupla si desea establecer los valores por separado:

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

kevinburke ha solicitado que se fusione con el proyecto de solicitudes principales, pero aún no ha sido aceptado.

Hieu
fuente
La opción 1 no funciona. si continúa leyendo ese hilo, otras personas han dicho "esto no funcionará para su caso de uso, me temo. La función de tiempo de espera de lectura está en el alcance de una llamada de recv () de socket individual, de modo que si el servidor deja de enviar datos por más tiempo que el tiempo de espera de lectura que abortaremos ".
Kiarash
Hay otra buena solución en ese hilo usando Signal, que tampoco funcionaría para mí, porque uso Windows y signal.alarm es solo para Linux.
Kiarash
@Kiarash No lo he probado todavía. Sin embargo, como entiendo cuando dijo Lukasa this won't work for you use-case. Él quiso decir que no funciona con la transmisión de mp3 que el otro chico quiere.
Hieu
1
@Hieu - esto se fusionó en otra solicitud de extracción - github.com/kennethreitz/requests/pull/…
yprez
tiempo de espera = Ninguno no está bloqueando la llamada.
crazydan
49

timeout = int(seconds)

Desde entonces requests >= 2.4.0, puede usar el timeoutargumento, es decir:

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

Nota:

timeoutno es un límite de tiempo en toda la descarga de respuesta; más bien, exceptionse genera un mensaje si el servidor no ha emitido una respuesta durante segundos de tiempo de espera (más precisamente, si no se han recibido bytes en el socket subyacente durante segundos de tiempo de espera). Si no se especifica explícitamente el tiempo de espera, las solicitudes no se agotarán.

CONvid19
fuente
¿Qué versión de solicitudes tiene el nuevo parámetro de tiempo de espera?
Rusty
1
Parece ser desde la versión 2.4.0: ¡ Soporte para tiempos de espera de conexión! El tiempo de espera ahora acepta una tupla (conectar, leer) que se usa para establecer tiempos de espera de conexión y lectura individuales . pypi.org/project/requests/2.4.0
CONvid19
23

Para crear un tiempo de espera puede usar señales .

La mejor manera de resolver este caso es probablemente

  1. Establecer una excepción como manejador de la señal de alarma
  2. Llame a la señal de alarma con un retraso de diez segundos
  3. Llama a la función dentro de un try-except-finallybloque.
  4. El bloque excepto se alcanza si la función expiró.
  5. En el bloque finalmente, aborta la alarma, por lo que no se señala más tarde.

Aquí hay un código de ejemplo:

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)

Hay algunas advertencias sobre esto:

  1. No es seguro para subprocesos, las señales siempre se entregan al subproceso principal, por lo que no puede colocar esto en ningún otro subproceso.
  2. Hay un ligero retraso después de la programación de la señal y la ejecución del código real. Esto significa que el ejemplo expiraría incluso si solo durmiera durante diez segundos.

¡Pero todo está en la biblioteca estándar de Python! Excepto por la función de suspensión de importación, es solo una importación. Si va a utilizar tiempos de espera en muchos lugares, puede poner fácilmente TimeoutException, _timeout y singaling en una función y simplemente llamar a eso. O puede hacer un decorador y ponerlo en funciones, vea la respuesta vinculada a continuación.

También puede configurar esto como un "administrador de contexto" para que pueda usarlo con la withdeclaración:

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 posible inconveniente con este enfoque de administrador de contexto es que no puede saber si el código realmente agotó el tiempo de espera o no.

Fuentes y lecturas recomendadas:

totokaka
fuente
3
Las señales solo se entregan en el hilo principal, por lo que definitivamente no funcionará en otros hilos, probablemente no .
Dima Tisnek
1
El paquete timeout-decorator proporciona un decorador de timeout que usa señales (u opcionalmente multiprocesamiento).
Christian Long
13

Pruebe esta solicitud con tiempo de espera y manejo de errores:

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

Establecer stream=Truey usar r.iter_content(1024). Sí, de eventlet.Timeoutalguna manera no funciona para mí.

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 discusión está aquí https://redd.it/80kp1h

Polv
fuente
es una solicitud vergonzosa que no admite parámetros de tiempo máximo, esta solución es la única que funciona con asyncio
wukong
4

Esto puede ser excesivo, pero la cola de tareas distribuidas de Celery tiene un buen soporte para los tiempos de espera.

En particular, puede definir un límite de tiempo flexible que simplemente genera una excepción en su proceso (para que pueda limpiar) y / o un límite de tiempo difícil que finaliza la tarea cuando se ha excedido el límite de tiempo.

Debajo de las cubiertas, esto utiliza el mismo enfoque de señales que se menciona en su publicación "anterior", pero de una manera más útil y manejable. Y si la lista de sitios web que está monitoreando es larga, podría beneficiarse de su característica principal: todo tipo de formas de administrar la ejecución de una gran cantidad de tareas.

Chris Johnson
fuente
Esta podría ser una buena solución. El problema del tiempo de espera total no está relacionado directamente con python-requestssino con httplib(utilizado por las solicitudes de Python 2.7). El paquete pasa todo lo relacionado timeoutdirectamente a httplib. Creo que nada se puede arreglar en la solicitud porque el proceso puede permanecer durante mucho tiempo en httplib.
hynekcer
@hynekcer, creo que tienes razón. Esta es la razón por la cual detectar los tiempos de espera fuera de proceso y hacerlos cumplir mediante la eliminación limpia de procesos, como lo hace Celery, puede ser un buen enfoque.
Chris Johnson
3

Creo que puede usar multiprocessingy no depender de un paquete de terceros:

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)

El tiempo de espera pasado kwargses el tiempo de espera para obtener cualquier respuesta del servidor, el argumento timeoutes el tiempo de espera para obtener la respuesta completa .

Jorge Leitao
fuente
Esto se puede mejorar con una prueba genérica / excepto en la función privada que detecta todos los errores y los pone en return_dict ['error']. Luego, al final, antes de regresar, verifique si 'error' en return_dict y luego levántelo. También hace que sea mucho más fácil hacer la prueba.
dialt0ne
2

tiempo de espera = (tiempo de espera de conexión, tiempo de espera de lectura de datos) o dar un único argumento (tiempo de espera = 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
fuente
1

este código funciona para socketError 11004 y 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
fuente
Votación a favor de la creatividad
JSmyth
1

A pesar de que la pregunta es sobre solicitudes, me parece muy fácil hacerlo con pycurl CURLOPT_TIMEOUT o CURLOPT_TIMEOUT_MS.

No se requiere roscado ni señalización:

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
fuente
1

En caso de que esté utilizando la opción stream=True, puede hacer esto:

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 solución no necesita señales ni multiprocesamiento.

ub_marco
fuente
1

Solo otra solución (la obtuve de http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads )

Antes de subir, puede averiguar el tamaño del contenido:

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

Pero tenga cuidado, un remitente puede configurar un valor incorrecto en el campo de respuesta 'longitud de contenido'.

Denis Kuzin
fuente
Gracias. Solución limpia y simple. Funciona para mi.
petezurich
0

Si se trata de eso, cree un hilo de vigilancia que arruine el estado interno de las solicitudes después de 10 segundos, por ejemplo:

  • cierra el zócalo subyacente, e idealmente
  • desencadena una excepción si las solicitudes vuelven a intentar la operación

Tenga en cuenta que, según las bibliotecas del sistema, es posible que no pueda establecer una fecha límite para la resolución de DNS.

Dima Tisnek
fuente
0

Bueno, probé muchas soluciones en esta página y seguí enfrentando inestabilidades, bloqueos aleatorios, mal rendimiento de las conexiones.

Ahora estoy usando Curl y estoy muy contento con su funcionalidad de "tiempo máximo" y con el rendimiento global, incluso con una implementación tan pobre:

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

Aquí, definí un parámetro de tiempo máximo de 6 segundos, que abarca tanto el tiempo de conexión como el de transferencia.

Estoy seguro de que Curl tiene un buen enlace de python, si prefiere seguir con la sintaxis pitónica :)

tecnico
fuente
0

Hay un paquete llamado timeout-decorator que puede usar para agotar el tiempo de cualquier función de Python.

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

Utiliza el enfoque de señales que sugieren algunas respuestas aquí. Alternativamente, puede indicarle que use multiprocesamiento en lugar de señales (por ejemplo, si se encuentra en un entorno de subprocesos múltiples).

Cristiano largo
fuente
0

Estoy usando las solicitudes 2.2.1 y eventlet no funcionó para mí. En cambio, pude usar el tiempo de espera gevent ya que gevent se usa en mi servicio para 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)

Tenga en cuenta que gevent.timeout.Timeout no se detecta mediante el manejo general de excepciones. Entonces, explícitamente, atrape gevent.timeout.Timeout o pase una excepción diferente para usarla así: with gevent.Timeout(5, requests.exceptions.Timeout):aunque no se pasa ningún mensaje cuando se genera esta excepción.

xsdf
fuente
-1

Se me ocurrió una solución más directa que es ciertamente fea pero soluciona el problema real. Va un poco así:

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

Puedes leer la explicación completa aquí

Realista
fuente