Limit czasu dla żądań Pythona. Pobierz całą odpowiedź

169

Zbieram statystyki na temat listy stron internetowych i dla uproszczenia używam zapytań. Oto mój kod:

data=[]
websites=['http://google.com', 'http://bbc.co.uk']
for w in websites:
    r= requests.get(w, verify=False)
    data.append( (r.url, len(r.content), r.elapsed.total_seconds(), str([(l.status_code, l.url) for l in r.history]), str(r.headers.items()), str(r.cookies.items())) )

Teraz chcę requests.getwygasnąć po 10 sekundach, aby pętla nie utknęła.

Ta kwestia była przedmiotem zainteresowania przed zbyt ale żadna z odpowiedzi są czyste. Będę za to nagradzać, aby uzyskać dobrą odpowiedź.

Słyszałem, że być może nieużywanie próśb jest dobrym pomysłem, ale w takim razie jak mam uzyskać oferty żądań miłych rzeczy. (te w krotce)

Kiarash
źródło
1
Jakiej odpowiedzi szukasz? (lub innymi słowy, dlaczego obecne odpowiedzi nie są dla Ciebie wystarczające?)
yuvi
Jesteśmy w okresie karencji nagrody. Czas wybrać odpowiedź?
totokaka
Nadal decyduję między rozwiązaniem eventlet a sygnałami. Zadam pytanie do dzisiejszego wieczoru.
Kiarash

Odpowiedzi:

137

A co z korzystaniem z eventletu? Jeśli chcesz przekroczyć limit czasu żądania po 10 sekundach, nawet jeśli odbierane są dane, ten fragment będzie działał dla Ciebie:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)
Alvaro
źródło
114
Z pewnością jest to niepotrzebnie skomplikowane.
holdenweb
7
Dziękuję Ci. Teraz rozumiem wyższość techniczną twojego rozwiązania (którą określiłeś dość zwięźle na początku swojej odpowiedzi) i zagłosowałem za nią. Problem z modułami innych firm nie polega na ich importowaniu, ale na upewnieniu się, że są do zaimportowania, stąd moje własne preferencje dotyczące korzystania z biblioteki standardowej tam, gdzie to możliwe.
holdenweb
9
Jest eventlet.monkey_patch()wymagane?
Użytkownik
3
Tak, socketmoduł wymaga łatki małpy, więc przynajmniej będziesz potrzebowaćeventlet.monkey_patch(socket=True)
Alvaro
52
Od 2018 roku ta odpowiedź jest nieaktualna. Użyjrequests.get('https://github.com', timeout=5)
CONvid19
313

Ustaw parametr limitu czasu :

r = requests.get(w, verify=False, timeout=10) # 10 seconds

Dopóki nie ustawisz stream=Truetego żądania, spowoduje requests.get()to przekroczenie limitu czasu wywołania, jeśli połączenie trwa dłużej niż dziesięć sekund lub jeśli serwer nie wyśle ​​danych przez więcej niż dziesięć sekund.

Lukasa
źródło
31
To nie dotyczy całej odpowiedzi. requests.readthedocs.org/en/latest/user/quickstart/#timeouts
Kiarash
1
Tak, w pewnych okolicznościach. Jedna z tych okoliczności należy do Ciebie. =) Zapraszam do obejrzenia kodu, jeśli nie jesteś przekonany.
Lukasa,
jakie są okoliczności
Kiarash
1
Właśnie to sprawdziłem i nigdy się nie zatrzymało: r = requests.get (' ipv4.download.thinkbroadband.com/1GB.zip ', timeout = 20)
Kiarash,
5
Ach, przepraszam, źle zrozumiałem, co miałeś na myśli, mówiąc „całą odpowiedź”. Tak, masz rację: nie jest to górny limit całkowitego czasu oczekiwania.
Lukasa,
85

AKTUALIZACJA: https://requests.readthedocs.io/en/master/user/advanced/#timeouts

W nowej wersji requests:

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 connectdo readlimitów czasu, jak i do limitów czasu. Określ krotkę, jeśli chcesz ustawić wartości osobno:

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

Jeśli serwer zdalny działa bardzo wolno, możesz nakazać Requests, aby czekały wiecznie na odpowiedź, przekazując None jako wartość limitu czasu, a następnie pobierając filiżankę kawy.

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

