Python istekleri için zaman aşımı. Tüm yanıtı al


169

Bir web sitesi listesinde istatistik topluyorum ve basitlik için talepleri kullanıyorum. İşte benim 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())) )

Şimdi, requests.get10 saniye sonra zaman aşımı yapmak istiyorum , böylece döngü takılmıyor.

Bu soru daha önce de ilgilendi, ancak cevapların hiçbiri temiz değil. Güzel bir cevap almak için bunu biraz ödüllendireceğim.

Belki istekleri kullanmamanın iyi bir fikir olduğunu duyuyorum ama o zaman isteklerin sunduğu güzel şeyleri nasıl almalıyım. (gruptaki olanlar)


1
Ne tür bir cevap arıyorsunuz? (ya da başka bir deyişle, mevcut cevaplar neden sizin için yeterli değil?)
yuvi

Ödülün lütuf dönemindeyiz. Bir cevap seçme zamanı?
totokaka

Hala olay çözümü ve sinyaller arasında karar veriyorum. Sorunu bu gece vereceğim.
Kiarash


Yanıtlar:


137

Etkinlik kullanmaya ne dersiniz? Veri alınsa bile isteği 10 saniye sonra zaman aşımına uğratmak istiyorsanız, bu snippet sizin için çalışır:

import requests
import eventlet
eventlet.monkey_patch()

with eventlet.Timeout(10):
    requests.get("http://ipv4.download.thinkbroadband.com/1GB.zip", verify=False)

115
Elbette bu gereksiz bir şekilde karmaşık.
holdenweb

7
Teşekkür ederim. Şimdi çözümünüzün teknik üstünlüğünü anlıyorum (ki yanıtınızın başında oldukça özlü bir şekilde belirtmiştiniz) ve onayladım. Üçüncü taraf modüllerle ilgili sorun, onları içe aktarmamakta, ancak bunların içe aktarılacaklarından emin olmaktır, bu nedenle mümkün olduğunda standart kitaplığı kullanma tercihim.
holdenweb

9
Is eventlet.monkey_patch()gerekli?
Kullanıcı

3
Evet, socketmodül maymun yamalı olmalı, bu yüzden en azından ihtiyacınız olacakeventlet.monkey_patch(socket=True)
Alvaro

53
2018 itibariyle bu cevap modası geçmiş. Kullanımrequests.get('https://github.com', timeout=5)
CONvid19

313

Zaman aşımı parametresini ayarlayın :

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

Bu stream=Trueisteği belirlemediğiniz sürece requests.get(), bağlantı on saniyeden uzun sürerse veya sunucu on saniyeden fazla veri göndermezse çağrının zaman aşımına uğramasına neden olur .



1
Evet, bazı durumlarda. Bu koşullardan biri sizin. =) Sizi ikna olmadıkça koda bakmaya davet ediyorum.
Lukasa

koşullar nelerdir?
Kiarash

1
Bunu kontrol ettim ve hiç durmadı: r = requests.get (' ipv4.download.thinkbroadband.com/1GB.zip ', zaman aşımı = 20)
Kiarash

5
Ah, özür dilerim, 'tüm yanıt' dediğinde ne demek istediğini yanlış anladım. Evet, haklısın: toplam bekleme süresinin üst sınırı değil.
Lukasa

85

GÜNCELLEME: https://requests.readthedocs.io/en/master/user/advanced/#timeouts

Yeni sürümünde requests:

Zaman aşımı için tek bir değer belirtirseniz, şöyle:

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

Zaman aşımı değeri hem zaman aşımlarına hem connectde readzaman aşımlarına uygulanacaktır. Değerleri ayrı ayrı ayarlamak istiyorsanız bir demet belirtin:

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

Uzak sunucu çok yavaşsa, Hiçbir zaman aşımı değeri olarak Hiçbiri'ni geçip bir fincan kahve alarak İsteklere sonsuza kadar yanıt vermesini söyleyebilirsiniz.

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

