Batas waktu untuk permintaan python. Dapatkan seluruh respons


169

Saya mengumpulkan statistik pada daftar situs web dan saya menggunakan permintaan untuk kesederhanaan. Ini kode saya:

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

Sekarang, saya ingin requests.gettimeout setelah 10 detik sehingga loop tidak macet.

Pertanyaan ini telah menarik sebelumnya juga tetapi tidak ada jawaban yang bersih. Saya akan memberikan beberapa hadiah untuk mendapatkan jawaban yang bagus.

Saya mendengar bahwa mungkin tidak menggunakan permintaan adalah ide yang baik tetapi kemudian bagaimana saya harus mendapatkan penawaran hal-hal baik yang ditawarkan. (yang ada di tuple)


1
Apa jawaban yang Anda cari? (atau, dengan kata lain, mengapa jawaban saat ini tidak cukup untuk Anda?)
yuvi

Kita berada dalam masa tenggang karunia. Saatnya memilih jawaban?
totokaka

Saya masih memutuskan antara solusi eventlet dan sinyal. Saya akan memberikan pertanyaan malam ini.
Kiarash


Jawaban:


138

Bagaimana dengan menggunakan eventlet? Jika Anda ingin menghentikan permintaan setelah 10 detik, meskipun data sedang diterima, cuplikan ini akan berfungsi untuk Anda:

import requests
import eventlet
eventlet.monkey_patch()

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

114
Tentunya ini tidak perlu rumit.
holdenweb

7
Terima kasih. Saya sekarang mengerti keunggulan teknis solusi Anda (yang Anda nyatakan dengan singkat pada awal jawaban Anda) dan meningkatkannya. Masalah dengan modul pihak ketiga bukan mengimpor mereka tetapi memastikan mereka ada di sana untuk diimpor, oleh karena itu preferensi saya sendiri untuk menggunakan perpustakaan standar jika memungkinkan.
holdenweb

9
Apakah eventlet.monkey_patch()wajib?
Pengguna

3
Ya, socketmodul perlu ditambal monyet, jadi setidaknya Anda akan membutuhkaneventlet.monkey_patch(socket=True)
Alvaro

51
Pada 2018 jawaban ini sudah usang. Gunakanrequests.get('https://github.com', timeout=5)
CONvid19

312

Setel parameter batas waktu :

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

Selama Anda tidak menetapkan stream=Truepermintaan itu, ini akan menyebabkan panggilan ke requests.get()timeout jika koneksi membutuhkan waktu lebih dari sepuluh detik, atau jika server tidak mengirim data selama lebih dari sepuluh detik.



1
Ya, dalam beberapa keadaan. Salah satu dari keadaan itu adalah milik Anda. =) Saya mengundang Anda untuk melihat kode jika Anda tidak yakin.
Lukasa

apa situasinya?
Kiarash

1
Saya baru saja memeriksa ini dan tidak pernah berhenti: r = requests.get (' ipv4.download.thinkbroadband.com/1GB.zip ', timeout = 20)
Kiarash

5
Ah, maaf, saya salah mengerti apa yang Anda maksud ketika Anda mengatakan 'seluruh respons'. Ya, Anda benar: ini bukan batas atas jumlah total waktu untuk menunggu.
Lukasa

85

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

Dalam versi baru requests:

Jika Anda menentukan nilai tunggal untuk batas waktu, seperti ini:

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

Nilai batas waktu akan diterapkan untuk batas waktu connectdan readbatas waktu. Tentukan tuple jika Anda ingin mengatur nilai secara terpisah:

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

Jika server jarak jauh sangat lambat, Anda dapat memberi tahu Permintaan untuk menunggu selamanya untuk tanggapan, dengan melewatkan None sebagai nilai batas waktu dan kemudian mengambil secangkir kopi.

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

Jawaban lama saya (mungkin kedaluwarsa) (yang sudah diposting sebelumnya):

Ada cara lain untuk mengatasi masalah ini:

1. Gunakan TimeoutSaucekelas internal

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

Kode ini harus menyebabkan kami menetapkan batas waktu baca sama dengan batas waktu koneksi, yang merupakan nilai batas waktu yang Anda berikan pada panggilan Session.get () Anda. (Perhatikan bahwa saya belum benar-benar menguji kode ini, jadi mungkin perlu debugging cepat, saya hanya menulisnya langsung ke jendela GitHub.)

2. Gunakan garpu permintaan dari kevinburke: https://github.com/kevinburke/requests/tree/connect-timeout

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

Jika Anda menentukan nilai tunggal untuk batas waktu, seperti ini:

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

Nilai batas waktu akan diterapkan untuk sambungan dan batas waktu baca. Tentukan tuple jika Anda ingin mengatur nilai secara terpisah:

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

kevinburke telah memintanya untuk bergabung ke dalam proyek permintaan utama, tetapi belum diterima.