Moja stara (prawdopodobnie nieaktualna) odpowiedź (która została opublikowana dawno temu):

Istnieją inne sposoby 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):
        connect = kwargs.get('connect', 5)
        read = kwargs.get('read', connect)
        super(MyTimeout, self).__init__(connect=connect, read=read)

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ń z 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))

kevinburke zażądał scalenia go z głównym projektem żądań, ale nie został jeszcze zaakceptowany.

Hieu
źródło
opcja 1 nie działa. jeśli będziesz kontynuować czytanie tego wątku, inni powiedzieli: „Obawiam się, że to nie zadziała w Twoim przypadku użycia. Funkcja limitu czasu odczytu znajduje się w zakresie pojedynczego wywołania recv () gniazda, więc jeśli serwer przestaje wysyłać dane na dłużej niż limit czasu odczytu, który przerwiemy. "
Kiarash
Jest jeszcze jedno fajne rozwiązanie w tym wątku, używając Signal, które też nie zadziałałoby dla mnie, ponieważ używam Windowsa i signal.alarm jest tylko linux.
Kiarash
@Kiarash Jeszcze tego nie testowałem. Jednak, jak rozumiem, gdy powiedział Lukasa this won't work for you use-case. Miał na myśli to, że nie działa ze strumieniem mp3, którego chce inny facet.
Hieu
1
@Hieu - to zostało połączone w innym żądaniu ściągnięcia - github.com/kennethreitz/requests/pull/…
yprez
timeout = Brak nie blokuje połączenia.
crazydan
49

timeout = int(seconds)

Ponieważ requests >= 2.4.0możesz użyć timeoutargumentu, tj .:

requests.get('https://duckduckgo.com/', timeout=10)

Uwaga:

timeoutnie jest limitem czasu na pobranie całej odpowiedzi; zamiast tego generowany exceptionjest znak an, jeśli serwer nie wysłał odpowiedzi w przypadku 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.

CONvid19
źródło
Jaka wersja żądań ma nowy parametr limitu czasu?
Rusty
1
Wydaje się, że od wersji 2.4.0: Obsługa limitów czasu połączenia! Limit czasu akceptuje teraz krotkę (połącz, odczyt), która jest używana do ustawiania indywidualnych limitów czasu połączenia i odczytu . pypi.org/project/requests/2.4.0
CONvid19
23

Aby utworzyć limit czasu, możesz użyć sygnałów .

Najlepszym sposobem rozwiązania tego przypadku jest prawdopodobnie

  1. Ustaw wyjątek jako procedurę obsługi sygnału alarmu
  2. Wywołaj sygnał alarmu z dziesięciosekundowym opóźnieniem
  3. Wywołaj funkcję wewnątrz try-except-finallybloku.
  4. Blok except zostaje osiągnięty, jeśli upłynął limit czasu funkcji.
  5. W ostatnim bloku przerywasz alarm, więc nie jest on później wysyłany.

Oto przykładowy kod:

import signal
from time import sleep

class TimeoutException(Exception):
    """ Simple Exception to be called on timeouts. """
    pass

def _timeout(signum, frame):
    """ Raise an TimeoutException.

    This is intended for use as a signal handler.
    The signum and frame arguments passed to this are ignored.

    """
    # Raise TimeoutException with system default timeout message
    raise TimeoutException()

# Set the handler for the SIGALRM signal:
signal.signal(signal.SIGALRM, _timeout)
# Send the SIGALRM signal in 10 seconds:
signal.alarm(10)

try:    
    # Do our code:
    print('This will take 11 seconds...')
    sleep(11)
    print('done!')
except TimeoutException:
    print('It timed out!')
finally:
    # Abort the sending of the SIGALRM signal:
    signal.alarm(0)

Istnieją pewne zastrzeżenia:

  1. Nie jest bezpieczny dla wątków, sygnały są zawsze dostarczane do głównego wątku, więc nie możesz umieścić tego w żadnym innym wątku.
  2. Po zaplanowaniu sygnału i wykonaniu właściwego kodu występuje niewielkie opóźnienie. Oznacza to, że przykład wyłączyłby się, nawet gdyby spał tylko przez dziesięć sekund.