Eski (muhtemelen eski) cevabım (uzun zaman önce gönderildi):

Bu sorunun üstesinden gelmenin başka yolları da vardır:

1. TimeoutSauceDahili sınıfı kullanın

Gönderen: 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

Bu kod, okuma zaman aşımını Session.get () çağrınızda geçirdiğiniz zaman aşımı değeri olan bağlantı zaman aşımına eşit olarak ayarlamamıza neden olmalıdır. (Bu kodu gerçekten test etmedim, bu yüzden bazı hızlı hata ayıklama gerektirebilir, sadece doğrudan GitHub penceresine yazdım.)

2. Kevinburke'un istek çatalını kullanın: https://github.com/kevinburke/requests/tree/connect-timeout

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

Zaman aşımı için tek bir değer belirtirseniz, şöyle:

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

Zaman aşımı değeri hem bağlantıya hem de okuma zaman aşımlarına uygulanır. Değerleri ayrı ayrı ayarlamak istiyorsanız bir demet belirtin:

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

kevinburke ana istekler projesinde birleştirilmesini istedi , ancak henüz kabul edilmedi.


seçenek 1 çalışmıyor. Eğer bu konuyu okumaya devam ederseniz, diğer insanlar "bu sizin kullanım durumunuz için işe yaramaz, korkarım. Okuma zaman aşımı işlevi bireysel bir soket recv () çağrısı kapsamındadır, böylece sunucu iptal edeceğimiz okuma zaman aşımından daha uzun süre veri göndermeyi durdurur. "
Kiarash

Bu iş parçacığı benim için işe yaramaz Signal kullanarak, başka bir güzel çözüm var, çünkü Windows ve signal.alarm sadece linux olduğunu.
Kiarash

@Kiarash Henüz test etmedim. Ancak anladığım gibi Lukasa ne zaman this won't work for you use-case. Diğer adam tarafından aranan mp3 akışı ile çalışmadığı anlamına geliyordu.
Hieu

1
@Hieu - bu başka bir istekte birleştirildi - github.com/kennethreitz/requests/pull/…
yprez

timeout = Hiçbiri çağrıyı engellemiyor.
crazydan

49

timeout = int(seconds)

Çünkü requests >= 2.4.0, timeoutargümanı kullanabilirsiniz , yani:

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

Not:

timeoutyanıt indirme işleminin tamamında bir zaman sınırı değildir; bunun yerine, exceptionsunucu zaman aşımı saniye boyunca bir yanıt vermediyse (daha kesin olarak, temel sokete zaman aşımı saniye boyunca bayt alınmamışsa) yükseltilir. Hiçbir zaman aşımı açıkça belirtilmezse, istekler zaman aşımına uğramaz.


Yeni zaman aşımı parametresinin hangi istek sürümü var?
Paslı

1
2.4.0 sürümünden beri görünüyor: Bağlantı zaman aşımı desteği! Zaman aşımı artık bireysel bağlantı ve okuma zaman aşımlarını ayarlamak için kullanılan bir demet (bağlantı, okuma) kabul eder . pypi.org/project/requests/2.4.0
CONvid19

23

Zaman aşımı oluşturmak için sinyalleri kullanabilirsiniz .

Bu davayı çözmenin en iyi yolu muhtemelen

  1. Alarm sinyalinin işleyicisi olarak bir istisna ayarlayın
  2. Alarm sinyalini on saniyelik bir gecikmeyle arayın
  3. Bir try-except-finallybloğun içindeki işlevi çağırın .
  4. İşlev zaman aşımına uğradığında, dıştaki bloğa ulaşılır.
  5. Nihayet blokta alarmı iptal edersiniz, bu yüzden daha sonra tek tek olmaz.

İşte bazı örnek 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)

