Pourquoi requests.get () ne revient-il pas? Quel est le délai d'expiration par défaut utilisé par requests.get ()?

93

Dans mon script, requests.getne retourne jamais:

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)

Quelle pourrait être la ou les raisons possibles? Un remède? Quel est le délai d'expiration par défaut getutilisé?

Nawaz
la source
1
@ user2357112: Est-ce important? Je doute.
Nawaz
C'est vraiment important. Si vous fournissez l'URL à laquelle vous essayez d'accéder et le proxy que vous essayez d'utiliser, nous pouvons voir ce qui se passe lorsque nous essayons d'envoyer des demandes similaires.
user2357112 prend en charge Monica le
1
@ user2357112: Très bien. Modifié la question.
Nawaz
2
Votre proxy est également incorrect. Vous devez le spécifier ainsi: proxies={'http': 'http://222.255.169.74:8080'}. Cela pourrait expliquer pourquoi il ne se termine pas sans délai.
Ian Stapleton Cordasco

Réponses:

129

Quel est le délai d'expiration par défaut utilisé?

Le délai d'expiration par défaut est None, ce qui signifie qu'il attendra (se bloque) jusqu'à ce que la connexion soit fermée.

Que se passe-t-il lorsque vous passez une valeur de délai d'expiration?

r = requests.get(
    'http://www.justdial.com',
    proxies={'http': '222.255.169.74:8080'},
    timeout=5
)
Ron Rothman
la source
3
Je pense que tu as raison. Nonesignifie infini (ou "attendez que la connexion soit fermée"). Si je passe le timeout moi-même, il revient!
Nawaz
14
@User timeout fonctionne aussi bien avec https qu'avec http
jaapz
Cela semble vraiment difficile à trouver dans la documentation en googlant ou autrement. Quelqu'un sait-il où cela apparaît dans la documentation?
wordsforthewise
Merci, faire print(requests.request.__doc__)en IPython est plus ce que je recherchais. Je me demandais quels étaient les autres arguments facultatifs request.get().
wordsforthewise
40

De la documentation des demandes :

Vous pouvez dire aux requêtes d'arrêter d'attendre une réponse après un certain nombre de secondes avec le paramètre 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)

Remarque:

le délai d'expiration n'est pas une limite de temps pour tout le téléchargement de la réponse; au lieu de cela, une exception est levée si le serveur n'a pas émis de réponse pendant les secondes d'expiration (plus précisément, si aucun octet n'a été reçu sur le socket sous-jacent pendant les secondes d'expiration).

Il m'arrive souvent que requests.get () prenne beaucoup de temps à revenir même si le timeoutest 1 seconde. Il existe plusieurs moyens de résoudre 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):
        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

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

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

REMARQUE: la modification a depuis été fusionnée avec le projet Requests principal .

3. En utilisant evenletou signalcomme déjà mentionné dans la question similaire: Timeout for python requests.get réponse entière

Hieu
la source
7
Vous n'avez jamais répondu à la valeur par défaut
Utilisateur
Quote: Vous pouvez dire aux requêtes d'arrêter d'attendre une réponse après un certain nombre de secondes avec le paramètre timeout. Presque tout le code de production doit utiliser ce paramètre dans presque toutes les demandes. Le non-respect de cette consigne peut entraîner le blocage indéfini de votre programme: notez que le délai d'expiration n'est pas une limite de temps pour tout le téléchargement de la réponse; au lieu de cela, une exception est levée si le serveur n'a pas émis de réponse pendant les secondes d'expiration (plus précisément, si aucun octet n'a été reçu sur le socket sous-jacent pendant les secondes d'expiration). Si aucun délai n'est spécifié explicitement, les demandes n'expirent pas.
DDay
Le code a une faute de frappe: importer les demandes <nouvelle ligne ici> de requests.adapters import TimeoutSauce
Sinan Çetinkaya
4

Je voulais un délai d'expiration par défaut facilement ajouté à un tas de code (en supposant que le délai d'expiration résout votre problème)

C'est la solution que j'ai choisie à partir d'un ticket soumis au référentiel pour les demandes.

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

La solution est les deux dernières lignes ici, mais je montre plus de code pour un meilleur contexte. J'aime utiliser une session pour le comportement de nouvelle tentative.

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

alors vous pouvez faire quelque chose comme ceci:

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

Examiné toutes les réponses et est arrivé à la conclusion que le problème existe toujours. Sur certains sites, les demandes peuvent se bloquer indéfiniment et l'utilisation du multitraitement semble être excessive. Voici mon approche (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)

METTRE À JOUR

Si vous recevez un avertissement d'obsolescence concernant l'utilisation de conn_timeout et read_timeout, vérifiez près du bas de CETTE référence pour savoir comment utiliser la structure de données ClientTimeout. Un moyen simple d'appliquer cette structure de données par la référence liée au code d'origine ci-dessus serait:

async def get_http(url):
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as client:
        try:
            etc.
Alex Polekha
la source
2
@Nawaz Python 3.5+. Merci pour la question, a mis à jour la réponse avec la version Python. C'est du code Python légal. Veuillez consulter la documentation aiohttp aiohttp.readthedocs.io/en/stable/index.html
Alex Polekha
Cela a résolu mes problèmes alors que d'autres méthodes ne le faisaient pas. Py 3.7. En raison de déprications, a dû utiliser ... timeout = aiohttp.ClientTimeout (total = 60) async avec aiohttp.ClientSession (timeout = timeout) comme client:
Thom Ives
2

La mise à jour de la fonction "envoyer" documentée résoudra ce problème pour toutes les requêtes - même dans de nombreuses bibliothèques et sdk dépendants. Lorsque vous corrigez des bibliothèques, assurez-vous de corriger les fonctions prises en charge / documentées, et non TimeoutSauce, sinon vous risquez de perdre silencieusement l'effet de votre correctif.

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

Les effets de l'absence de délai d'expiration sont assez graves, et l'utilisation d'un délai d'expiration par défaut ne peut presque jamais rien casser - car TCP lui-même a également des délais d'expiration par défaut.

Erik Aronesty
la source
0

Dans mon cas, la raison de "requests.get ne retourne jamais" est que la requests.get()tentative de connexion à l'hôte résolue avec ipv6 ip en premier . Si quelque chose ne va pas pour connecter cette ip ipv6 et rester bloqué, alors il réessaye ipv4 ip uniquement si je définis explicitement timeout=<N seconds>et atteint le délai d'expiration.

Ma solution consiste à patcher le python socketpour ignorer ipv6 (ou ipv4 si ipv4 ne fonctionne pas), cette réponse ou cette réponse fonctionne pour moi.

Vous vous demandez peut-être pourquoi la curlcommande fonctionne, car curlconnectez ipv4 sans attendre la fin de l'ipv6. Vous pouvez tracer les appels système de socket avec la strace -ff -e network -s 10000 -- curl -vLk '<your url>'commande. Pour python, la strace -ff -e network -s 10000 -- python3 <your python script>commande peut être utilisée.

Fruit
la source