Ale wszystko jest w standardowej bibliotece Pythona! Poza importem funkcji uśpienia jest to tylko jeden import. Jeśli zamierzasz używać limitów czasu w wielu miejscach, możesz łatwo umieścić wyjątek TimeoutException, _timeout i sygnalizację w funkcji i po prostu to wywołać. Lub możesz stworzyć dekorator i umieścić go na funkcjach, zobacz odpowiedź połączoną poniżej.

Możesz również ustawić go jako „menedżera kontekstu”, aby używać go z withinstrukcją:

import signal
class Timeout():
    """ Timeout for use with the `with` statement. """

    class TimeoutException(Exception):
        """ Simple Exception to be called on timeouts. """
        pass

    def _timeout(signum, frame):
        """ Raise an TimeoutException.

        This is intended for use as a signal handler.
        The signum and frame arguments passed to this are ignored.

        """
        raise Timeout.TimeoutException()

    def __init__(self, timeout=10):
        self.timeout = timeout
        signal.signal(signal.SIGALRM, Timeout._timeout)

    def __enter__(self):
        signal.alarm(self.timeout)

    def __exit__(self, exc_type, exc_value, traceback):
        signal.alarm(0)
        return exc_type is Timeout.TimeoutException

# Demonstration:
from time import sleep

print('This is going to take maximum 10 seconds...')
with Timeout(10):
    sleep(15)
    print('No timeout?')
print('Done')

Jedną możliwą wadą tego podejścia menedżera kontekstu jest to, że nie możesz wiedzieć, czy kod faktycznie przekroczył limit czasu, czy nie.

Źródła i zalecana lektura:

totokaka
źródło
3
Sygnały są dostarczane tylko w głównym wątku, więc na pewno nie będzie działać w innych wątkach, prawdopodobnie nie .
Dima Tisnek
1
Limitu czasu, dekorator pakiet zawiera dekorator limitu czasu, który wykorzystuje sygnały (lub ewentualnie wieloprocesorowe).
Christian Long,
13

Wypróbuj to żądanie z limitem czasu i obsługą błędów:

import requests
try: 
    url = "http://google.com"
    r = requests.get(url, timeout=10)
except requests.exceptions.Timeout as e: 
    print e
DaWe
źródło
5

Ustaw stream=Truei używaj r.iter_content(1024). Tak, eventlet.Timeoutjakoś mi się to nie udaje.

try:
    start = time()
    timeout = 5
    with get(config['source']['online'], stream=True, timeout=timeout) as r:
        r.raise_for_status()
        content = bytes()
        content_gen = r.iter_content(1024)
        while True:
            if time()-start > timeout:
                raise TimeoutError('Time out! ({} seconds)'.format(timeout))
            try:
                content += next(content_gen)
            except StopIteration:
                break
        data = content.decode().split('\n')
        if len(data) in [0, 1]:
            raise ValueError('Bad requests data')
except (exceptions.RequestException, ValueError, IndexError, KeyboardInterrupt,
        TimeoutError) as e:
    print(e)
    with open(config['source']['local']) as f:
        data = [line.strip() for line in f.readlines()]

Dyskusja jest tutaj https://redd.it/80kp1h

Polv
źródło
Szkoda, że ​​żądanie nie obsługuje parametrów maxtime, to rozwiązanie jako jedyne działało z asyncio
wukong.
4

Może to być przesada, ale kolejka zadań rozproszonych Celery ma dobrą obsługę limitów czasu.

W szczególności możesz zdefiniować miękki limit czasu, który po prostu wywołuje wyjątek w twoim procesie (abyś mógł wyczyścić) i / lub sztywny limit czasu, który kończy zadanie, gdy limit czasu zostanie przekroczony.

Pod okładkami stosuje się to samo podejście do sygnałów, jak w poście „przed”, ale w bardziej użyteczny i łatwiejszy w zarządzaniu sposób. A jeśli lista monitorowanych witryn internetowych jest długa, może przydać się jej główna funkcja - wszelkiego rodzaju sposoby zarządzania wykonywaniem dużej liczby zadań.

Chris Johnson
źródło
To mogłoby być dobre rozwiązanie. Problem całkowitego limitu czasu nie jest związany bezpośrednio z, python-requestsale z httplib(używany przez żądania dla Pythona 2.7). Pakiet przekazuje wszystko, co dotyczy timeoutbezpośrednio do httplib. Myślę, że nic nie może zostać naprawione w żądaniu, ponieważ proces może pozostać przez długi czas w httplib.
hynekcer
@hynekcer, myślę, że masz rację. Dlatego wykrywanie przekroczeń czasu poza procesem i wymuszanie poprzez czyste zabijanie procesów, tak jak robi to Celery, może być dobrym podejściem.
Chris Johnson,
3