Bunun için bazı uyarılar var:

  1. İplik güvenli değildir, sinyaller her zaman ana iş parçacığına iletilir, bu yüzden bunu başka bir iş parçacığına koyamazsınız.
  2. Sinyalin programlanmasından ve gerçek kodun yürütülmesinden sonra hafif bir gecikme olur. Bu, örneğin yalnızca on saniye uyuduğunda bile zaman aşımına uğrayacağı anlamına gelir.

Ama hepsi standart python kütüphanesinde! Uyku işlevi içe aktarma dışında yalnızca bir içe aktarmadır. Zaman aşımlarını birçok yerde kullanacaksanız TimeoutException, _timeout ve singaling işlevlerini kolayca bir işleve koyabilir ve sadece çağırabilirsiniz. Veya bir dekoratör yapabilir ve işlevlere koyabilirsiniz, aşağıdaki bağlantıya bakın.

Bunu bir "içerik yöneticisi" olarak da ayarlayabilirsiniz, böylece şu withifadeyle kullanabilirsiniz:

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

Bu bağlam yöneticisi yaklaşımı ile olası bir aşağı tarafı, kodun gerçekten zaman aşımına uğramış olup olmadığını bilemezsiniz.

Kaynaklar ve önerilen okumalar:


3
Sinyaller sadece böylece, ana iş parçacığı teslim edilir Defnitely değil, diğer iş parçacığı çalışmaz muhtemelen .
Dima Tisnek

1
Zaman aşımı-dekoratör paketi, sinyaller (veya isteğe bağlı olarak çok işlemli) kullanan bir zaman aşımı dekoratörü sağlar.
Christian Long

13

Bu isteği zaman aşımı ve hata işleme ile deneyin:

import requests
try: 
    url = "http://google.com"
    r = requests.get(url, timeout=10)
except requests.exceptions.Timeout as e: 
    print e

5

Ayarlayın stream=Trueve kullanın r.iter_content(1024). Evet, bir eventlet.Timeoutşekilde benim için çalışmıyor.

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()]

Tartışma burada https://redd.it/80kp1h


utanç verici bir istek maksimum zaman paramlerini desteklemiyor, bu çözüm asyncio ile çalışan tek çözüm
wukong

4

Bu aşırıya kaçabilir, ancak Kereviz dağıtılmış görev kuyruğunun zaman aşımları için iyi bir desteği vardır.

Özellikle, işleminizde bir istisna oluşturan (böylece temizleyebilirsiniz) yumuşak bir zaman sınırı ve / veya zaman sınırı aşıldığında görevi sonlandıran zor bir zaman sınırı tanımlayabilirsiniz.

Kapakların altında bu, "önceki" yayınınızda belirtilenle aynı sinyaller yaklaşımını kullanır, ancak daha kullanışlı ve yönetilebilir bir şekilde. Ve izlediğiniz web sitelerinin listesi uzunsa, birincil özelliğinden yararlanabilirsiniz - çok sayıda görevin yürütülmesini yönetmenin her türlü yolu.


Bu iyi bir çözüm olabilir. Toplam zaman aşımı sorunu doğrudan python-requestsdeğil, httplib(Python 2.7 istekleri tarafından kullanılır) ile ilgilidir. Paket, ilgili her şeyi timeoutdoğrudan httplib'e iletir. Bence süreç httplib'de uzun süre kalabilir çünkü hiçbir şey talep düzeltilemez.
hynekcer

@hynekcer, haklı olduğunu düşünüyorum. Kereviz gibi, zaman aşımlarının işlem dışı tespit edilmesi ve temiz öldürme işlemleriyle uygulanması iyi bir yaklaşım olabilir.
Chris Johnson

3

Kullanabileceğinize multiprocessingve 3. taraf bir pakete bağlı olmadığınıza inanıyorum :

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)

Geçirilen kwargszaman aşımı , sunucudan herhangi bir yanıt almak için zaman aşımı , bağımsız değişken timeoutise tam yanıt almak için zaman aşımıdır.