opsi 1 tidak berfungsi. jika Anda terus membaca utas itu, orang lain mengatakan "ini tidak akan berfungsi untuk kasus penggunaan Anda, saya khawatir. Fungsi batas waktu baca adalah pada ruang lingkup panggilan soket individual recv (), sehingga jika server berhenti mengirim data lebih dari batas waktu baca yang akan kami batalkan. "
Kiarash

Ada solusi bagus lain di utas itu menggunakan Signal, yang tidak akan bekerja untuk saya juga, karena saya menggunakan Windows dan signal.alarm hanya linux.
Kiarash

@ Cirash saya belum mengujinya. Namun, seperti yang saya mengerti ketika kata Lukasa this won't work for you use-case. Maksudnya itu tidak bekerja dengan aliran mp3 yang diinginkan oleh orang lain.
Hieu

1
@Hieu - ini digabung dalam permintaan tarik lainnya - github.com/kennethreitz/requests/pull/…
yprez

timeout = Tidak ada yang tidak memblokir panggilan.
crazydan

49

timeout = int(seconds)

Karena requests >= 2.4.0, Anda dapat menggunakan timeoutargumen, yaitu:

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

catatan:

timeoutbukan batas waktu untuk keseluruhan unduhan respons; melainkan, exceptiondinaikkan jika server belum mengeluarkan respons untuk batas waktu detik (lebih tepatnya, jika tidak ada byte yang diterima pada soket dasar untuk batas waktu detik). Jika tidak ada batas waktu yang ditentukan secara eksplisit, permintaan tidak akan habis.


Versi permintaan apa yang memiliki parameter batas waktu baru?
Rusty

1
Tampaknya sejak versi 2.4.0: Dukungan untuk waktu tunggu koneksi! Timeout sekarang menerima tuple (terhubung, baca) yang digunakan untuk mengatur masing-masing terhubung dan membaca timeout . pypi.org/project/requests/2.4.0
CONvid19

23

Untuk membuat batas waktu Anda dapat menggunakan sinyal .

Cara terbaik untuk menyelesaikan kasus ini mungkin

  1. Tetapkan pengecualian sebagai pawang untuk sinyal alarm
  2. Panggil sinyal alarm dengan penundaan sepuluh detik
  3. Panggil fungsi di dalam try-except-finallyblok.
  4. Blok kecuali tercapai jika fungsi waktu habis.
  5. Di blok terakhir Anda membatalkan alarm, sehingga tidak dinyalakan kemudian.

Berikut ini beberapa contoh kode:

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)

Ada beberapa peringatan untuk ini:

  1. Itu bukan threadsafe, sinyal selalu dikirim ke utas utama, jadi Anda tidak dapat menempatkan ini di utas lainnya.
  2. Ada sedikit keterlambatan setelah penjadwalan sinyal dan pelaksanaan kode aktual. Ini berarti bahwa contoh akan habis meskipun hanya tidur selama sepuluh detik.

Tapi, itu semua ada di pustaka python standar! Kecuali untuk impor fungsi tidur, itu hanya satu impor. Jika Anda akan menggunakan waktu habis banyak tempat Anda dapat dengan mudah menempatkan TimeoutException, _timeout dan bernyanyi dalam suatu fungsi dan panggil saja. Atau Anda dapat membuat dekorator dan menjalankan fungsinya, lihat jawabannya di bawah ini.

Anda juga dapat mengatur ini sebagai "manajer konteks" sehingga Anda dapat menggunakannya dengan withpernyataan:

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

Satu kemungkinan kelemahan dari pendekatan manajer konteks ini adalah bahwa Anda tidak dapat mengetahui apakah kode tersebut benar-benar kehabisan waktu atau tidak.

Sumber dan bacaan yang direkomendasikan:


3
Sinyal hanya disampaikan dalam thread utama, sehingga defnitely tidak akan bekerja di thread lain, tidak mungkin .
Dima Tisnek

1
Paket dekor-dekorator menyediakan dekorator batas waktu yang menggunakan sinyal (atau opsional multiprosesor).
Christian Long

13

Coba permintaan ini dengan penanganan batas waktu & kesalahan:

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

5

Atur stream=Truedan gunakan r.iter_content(1024). Ya, eventlet.Timeoutentah bagaimana tidak berhasil untuk saya.

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

Diskusi ada di sini https://redd.it/80kp1h


ini permintaan yang memalukan tidak mendukung params maxtime, solusi ini adalah satu-satunya yang bekerja dengan asyncio
wukong

4

Ini mungkin berlebihan, tetapi antrian tugas yang didistribusikan Selery memiliki dukungan yang baik untuk batas waktu.

Secara khusus, Anda dapat menentukan batas waktu lunak yang hanya meningkatkan pengecualian dalam proses Anda (sehingga Anda dapat membersihkan) dan / atau batas waktu sulit yang menghentikan tugas ketika batas waktu telah terlampaui.

