python requests.get応答全体のタイムアウト


169

私はウェブサイトのリストに関する統計を収集しており、単純化するためにそれに対するリクエストを使用しています。これが私のコードです:

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

ここでrequests.get、ループがスタックしないように、10秒後にタイムアウトしたいと思います。

この質問は以前にも関心がありましたが、明確な答えはありません。良い答えを得るためにこれにいくつかの賞金をかけます。

リクエストを使わないのは良い考えだと聞きましたが、リクエストが提供する素晴らしいものをどうやって手に入れるべきでしょうか。(タプル内のもの)


1
どのような答えを探していますか?(または、言い換えれば、なぜ現在の答えでは不十分なのですか?)
yuvi

賞金の猶予期間中です。答えを選ぶ時ですか?
トトカカ2014年

私はまだeventletソリューションとシグナルの間で決定しています。今夜までに質問をします。
Kiarash 2014年


回答:


138

イベントレットの使用についてはどうですか?10秒後にリクエストをタイムアウトしたい場合は、データが受信されている場合でも、次のスニペットが機能します。

import requests
import eventlet
eventlet.monkey_patch()

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

114
確かにこれは不必要に複雑です。
holdenweb

7
ありがとうございました。私は今あなたのソリューションの技術的優位性(あなたがあなたの答えの初めにかなり簡潔に述べた)を理解し、それを支持しました。サードパーティのモジュールの問題は、それらをインポートするのではなく、インポートすることを保証することです。そのため、可能な限り、標準ライブラリを使用することを自分で好みます。
holdenweb 2014年

9
されるeventlet.monkey_patch()必要?
ユーザー

3
はい、socketモジュールにサルのパッチを適用する必要があるため、少なくとも次のものが必要ですeventlet.monkey_patch(socket=True)
Alvaro

51
2018年現在、この回答は古くなっています。使用requests.get('https://github.com', timeout=5)
CONvid19

312

タイムアウトパラメータを設定します

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

そのstream=Trueリクエストを設定しない限りrequests.get()、接続に10秒以上かかる場合、またはサーバーがデータを10秒以上送信しない場合、呼び出しはタイムアウトになります。


31
それは応答全体ではありません。requests.readthedocs.org/en/latest/user/quickstart/#timeouts
Kiarash

1
はい、状況によってはそうです。それらの状況の1つはたまたまあなたのものです。=)確信がない場合は、コードを確認することをお勧めします。
ルカサ2014

どのような状況ですか?
Kiarash 14

1
私はこれをチェックしたところ止まりませんでした:r = requests.get( ' ipv4.download.thinkbroadband.com/1GB.zip'、timeout = 20)
Kiarash

5
ああ、申し訳ありませんが、「回答全体」と言ったときの意味を誤解しました。はい、そうです。これは、待機する合計時間の上限ではありません。
ルカサ2014

85

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

の新しいバージョンrequests

次のように、タイムアウトに単一の値を指定した場合:

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

タイムアウト値はconnectreadタイムアウトとタイムアウトの両方に適用されます。値を個別に設定する場合は、タプルを指定します。

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

リモートサーバーの速度が非常に遅い場合は、タイムアウト値としてNoneを渡し、コーヒーを1杯取得することで、リクエストに永久に応答を待つように指示できます。

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

私の古い(おそらく時代遅れの)回答(これはかなり前に投稿されました):

この問題を解決する方法は他にもあります。

1. TimeoutSauce内部クラスを使用する

送信元: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

このコードにより、読み取りタイムアウトが接続タイムアウトと同じに設定されます。これは、Session.get()呼び出しで渡すタイムアウト値です。(私は実際にこのコードをテストしていないため、簡単なデバッグが必要な場合があるので、GitHubウィンドウに直接書き込んだだけです。)

2. kevinburkeからのリクエストのフォークを使用します: https : //github.com/kevinburke/requests/tree/connect-timeout

そのドキュメントから:https : //github.com/kevinburke/requests/blob/connect-timeout/docs/user/advanced.rst

次のように、タイムアウトに単一の値を指定した場合:

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

タイムアウト値は、接続タイムアウトと読み取りタイムアウトの両方に適用されます。値を個別に設定する場合は、タプルを指定します。

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

