¿Por qué no regresa request.get ()? ¿Cuál es el tiempo de espera predeterminado que usa request.get ()?

93

En mi script, requests.getnunca regresa:

import requests

print ("requesting..")

# This call never returns!
r = requests.get(
    "http://www.some-site.com",
    proxies = {'http': '222.255.169.74:8080'},
)

print(r.ok)

¿Cuáles podrían ser las posibles razones? ¿Algún remedio? ¿Cuál es el tiempo de espera predeterminado que getusa?

Nawaz
fuente
1
@ user2357112: ¿Importa? Yo dudo.
Nawaz
Definitivamente importa. Si proporciona la URL a la que intenta acceder y el proxy que intenta usar, podemos ver qué sucede cuando intentamos enviar solicitudes similares.
user2357112 apoya a Monica el
1
@ user2357112: Muy bien. Editó la pregunta.
Nawaz
2
Su proxy también es incorrecto. Debe especificar que de este modo: proxies={'http': 'http://222.255.169.74:8080'}. Esa podría ser la razón por la que no se completa sin un tiempo de espera.
Ian Stapleton Cordasco

Respuestas:

130

¿Cuál es el tiempo de espera predeterminado que se usa?

El tiempo de espera predeterminado es None, lo que significa que esperará (se bloqueará) hasta que se cierre la conexión.

¿Qué sucede cuando pasa un valor de tiempo de espera?

r = requests.get(
    'http://www.justdial.com',
    proxies={'http': '222.255.169.74:8080'},
    timeout=5
)
ron rothman
fuente
3
Creo que tienes razón. Nonesignifica infinito (o "esperar hasta que se cierre la conexión"). Si me paso el tiempo de espera, ¡vuelve!
Nawaz
14
@User timeout funciona tan bien con https como con http
jaapz
Esto parece realmente difícil de encontrar en los documentos al buscar en Google o de otra manera. ¿Alguien sabe dónde aparece esto en los documentos?
wordsforthewise
Gracias, hacerlo print(requests.request.__doc__)en IPython es más de lo que estaba buscando. Me preguntaba qué otros argumentos opcionales request.get()había.
wordsforthewise
40

De la documentación de solicitudes :

Puede decirle a Solicitudes que dejen de esperar una respuesta después de un número determinado de segundos con el parámetro de tiempo de espera:

>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

Nota:

el tiempo de espera no es un límite de tiempo para toda la descarga de respuesta; más bien, se genera una excepción si el servidor no ha emitido una respuesta durante los segundos de tiempo de espera (más precisamente, si no se han recibido bytes en el socket subyacente durante los segundos de tiempo de espera).

Me pasa mucho que request.get () tarda mucho en regresar, incluso si timeoutes de 1 segundo. Hay algunas formas de solucionar 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):
        if kwargs['connect'] is None:
            kwargs['connect'] = 5
        if kwargs['read'] is None:
            kwargs['read'] = 5
        super(MyTimeout, self).__init__(*args, **kwargs)

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 es posible que necesite una depuración rápida, simplemente lo escribí directamente en la ventana de GitHub).

2. Utilice 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 solo valor para el tiempo de espera, así:

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

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

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

NOTA: Desde entonces, el cambio se ha combinado con el proyecto principal de Solicitudes .

3. Usando evenleto signalcomo ya se mencionó en la pregunta similar: tiempo de espera para solicitudes de Python. Obtener la respuesta completa

Hieu
fuente
7
Nunca respondió cuál es el valor predeterminado
Usuario
Cotización: puede indicar a las solicitudes que dejen de esperar una respuesta después de un número determinado de segundos con el parámetro de tiempo de espera. Casi todo el código de producción debería usar este parámetro en casi todas las solicitudes. Si no lo hace, su programa puede bloquearse indefinidamente: tenga en cuenta que el tiempo de espera no es un límite de tiempo para toda la descarga de respuesta; más bien, se genera una excepción si el servidor no ha emitido una respuesta durante los segundos de tiempo de espera (más precisamente, si no se han recibido bytes en el socket subyacente durante los segundos de tiempo de espera). Si no se especifica ningún tiempo de espera de forma explícita, las solicitudes no expiran.
Día
El código tiene un error tipográfico: las solicitudes de importación <nueva línea aquí> de las solicitudes. Los adaptadores importan TimeoutSauce
Sinan Çetinkaya
4

Quería un tiempo de espera predeterminado agregado fácilmente a un montón de código (asumiendo que el tiempo de espera resuelve su problema)