Uważam, że możesz korzystać multiprocessingz pakietu innej firmy i nie polegać na nim:

import multiprocessing
import requests

def call_with_timeout(func, args, kwargs, timeout):
    manager = multiprocessing.Manager()
    return_dict = manager.dict()

    # define a wrapper of `return_dict` to store the result.
    def function(return_dict):
        return_dict['value'] = func(*args, **kwargs)

    p = multiprocessing.Process(target=function, args=(return_dict,))
    p.start()

    # Force a max. `timeout` or wait for the process to finish
    p.join(timeout)

    # If thread is still active, it didn't finish: raise TimeoutError
    if p.is_alive():
        p.terminate()
        p.join()
        raise TimeoutError
    else:
        return return_dict['value']

call_with_timeout(requests.get, args=(url,), kwargs={'timeout': 10}, timeout=60)

Przekazany do limitu czasu kwargsjest limit czasu, aby uzyskać żadnej odpowiedzi z serwera, argument timeoutjest limit czasu, aby uzyskać pełną odpowiedź.

Jorge Leitao
źródło
Można to poprawić za pomocą ogólnej try / z wyjątkiem funkcji prywatnej, która wyłapuje wszystkie błędy i umieszcza je w return_dict ['błąd']. Następnie na końcu, przed powrotem, sprawdź, czy 'error' w return_dict, a następnie podnieś go. Znacznie ułatwia to również testowanie.
dialt0ne
2

timeout = (limit czasu połączenia, limit czasu odczytu danych) lub podaj pojedynczy argument (limit czasu = 1)

import requests

try:
    req = requests.request('GET', 'https://www.google.com',timeout=(1,1))
    print(req)
except requests.ReadTimeout:
    print("READ TIME OUT")
Fayzan qureshi
źródło
1

ten kod działa dla socketError 11004 i 10060 ......

# -*- encoding:UTF-8 -*-
__author__ = 'ACE'
import requests
from PyQt4.QtCore import *
from PyQt4.QtGui import *


class TimeOutModel(QThread):
    Existed = pyqtSignal(bool)
    TimeOut = pyqtSignal()

    def __init__(self, fun, timeout=500, parent=None):
        """
        @param fun: function or lambda
        @param timeout: ms
        """
        super(TimeOutModel, self).__init__(parent)
        self.fun = fun

        self.timeer = QTimer(self)
        self.timeer.setInterval(timeout)
        self.timeer.timeout.connect(self.time_timeout)
        self.Existed.connect(self.timeer.stop)
        self.timeer.start()

        self.setTerminationEnabled(True)

    def time_timeout(self):
        self.timeer.stop()
        self.TimeOut.emit()
        self.quit()
        self.terminate()

    def run(self):
        self.fun()


bb = lambda: requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip")

a = QApplication([])

z = TimeOutModel(bb, 500)
print 'timeout'

a.exec_()
ACEE
źródło
Głosowanie za kreatywnością
JSmyth
1

Pomimo pytania o żądania, uważam, że jest to bardzo łatwe dzięki pycurl CURLOPT_TIMEOUT lub CURLOPT_TIMEOUT_MS.

Nie wymaga gwintowania ani sygnalizacji:

import pycurl
import StringIO

url = 'http://www.example.com/example.zip'
timeout_ms = 1000
raw = StringIO.StringIO()
c = pycurl.Curl()
c.setopt(pycurl.TIMEOUT_MS, timeout_ms)  # total timeout in milliseconds
c.setopt(pycurl.WRITEFUNCTION, raw.write)
c.setopt(pycurl.NOSIGNAL, 1)
c.setopt(pycurl.URL, url)
c.setopt(pycurl.HTTPGET, 1)
try:
    c.perform()
except pycurl.error:
    traceback.print_exc() # error generated on timeout
    pass # or just pass if you don't want to print the error
John Smith
źródło
1

Jeśli korzystasz z opcji, stream=Truemożesz to zrobić:

r = requests.get(
    'http://url_to_large_file',
    timeout=1,  # relevant only for underlying socket
    stream=True)