kevinburkeはメインのリクエストプロジェクトにマージするように要求しましたが、まだ受け入れられていません。


オプション1は機能しません。あなたがそのスレッドを読み続けると、他の人々は「これはあなたのユースケースでは機能しません、私は恐れています。読み取りタイムアウト関数は個々のソケットのrecv()呼び出しのスコープにあるので、サーバーが読み取りタイムアウトを超えるとデータの送信を停止し、中止します。」
Kiarash 14年

Signalを使用するスレッドには、Windowsを使用していて、signal.alarmがLinuxのみであるため、私にとっても機能しない別の優れたソリューションがあります。
Kiarash 14年

@Kiarash私はまだテストしていません。しかし、ルカサが言っthis won't work for you use-caseたときに私が理解するように。彼はそれが他の男が望んでいたmp3ストリームでは動作しないことを意味しました。
Hieu

1
@Hieuは-これは別のプル要求にマージされました- github.com/kennethreitz/requests/pull/...
yprez

timeout = Noneは呼び出しをブロックしていません。
crazydan

49

timeout = int(seconds)

なので requests >= 2.4.0、次のtimeout引数を使用できます。

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

注意:

timeout応答のダウンロード全体の時間制限ではありません。むしろ、exceptionサーバーがタイムアウト秒数の間応答を発行しなかった場合(より正確には、基礎となるソケットでタイムアウト秒数の間バイトが受信されなかった場合)が発生します。タイムアウトが明示的に指定されていない場合、リクエストはタイムアウトしません。


新しいタイムアウトパラメータを持つリクエストのバージョンは何ですか?
Rusty

1
バージョン2.4.0以降のようです:接続タイムアウトのサポート!Timeoutは、個々の接続と読み取りのタイムアウトを設定するために使用されるタプル(接続、読み取り)を受け入れるようになりましたpypi.org/project/requests/2.4.0
CONvid19

23

タイムアウトを作成するには、シグナルを使用できます。

このケースを解決する最良の方法は、おそらく

  1. アラーム信号のハンドラーとして例外を設定する
  2. アラーム信号を10秒の遅延で呼び出す
  3. try-except-finallyブロック内で関数を呼び出します。
  4. 関数がタイムアウトした場合、exceptブロックに到達します。
  5. 最後のブロックでは、アラームを中止するため、後で通知されません。

ここにいくつかのサンプルコードがあります:

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)

これにはいくつかの注意事項があります:

  1. これはスレッドセーフではありません。シグナルは常にメインスレッドに配信されるため、これを他のスレッドに配置することはできません。
  2. 信号のスケジューリングと実際のコードの実行後、わずかな遅延があります。これは、例が10秒間スリープしただけでもタイムアウトになることを意味します。

しかし、それはすべて標準のPythonライブラリにあります!スリープ機能のインポートを除いて、それは1つだけのインポートです。多くの場所でタイムアウトを使用する場合は、簡単にTimeoutException、_timeout、および信号を関数に入れて、それを呼び出すだけです。または、デコレータを作成して関数に配置することもできます。以下のリンクにある回答を参照してください。

これを「コンテキストマネージャ」として設定して、次のwithステートメントで使用することもできます。

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

このコンテキストマネージャーアプローチの欠点の1つは、コードが実際にタイムアウトしたかどうかを確認できないことです。

ソースと推奨読書:


3
シグナルは唯一ので、それは、メインスレッドに配信されdefnitelyない、他のスレッドでは動作しませんでしょう
Dima Tisnek 2014年

1
タイムアウトデコレータパッケージは、信号(または必要に応じてマルチプロセッシング)を使用してタイムアウトデコレータを提供します。
クリスチャンロング

13

タイムアウトとエラー処理でこのリクエストを試してください:

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

5

設定stream=Trueして使用しますr.iter_content(1024)。はい、eventlet.Timeoutどういうわけか私にはうまくいきません。

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

議論はここにありますhttps://redd.it/80kp1h


残念なリクエストですが、maxtimeパラメータをサポートしていません。このソリューションは、asyncioで動作する唯一のソリューションです
wukong

4

これはやり過ぎかもしれませんが、Celery分散タスクキューはタイムアウトを適切にサポートしています。

