W moim skrypcie requests.get
nigdy 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 get
używa?
python
get
python-requests
Nawaz
źródło
źródło
proxies={'http': 'http://222.255.169.74:8080'}
. Może dlatego nie kończy się bez limitu czasu.Odpowiedzi:
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 )
źródło
None
oznacza nieskończony (lub „poczekaj, aż połączenie zostanie zamknięte”). Jeśli sam przekroczę limit czasu, to powróci!print(requests.request.__doc__)
praca w IPythonie jest jednak bardziej tym, czego szukałem. Zastanawiałem się, jakie były inne opcjonalne argumentyrequest.get()
.Z dokumentacji wniosków :
Często zdarza mi się, że request.get () zwraca bardzo dużo czasu, nawet jeśli
timeout
wynosi 1 sekundę. Istnieje kilka sposobów rozwiązania tego problemu:1. Użyj
TimeoutSauce
klasy wewnętrznejOd: https://github.com/kennethreitz/requests/issues/1928#issuecomment-35811896
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
UWAGA: zmiana została od tego czasu scalona z głównym projektem Requests .
3. Użycie
evenlet
lubsignal
jak już wspomniano w podobnym pytaniu: Limit czasu dla żądań pythonaźródło
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:
źródło
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.
źródło
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.
źródło
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
socket
celu 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
curl
polecenie działa, ponieważcurl
podłą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 Pythonastrace -ff -e network -s 10000 -- python3 <your python script>
można użyć polecenia.źródło