with open('/tmp/out_file.txt'), 'wb') as f:
    start_time = time.time()
    for chunk in r.iter_content(chunk_size=1024):
        if chunk:  # filter out keep-alive new chunks
            f.write(chunk)
        if time.time() - start_time > 8:
            raise Exception('Request took longer than 8s')

Rozwiązanie nie wymaga sygnałów ani przetwarzania wieloprocesowego.

ub_marco
źródło
1

Jeszcze jedno rozwiązanie (pobrane z http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads )

Przed przesłaniem możesz sprawdzić rozmiar zawartości:

TOO_LONG = 10*1024*1024  # 10 Mb
big_url = "http://ipv4.download.thinkbroadband.com/1GB.zip"
r = requests.get(big_url, stream=True)
print (r.headers['content-length'])
# 1073741824  

if int(r.headers['content-length']) < TOO_LONG:
    # upload content:
    content = r.content

Ale bądź ostrożny, nadawca może ustawić nieprawidłową wartość w polu odpowiedzi „content-length”.

Denis Kuzin
źródło
Dzięki. Czyste i proste rozwiązanie. Pracuje dla mnie.
petezurich
0

Jeśli do tego dojdzie, utwórz wątek watchdog, który po 10 sekundach zepsuje stan wewnętrzny żądań, np .:

  • zamyka podstawowe gniazdo, najlepiej
  • wyzwala wyjątek, jeśli żądanie ponawia operację

Zauważ, że w zależności od bibliotek systemowych możesz nie być w stanie ustawić ostatecznego terminu rozpoznawania nazw DNS.

Dima Tisnek
źródło
0

Cóż, wypróbowałem wiele rozwiązań na tej stronie i nadal miałem do czynienia z niestabilnością, przypadkowymi zawieszeniami, słabą wydajnością połączeń.

Teraz używam Curl i jestem bardzo zadowolony z jego funkcji „maksymalnego czasu” i globalnych wyników, nawet przy tak słabej implementacji:

content=commands.getoutput('curl -m6 -Ss "http://mywebsite.xyz"')

Tutaj zdefiniowałem maksymalny parametr czasu 6 sekund, obejmujący zarówno czas połączenia, jak i przesyłania.

Jestem pewien, że Curl ma fajne powiązanie Pythona, jeśli wolisz trzymać się składni Pythona :)

technika
źródło
0

Istnieje pakiet o nazwie timeout-decorator , którego można użyć do przekroczenia limitu czasu dowolnej funkcji Pythona.

@timeout_decorator.timeout(5)
def mytest():
    print("Start")
    for i in range(1,10):
        time.sleep(1)
        print("{} seconds have passed".format(i))

Wykorzystuje podejście sygnalizacyjne, które sugerują niektóre odpowiedzi. Alternatywnie możesz powiedzieć mu, aby używał przetwarzania wieloprocesowego zamiast sygnałów (np. Jeśli jesteś w środowisku wielowątkowym).

Christian Long
źródło
0

Używam żądań 2.2.1, a Eventlet nie działa dla mnie. Zamiast tego mogłem użyć gevent timeout, ponieważ gevent jest używany w mojej służbie dla gunicorn.

import gevent
import gevent.monkey
gevent.monkey.patch_all(subprocess=True)
try:
    with gevent.Timeout(5):
        ret = requests.get(url)
        print ret.status_code, ret.content
except gevent.timeout.Timeout as e:
    print "timeout: {}".format(e.message)

Należy pamiętać, że gevent.timeout.Timeout nie podlega ogólnej obsłudze wyjątków. Więc albo jawnie przechwyć gevent.timeout.Timeout lub przekaż inny wyjątek do użycia w ten sposób: with gevent.Timeout(5, requests.exceptions.Timeout):chociaż żaden komunikat nie jest przekazywany, gdy ten wyjątek jest zgłaszany.

xsdf
źródło
-1

Wymyśliłem bardziej bezpośrednie rozwiązanie, które jest wprawdzie brzydkie, ale rozwiązuje prawdziwy problem. Trochę to wygląda tak:

resp = requests.get(some_url, stream=True)
resp.raw._fp.fp._sock.settimeout(read_timeout)
# This will load the entire response even though stream is set
content = resp.content

Możesz przeczytać pełne wyjaśnienie tutaj

Realistyczny
źródło