Di bawah penutup, ini menggunakan pendekatan sinyal yang sama seperti yang dirujuk di pos "sebelum" Anda, tetapi dengan cara yang lebih dapat digunakan dan dikelola. Dan jika daftar situs web yang Anda pantau panjang, Anda mungkin mendapat manfaat dari fitur utamanya - semua jenis cara untuk mengelola pelaksanaan sejumlah besar tugas.


Ini bisa menjadi solusi yang bagus. Masalah total timeout tidak terkait langsung dengan python-requeststetapi dengan httplib(digunakan oleh permintaan untuk Python 2.7). Paket melewati semua yang terkait timeoutlangsung ke httplib. Saya pikir tidak ada yang bisa diperbaiki dalam permintaan karena prosesnya dapat bertahan lama di httplib.
hynekcer

@hynekcer, saya pikir Anda benar. Inilah sebabnya mengapa mendeteksi timeout di luar proses dan menegakkannya dengan membunuh proses secara bersih, seperti yang dilakukan Celery, bisa menjadi pendekatan yang baik.
Chris Johnson

3

Saya percaya Anda dapat menggunakan multiprocessingdan tidak bergantung pada paket pihak ke-3:

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)

Timeout diteruskan ke kwargsadalah batas waktu untuk mendapatkan setiap respon dari server, argumen timeoutadalah batas waktu untuk mendapatkan lengkap respon.


Ini dapat ditingkatkan dengan coba generik / kecuali dalam fungsi pribadi yang menangkap semua kesalahan dan menempatkannya dalam return_dict ['error']. Kemudian di akhir, sebelum kembali, periksa apakah 'kesalahan' di return_dict dan kemudian naikkan. Itu membuatnya lebih mudah untuk diuji juga.
dialt0ne

2

batas waktu = (batas waktu koneksi, batas waktu baca data) atau memberikan argumen tunggal (batas waktu = 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

kode ini berfungsi untuk socketError 11004 dan 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_()

Upvoting untuk kreativitas
JSmyth

1

Meskipun ada pertanyaan tentang permintaan, saya menemukan ini sangat mudah dilakukan dengan pycurl CURLOPT_TIMEOUT atau CURLOPT_TIMEOUT_MS.

Tidak perlu threading atau pensinyalan:

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

Jika Anda menggunakan opsi, stream=TrueAnda bisa melakukan ini:

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

Solusinya tidak perlu sinyal atau multi-pemrosesan.


1

Hanya satu solusi lain (dapatkan dari http://docs.python-requests.org/en/master/user/advanced/#streaming-uploads )

Sebelum mengunggah, Anda dapat mengetahui ukuran konten:

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

Namun hati-hati, pengirim dapat menetapkan nilai yang salah di bidang respons 'panjang konten'.


Terima kasih. Solusi bersih dan sederhana. Bekerja untukku.
petezurich

0

Jika itu yang terjadi, buat utas pengawas yang mengacaukan keadaan internal permintaan setelah 10 detik, misalnya:

  • menutup soket yang mendasarinya, dan idealnya
  • memicu pengecualian jika permintaan mencoba kembali operasi

Perhatikan bahwa tergantung pada pustaka sistem Anda mungkin tidak dapat menetapkan batas waktu pada resolusi DNS.


0

Yah, saya mencoba banyak solusi pada halaman ini dan masih menghadapi ketidakstabilan, hang acak, kinerja koneksi yang buruk.

Saya sekarang menggunakan Curl dan saya sangat senang dengan fungsionalitas "waktu max" dan tentang kinerja global, bahkan dengan implementasi yang buruk:

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

Di sini, saya mendefinisikan parameter waktu maks 6 detik, melibatkan koneksi dan waktu transfer.

Saya yakin Curl memiliki ikatan python yang bagus, jika Anda lebih suka tetap menggunakan sintaks pythonic :)


0

Ada paket yang disebut timeout-decorator yang dapat Anda gunakan untuk mematikan fungsi python.

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

Ia menggunakan pendekatan sinyal yang disarankan beberapa jawaban di sini. Sebagai alternatif, Anda dapat memerintahkannya untuk menggunakan multi-pemrosesan alih-alih sinyal (misalnya jika Anda berada di lingkungan multi-utas).


0

Saya menggunakan permintaan 2.2.1 dan eventlet tidak berhasil untuk saya. Alih-alih, saya bisa menggunakan timeout gevent karena gevent digunakan dalam layanan saya untuk 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)

Harap dicatat bahwa gevent.timeout.Timeout tidak ditangkap oleh penanganan Pengecualian umum. Jadi baik secara eksplisit menangkap gevent.timeout.Timeout atau meneruskan dalam pengecualian berbeda untuk digunakan seperti: with gevent.Timeout(5, requests.exceptions.Timeout):meskipun tidak ada pesan yang dilewatkan ketika pengecualian ini dimunculkan.


-1

Saya datang dengan solusi yang lebih langsung yang diakui jelek tetapi memperbaiki masalah sebenarnya. Bunyinya agak seperti ini:

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

Anda dapat membaca penjelasan lengkapnya di sini


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.