Dlaczego request.get () nie zwraca? Jaki jest domyślny limit czasu używany przez request.get ()?

93

W moim skrypcie requests.getnigdy nie zwraca:

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)

Jakie mogą być możliwe przyczyny? Jakieś lekarstwo? Jaki jest domyślny limit czasu, którego getużywa?

Nawaz
źródło
1
@ user2357112: Czy to ma znaczenie? Wątpię.
Nawaz
To zdecydowanie ma znaczenie. Jeśli podasz adres URL, do którego próbujesz uzyskać dostęp, oraz serwer proxy, którego próbujesz użyć, możemy zobaczyć, co się stanie, gdy spróbujemy wysłać podobne żądania.
user2357112 obsługuje Monikę
1
@ user2357112: W porządku. Zredagował pytanie.
Nawaz
2
Twój serwer proxy jest również nieprawidłowy. Musisz określić to tak: proxies={'http': 'http://222.255.169.74:8080'}. Może dlatego nie kończy się bez limitu czasu.
Ian Stapleton Cordasco

Odpowiedzi:

132

Jaki jest domyślny limit czasu, który pobiera używa?

Domyślny limit czasu to None, co oznacza, że ​​będzie czekał (zawiesi się), aż połączenie zostanie zamknięte.

Co się stanie, gdy przekażesz wartość limitu czasu?

r = requests.get(
    'http://www.justdial.com',
    proxies={'http': '222.255.169.74:8080'},
    timeout=5
)
ron rothman
źródło
3
Myślę, że masz rację. Noneoznacza nieskończony (lub „poczekaj, aż połączenie zostanie zamknięte”). Jeśli sam przekroczę limit czasu, to powróci!
Nawaz
14
@ Limit czasu użytkownika działa tak samo dobrze z https, jak z http
jaapz
Wydaje się, że jest to naprawdę trudne do znalezienia w dokumentach przez wyszukiwanie w Google lub w inny sposób. Czy ktoś wie, gdzie to pojawia się w dokumentach?
słowa z
Dzięki, print(requests.request.__doc__)praca w IPythonie jest jednak bardziej tym, czego szukałem. Zastanawiałem się, jakie były inne opcjonalne argumenty request.get().
słowa z
40

Z dokumentacji wniosków :

Możesz nakazać Requests, aby przestały czekać na odpowiedź po określonej liczbie sekund za pomocą parametru 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)

Uwaga:

Limit czasu nie jest limitem czasu na pobranie całej odpowiedzi; raczej wyjątek jest zgłaszany, jeśli serwer nie wysłał odpowiedzi dla przekroczenia limitu czasu (a dokładniej, jeśli żadne bajty nie zostały odebrane w podstawowym gnieździe przez czas określony w sekundach).

Często zdarza mi się, że request.get () zwraca bardzo dużo czasu, nawet jeśli timeoutwynosi 1 sekundę. Istnieje kilka sposobów rozwiązania tego problemu:

1. Użyj TimeoutSauceklasy wewnętrznej

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

Ten kod powinien spowodować, że ustawimy limit czasu odczytu jako równy limitowi czasu połączenia, który jest wartością limitu czasu, którą przekazujesz w wywołaniu Session.get (). (Zauważ, że tak naprawdę nie testowałem tego kodu, więc może wymagać szybkiego debugowania, po prostu napisałem go bezpośrednio w oknie GitHub.)

2. Użyj rozwidlenia żądań od kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

Z jego dokumentacji: https://github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

Jeśli określisz jedną wartość limitu czasu, na przykład:

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

Wartość limitu czasu zostanie zastosowana zarówno do połączenia, jak i do limitów czasu odczytu. Określ krotkę, jeśli chcesz ustawić wartości osobno:

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

UWAGA: zmiana została od tego czasu scalona z głównym projektem Requests .

3. Użycie evenletlub signaljak już wspomniano w podobnym pytaniu: Limit czasu dla żądań pythona