Esta es la solución que recogí de un ticket enviado al repositorio de solicitudes.

crédito: https://github.com/kennethreitz/requests/issues/2011#issuecomment-477784399

La solución son las últimas dos líneas aquí, pero muestro más código para un mejor contexto. Me gusta usar una sesión para reintentar el comportamiento.

import requests
import functools
from requests.adapters import HTTPAdapter,Retry


def requests_retry_session(
        retries=10,
        backoff_factor=2,
        status_forcelist=(500, 502, 503, 504),
        session=None,
        ) -> requests.Session:
    session = session or requests.Session()
    retry = Retry(
            total=retries,
            read=retries,
            connect=retries,
            backoff_factor=backoff_factor,
            status_forcelist=status_forcelist,
            )
    adapter = HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    # set default timeout
    for method in ('get', 'options', 'head', 'post', 'put', 'patch', 'delete'):
        setattr(session, method, functools.partial(getattr(session, method), timeout=30))
    return session

entonces puedes hacer algo como esto:

requests_session = requests_retry_session()
r = requests_session.get(url=url,...
Tim Richardson
fuente
4

Revisó todas las respuestas y llegó a la conclusión de que el problema aún existe. En algunos sitios, las solicitudes pueden colgarse infinitamente y el uso de multiprocesamiento parece ser excesivo. Aquí está mi enfoque (Python 3.5+):

import asyncio

import aiohttp


async def get_http(url):
    async with aiohttp.ClientSession(conn_timeout=1, read_timeout=3) as client:
        try:
            async with client.get(url) as response:
                content = await response.text()
                return content, response.status
        except Exception:
            pass


loop = asyncio.get_event_loop()
task = loop.create_task(get_http('http://example.com'))
loop.run_until_complete(task)
result = task.result()
if result is not None:
    content, status = task.result()
    if status == 200:
        print(content)

ACTUALIZAR

Si recibe una advertencia de obsolescencia sobre el uso de conn_timeout y read_timeout, verifique cerca del final de ESTA referencia para saber cómo usar la estructura de datos ClientTimeout. Una forma sencilla de aplicar esta estructura de datos según la referencia vinculada al código original anterior sería:

async def get_http(url):
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as client:
        try:
            etc.
Alex Polekha
fuente
2
@Nawaz Python 3.5+. Gracias por la pregunta, actualicé la respuesta con la versión de Python. Es un código Python legal. Por favor, eche un vistazo a la documentación de aiohttp aiohttp.readthedocs.io/en/stable/index.html
Alex Polekha
Esto resolvió mis problemas cuando otros métodos no lo harían. Py 3.7. Debido a las depricaciones, tuve que usar ... timeout = aiohttp.ClientTimeout (total = 60) async con aiohttp.ClientSession (timeout = timeout) como cliente:
Thom Ives
2

Parchear la función "enviar" documentada solucionará este problema para todas las solicitudes, incluso en muchas bibliotecas dependientes y sdk. Cuando aplique parches a las bibliotecas, asegúrese de parchear las funciones compatibles / documentadas, no TimeoutSauce; de ​​lo contrario, puede terminar perdiendo silenciosamente el efecto de su parche.

import requests

DEFAULT_TIMEOUT = 180

old_send = requests.Session.send

def new_send(*args, **kwargs):
     if kwargs.get("timeout", None) is None:
         kwargs["timeout"] = DEFAULT_TIMEOUT
     return old_send(*args, **kwargs)

requests.Session.send = new_send

Los efectos de no tener ningún tiempo de espera son bastante graves, y el uso de un tiempo de espera predeterminado casi nunca puede romper nada, porque el propio TCP también tiene tiempos de espera predeterminados.

Erik Aronesty
fuente
0

En mi caso, la razón de "request.get never returns" es porque el requests.get()intento de conectarme al host se resolvió con ipv6 ip primero . Si algo salió mal para conectar esa ip ipv6 y se atasca, entonces vuelve a intentar ipv4 ip solo si configuro explícitamente timeout=<N seconds>y alcanzo el tiempo de espera.

Mi solución es parchear el python socketpara ignorar ipv6 (o ipv4 si ipv4 no funciona), esta respuesta o esta respuesta funcionan para mí.

Quizás se pregunte por qué funciona el curlcomando, porque curlconecte ipv4 sin esperar a que se complete ipv6. Puede rastrear las llamadas al sistema de socket con strace -ff -e network -s 10000 -- curl -vLk '<your url>'command. Para Python, strace -ff -e network -s 10000 -- python3 <your python script>se puede usar el comando.

Fruta
fuente