Bu, tüm hataları yakalayan ve return_dict ['error'] içine koyan özel işlev dışında genel bir try / ile geliştirilebilir. Daha sonra, geri dönmeden önce, return_dict içinde 'hata' olup olmadığını kontrol edin ve kaldırın. Test etmeyi çok daha kolay hale getirir.
dialt0ne

2

timeout = (bağlantı zaman aşımı, veri okuma zaman aşımı) veya tek bir bağımsız değişken verin (timeout = 1)

import requests

try:
    req = requests.request('GET', 'https://www.google.com',timeout=(1,1))
    print(req)
except requests.ReadTimeout:
    print("READ TIME OUT")

1

bu kod socketError 11004 ve 10060 için çalışıyor ......

# -*- 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_()

Yaratıcılık için upvoting
JSmyth

1

Soruların isteklerle ilgili olmasına rağmen, bunu pycurl ile yapmayı çok kolay buluyorum CURLOPT_TIMEOUT veya CURLOPT_TIMEOUT_MS .

Diş çekme veya sinyalizasyon gerekmez:

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

1

Seçeneği kullanıyorsanız stream=Truebunu yapabilirsiniz:

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

Çözüm sinyallere veya çoklu işlemlere ihtiyaç duymaz.


1

Sadece bir başka çözüm ( http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads adresinden aldınız )

Yüklemeden önce içerik boyutunu öğrenebilirsiniz:

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

Ancak dikkatli olun, bir gönderen 'içerik uzunluğu' yanıt alanında yanlış değer ayarlayabilir.


Teşekkürler. Temiz ve basit bir çözüm. Benim için çalışıyor.
petezurich

0

Buna gelince, 10 saniye sonra isteklerin dahili durumunu karıştıran bir bekçi köpeği iş parçacığı oluşturun , örneğin:

  • alttaki soketi kapatır ve ideal olarak
  • istekler işlemi yeniden denerse bir istisna tetikler

Sistem kitaplıklarına bağlı olarak DNS çözümlemesi için son tarih ayarlayamayabileceğinizi unutmayın.


0

Bu sayfada birçok çözüm denedim ve hala kararsızlıklar, rastgele kilitlenmeler, zayıf bağlantı performansı ile karşı karşıya kaldım.

Şimdi Curl kullanıyorum ve bu kadar zayıf bir uygulamada bile "maksimum zaman" işlevselliği ve küresel performanslar hakkında gerçekten mutluyum:

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

Burada, hem bağlantı hem de aktarım süresini kapsayan 6 saniyelik bir maksimum zaman parametresi tanımladım.

Pythonic sözdizimine bağlı kalmayı tercih ederseniz, Curl'un güzel bir python bağlaması olduğundan eminim :)


0

Herhangi bir python işlevini zaman aşımı için kullanabileceğiniz timeout-decorator adlı bir paket var .

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

Burada bazı cevapların önerdiği sinyaller yaklaşımını kullanır. Alternatif olarak, sinyaller yerine çok işlemcili kullanmasını söyleyebilirsiniz (örneğin, çok iş parçacıklı bir ortamdaysanız).


0

2.2.1 isteklerini kullanıyorum ve etkinlik benim için çalışmadı. Bunun yerine gevent gunicorn için hizmetimde gevent kullanıldığı için gevent zaman aşımı kullanabildim.

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)

Lütfen gevent.timeout.Timeout öğesinin genel Özel Durum işleme tarafından yakalanmadığını unutmayın. Öyleyse, açık bir şekilde yakalamak gevent.timeout.Timeout veya farklı bir istisnayı böyle kullanmak için geçirin: with gevent.Timeout(5, requests.exceptions.Timeout):bu istisna ortaya çıktığında hiçbir mesaj iletilmez.


-1

Kuşkusuz çirkin ama gerçek sorunu çözen daha doğrudan bir çözüm buldum. Biraz böyle gider:

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

Sen tam bir açıklama okuyabilir burada


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.