Perché richieste.get () non ritorna? Qual è il timeout predefinito utilizzato da requests.get ()?


93

Nel mio script, requests.getnon ritorna mai:

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)

Quali potrebbero essere le possibili ragioni? Qualche rimedio? Qual è il timeout predefinito getutilizzato?


1
@ user2357112: importa? Io dubito.
Nawaz

È decisamente importante. Se fornisci l'URL a cui stai tentando di accedere e il proxy che stai tentando di utilizzare, possiamo vedere cosa succede quando proviamo a inviare richieste simili.
user2357112 supporta Monica

1
@ user2357112: Va bene. Ha modificato la domanda.
Nawaz

2
Anche il tuo proxy non è corretto. È necessario specificare in questo modo: proxies={'http': 'http://222.255.169.74:8080'}. Questo potrebbe essere il motivo per cui non viene completato senza un timeout.
Ian Stapleton Cordasco

Risposte:


130

Qual è il timeout predefinito che viene utilizzato?

Il timeout predefinito è None, il che significa che attenderà (si bloccherà) fino alla chiusura della connessione.

Cosa succede quando passi un valore di timeout?

r = requests.get(
    'http://www.justdial.com',
    proxies={'http': '222.255.169.74:8080'},
    timeout=5
)

3
Penso tu abbia ragione. Nonesignifica infinito (o "aspetta fino a quando la connessione è chiusa"). Se passo il timeout da solo, ritorna!
Nawaz

14
@ Il timeout utente funziona altrettanto bene con https come con http
jaapz

Questo sembra davvero difficile da trovare nei documenti su Google o in altro modo. Qualcuno sa dove questo compare nei documenti?
parole per il


Grazie, fare print(requests.request.__doc__)in IPython è più di quello che stavo cercando però. Mi chiedevo quali altri argomenti opzionali request.get()ci fossero.
parole per il

40

Dalla documentazione richiesta :

Puoi dire a Requests di interrompere l'attesa di una risposta dopo un determinato numero di secondi con il parametro timeout:

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

il timeout non è un limite di tempo per l'intero download della risposta; piuttosto, viene sollevata un'eccezione se il server non ha emesso una risposta per timeout secondi (più precisamente, se non sono stati ricevuti byte sul socket sottostante per timeout secondi).

Succede spesso che request.get () impieghi molto tempo per tornare anche se il file timeout è 1 secondo. Ci sono alcuni modi per superare questo problema:

1. Usa il file TimeoutSauce classe 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):
        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

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

2. Utilizza 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 specifichi 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 che a quello di lettura. Specificare una tupla se si desidera impostare i valori separatamente:

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

NOTA: da allora la modifica è stata unita al progetto principale Requests .

3. Usando evenleto signalcome già menzionato nella domanda simile: Timeout per le richieste python. Ottieni l'intera risposta


7
Non hai mai risposto quale sia l'impostazione predefinita
utente

Quote: puoi dire a Requests di interrompere l'attesa di una risposta dopo un determinato numero di secondi con il parametro timeout. Quasi tutto il codice di produzione dovrebbe utilizzare questo parametro in quasi tutte le richieste. In caso contrario, il programma potrebbe bloccarsi indefinitamente: Nota che il timeout non è un limite di tempo per l'intero download della risposta; piuttosto, viene sollevata un'eccezione se il server non ha emesso una risposta per timeout secondi (più precisamente, se non sono stati ricevuti byte sul socket sottostante per timeout secondi). Se nessun timeout è specificato esplicitamente, le richieste non scadono.
DDay

Il codice ha un errore di battitura: import requests <nuova riga qui> da requests.adapters import TimeoutSauce
Sinan Çetinkaya

4

Volevo un timeout predefinito facilmente aggiunto a un mucchio di codice (supponendo che il timeout risolva il tuo problema)

Questa è la soluzione che ho preso da un ticket inviato al repository per le Richieste.

credito: https://github.com/kennethreitz/requests/issues/2011#issuecomment-477784399

La soluzione è l'ultima coppia di righe qui, ma mostro più codice per un contesto migliore. Mi piace usare una sessione per ritentare il comportamento.

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

allora puoi fare qualcosa del genere:

requests_session = requests_retry_session()
r = requests_session.get(url=url,...

4

Ho esaminato tutte le risposte e sono giunto alla conclusione che il problema esiste ancora. Su alcuni siti le richieste possono bloccarsi all'infinito e l'utilizzo del multiprocessing sembra essere eccessivo. Ecco il mio approccio (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)

AGGIORNARE

Se ricevi un avviso di deprecazione sull'utilizzo di conn_timeout e read_timeout, controlla nella parte inferiore di QUESTO riferimento per sapere come utilizzare la struttura dati di ClientTimeout. Un modo semplice per applicare questa struttura dati per il riferimento collegato al codice originale sopra sarebbe:

async def get_http(url):
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as client:
        try:
            etc.

2
@Nawaz Python 3.5+. Grazie per la domanda, aggiornata la risposta con la versione Python. È codice Python legale. Dai un'occhiata alla documentazione aiohttp aiohttp.readthedocs.io/en/stable/index.html
Alex Polekha

Questo ha risolto i miei problemi quando altri metodi no. Py 3.7. A causa di svantaggi, ho dovuto utilizzare ... timeout = aiohttp.ClientTimeout (total = 60) async with aiohttp.ClientSession (timeout = timeout) come client:
Thom Ives

2

Applicare una patch alla funzione "invia" documentata risolverà questo problema per tutte le richieste, anche in molte librerie e sdk dipendenti. Quando si applicano le patch alle librerie, assicurarsi di patchare le funzioni supportate / documentate, non TimeoutSauce, altrimenti si rischia di perdere silenziosamente l'effetto della patch.

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

Gli effetti dell'assenza di timeout sono piuttosto gravi e l'uso di un timeout predefinito non può quasi mai interrompere nulla, perché anche il TCP stesso ha timeout predefiniti.


0

Nel mio caso, il motivo di "requests.get non ritorna mai" è perché il requests.get()tentativo di connessione all'host è stato risolto prima con ipv6 ip . Se qualcosa è andato storto per connettere quell'ip ipv6 e rimanere bloccato, allora riprova ipv4 ip solo se ho impostato esplicitamentetimeout=<N seconds> e premo il timeout.

La mia soluzione è applicare patch a scimmia al python socketper ignorare ipv6 (o ipv4 se ipv4 non funziona), questa o questa risposta funzionano per me.

Potresti chiederti perché il curlcomando funziona, perché curlconnetti ipv4 senza attendere il completamento di ipv6. Puoi tracciare le chiamate di sistema del socket con strace -ff -e network -s 10000 -- curl -vLk '<your url>'command. Per python, è strace -ff -e network -s 10000 -- python3 <your python script>possibile utilizzare il comando.

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.