Hieu
źródło
8
Nigdy nie odpowiedziałeś, jaka jest wartość domyślna
Użytkownik
Cytat: Możesz nakazać requestom, aby przestały czekać na odpowiedź po określonej liczbie sekund za pomocą parametru limitu czasu. Prawie cały kod produkcyjny powinien używać tego parametru w prawie wszystkich żądaniach. Niezastosowanie się do tego może spowodować zawieszenie programu na czas nieokreślony: Uwaga: przekroczenie limitu czasu nie jest limitem czasu na pobranie całej odpowiedzi; raczej wyjątek jest zgłaszany, jeśli serwer nie wysłał odpowiedzi dla przekroczenia limitu czasu (a dokładniej, jeśli żadne bajty nie zostały odebrane w podstawowym gnieździe przez czas określony w sekundach). Jeśli nie określono jawnie limitu czasu, żądania nie przekraczają limitu czasu.
Dzień
Kod zawiera literówkę: żądania importu <tutaj nowa linia> z request.adapters import TimeoutSauce
Sinan Çetinkaya
4

Chciałem, aby domyślny limit czasu można było łatwo dodać do zestawu kodu (zakładając, że limit czasu rozwiązuje problem)

To jest rozwiązanie, które wybrałem z biletu przesłanego do repozytorium żądań.

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

Rozwiązaniem jest kilka ostatnich wierszy tutaj, ale pokazuję więcej kodu dla lepszego kontekstu. Lubię używać sesji do ponawiania prób.

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

wtedy możesz zrobić coś takiego:

requests_session = requests_retry_session()
r = requests_session.get(url=url,...
Tim Richardson
źródło
4

Przejrzałem wszystkie odpowiedzi i doszedłem do wniosku, że problem nadal istnieje. W niektórych witrynach żądania mogą się zawieszać w nieskończoność, a korzystanie z przetwarzania wieloprocesowego wydaje się być przesadą. Oto moje podejście (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)

AKTUALIZACJA

Jeśli pojawi się ostrzeżenie o wycofaniu informacji o używaniu conn_timeout i read_timeout, należy sprawdzić w dolnej części TEGO odwołania, jak używać struktury danych ClientTimeout. Jednym prostym sposobem zastosowania tej struktury danych zgodnie z odsyłaczem do oryginalnego kodu powyżej jest:

async def get_http(url):
    timeout = aiohttp.ClientTimeout(total=60)
    async with aiohttp.ClientSession(timeout=timeout) as client:
        try:
            etc.
Alex Polekha
źródło
2
@Nawaz Python 3.5+. Dziękuję za pytanie, zaktualizowałem odpowiedź o wersję Pythona. To legalny kod Pythona. Zapoznaj się z dokumentacją aiohttp aiohttp.readthedocs.io/en/stable/index.html
Alex Polekha
To rozwiązało moje problemy, podczas gdy inne metody nie. Py 3.7. Ze względu na niedostatki musiałem użyć ... timeout = aiohttp.ClientTimeout (total = 60) async z aiohttp.ClientSession (timeout = timeout) jako klient:
Thom Ives
2

Poprawienie udokumentowanej funkcji „wyślij” naprawi to dla wszystkich żądań - nawet w wielu zależnych bibliotekach i pakietach SDK. Podczas łatania bibliotek pamiętaj, aby załatać obsługiwane / udokumentowane funkcje, a nie TimeoutSauce - w przeciwnym razie możesz po cichu utracić efekt swojej poprawki.

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

Skutki braku limitu czasu są dość poważne, a użycie domyślnego limitu czasu prawie nigdy nie może niczego zepsuć - ponieważ sam TCP ma również domyślne limity czasu.

Erik Aronesty
źródło
1

W moim przypadku przyczyną „request.get nigdy nie zwraca” jest requests.get()próba połączenia się najpierw z hostem rozwiązana za pomocą ipv6 ip . Jeśli coś poszło nie tak, aby podłączyć to ipv6 ip i utknęło, to ponowi próbę ipv4 ip tylko jeśli wyraźnie ustawię timeout=<N seconds>i przekroczę limit czasu.

Moim rozwiązaniem jest małpa łatanie Pythona w socketcelu zignorowania ipv6 (lub ipv4, jeśli ipv4 nie działa), albo ta odpowiedź, albo ta odpowiedź działa dla mnie.

Możesz się zastanawiać, dlaczego curlpolecenie działa, ponieważ curlpodłącz ipv4 bez czekania na zakończenie ipv6. Możesz śledzić wywołania systemowe gniazd za pomocą strace -ff -e network -s 10000 -- curl -vLk '<your url>'polecenia. W przypadku Pythona strace -ff -e network -s 10000 -- python3 <your python script>można użyć polecenia.

Owoc
źródło