特に、プロセスで例外を発生させるだけのソフトタイム制限(クリーンアップできるようにする)や、タイムリミットを超えたときにタスクを終了するハードタイム制限を定義できます。

内部では、これは「前」の投稿で参照されているのと同じ信号アプローチを使用しますが、より使いやすく管理しやすい方法で行われます。また、監視しているWebサイトのリストが長い場合は、その主要な機能である多数のタスクの実行を管理するためのあらゆる種類の方法を利用できます。


これは良い解決策になるでしょう。合計タイムアウトの問題はpython-requestshttplib(Python 2.7のリクエストで使用される)には直接関係していません。パッケージは、関連するすべてのものをtimeout直接httplibに渡します。プロセスはhttplibに長期間とどまることができるので、リクエストで修正できるものは何もないと思います。
hynekcer 2014

@hynekcer、私はあなたが正しいと思います。これが、Celeryのように、プロセス外でタイムアウトを検出し、プロセスを完全に強制終了することで強制するのが良いアプローチである理由です。
Chris Johnson

3

私はあなたがmultiprocessingサードパーティのパッケージを使用でき、依存しないと信じています:

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)

渡されたタイムアウトがkwargs取得するためのタイムアウトである任意のサーバーからの応答を、引数には、timeout取得するためのタイムアウトで、完全な応答を。


これは、すべてのエラーをキャッチし、それらをreturn_dict ['error']に入れるプライベート関数の汎用のtry / exceptで改善できます。次に、最後に、戻る前に、return_dictに「エラー」があるかどうかを確認し、それを発生させます。テストもはるかに簡単になります。
dialt0ne 2016年

2

timeout =(接続タイムアウト、データ読み取りタイムアウト)または単一の引数を指定(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

このコードはsocketError 11004および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_()

創造性への
賛成

1

リクエストについての質問にもかかわらず、これはpycurl CURLOPT_TIMEOUTまたはCURLOPT_TIMEOUT_MSを使用すると非常に簡単です。

スレッド化やシグナリングは不要です:

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

オプションstream=Trueを使用している場合は、これを行うことができます:

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

ソリューションは信号やマルチプロセッシングを必要としません。


1

もう1つの解決策(http://docs.python-requests.org/en/master/user/advanced/#streaming-uploadsから入手)

アップロードする前に、コンテンツサイズを確認できます。

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

ただし、送信者は 'content-length'応答フィールドに誤った値を設定する可能性があるので注意してください。


ありがとう。クリーンでシンプルなソリューション。私のために働く。
petezurich

0

それに関しては、10秒後にリクエストの内部状態を台無しにするウォッチドッグスレッドを作成します。例:

  • 下層のソケットを閉じ、理想的には
  • リクエストが操作を再試行した場合に例外をトリガーします

システムライブラリによっては、DNS解決の期限を設定できない場合があることに注意してください。


0

まあ、私はこのページで多くの解決策を試しましたが、それでも不安定さ、ランダムなハング、接続パフォーマンスの低下に直面しました。

私は現在Curlを使用していますが、このような貧弱な実装でも、 "最大時間"の機能とグローバルパフォーマンスに本当に満足しています。

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

ここでは、接続時間と転送時間の両方を包括する6秒の最大時間パラメーターを定義しました。

Python構文に固執したい場合は、Curlに適切なpythonバインディングがあると思います。


0

python関数をタイムアウトするために使用できる、timeout-decoratorというパッケージがあります。

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

ここでは、いくつかの回答が示唆するシグナルアプローチを使用しています。または、シグナルの代わりにマルチプロセッシングを使用するように指示することもできます(たとえば、マルチスレッド環境の場合)。


0

リクエスト2.2.1を使用していますが、イベントレットが機能しませんでした。代わりに、geventがgunicornのサービスで使用されているため、代わりにgeventタイムアウトを使用できました。

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)

gevent.timeout.Timeoutは一般的な例外処理ではキャッチされないことに注意してください。したがって、明示的にキャッチするgevent.timeout.Timeout か、別の例外を渡してそのように使用します。with gevent.Timeout(5, requests.exceptions.Timeout):ただし、この例外が発生したときにメッセージは渡されません。


-1

私は確かに醜いですが、実際の問題を修正するより直接的な解決策を考え出しました。次のようになります。

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

ここで完全な説明を読むことができます


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.