requests.requestにmax_retriesを設定できますか?


182

Pythonのリクエストモジュールはシンプルでエレガントですが、バグが1つあります。次のようなメッセージでrequests.exception.ConnectionErrorを取得することは可能です。

Max retries exceeded with url: ...

これは、リクエストがデータへのアクセスを数回試行できることを意味します。しかし、ドキュメントのどこにもこの可能性についての単一の言及はありません。ソースコードを確認したところ、デフォルト(おそらく0)の値を変更できる場所は見つかりませんでした。

それで、リクエストの最大再試行回数を何らかの方法で設定することは可能ですか?


9
2.xでのリクエストでこれに関する更新はありますか?requests.get(url、max_retries = num_max_retries))実装が気に入っています。
paragbaxi 2014年

11
@paragbaxi:さらに良いarequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
WoJ 2015

1
で); @WoJ私はあなたの例を取り、現実を作ったjust.getjust.postしてgithub.com/kootenpv/just
PascalVKooten

2
リクエストによる再試行に関する有用な記事:peterbe.com/plog/best-practice-with-retries-with-requests
Gokul

回答:


161

urllib3再試行を行うのは、基盤となるライブラリです。別の最大再試行回数を設定するには、代替のトランスポートアダプターを使用します。

from requests.adapters import HTTPAdapter

s = requests.Session()
s.mount('http://stackoverflow.com', HTTPAdapter(max_retries=5))

max_retries引数は整数またはかかるRetry()オブジェクトを、後者では、再試行される失敗の種類を細かく制御できます(整数値はRetry()、接続の失敗のみを処理するインスタンスに変換されます。接続が確立された後のエラーは、副作用につながる可能性があるため、デフォルトでは処理されません)。 。


リクエスト1.2.1のリリースに先立つ古い回答

requestsライブラリは、本当にこれは、設定がありません、またそれは、(参照するつもりはない、このプルリクエストを)。現在(リクエスト1.1)、再試行回数は0に設定されています。本当に高い値に設定したい場合は、これをグローバルに設定する必要があります。

import requests

requests.adapters.DEFAULT_RETRIES = 5

この定数は文書化されていません。将来のリリースではこれの処理方法が変更される可能性があるため、自分の危険で使用してください。

更新:これ変更されました。バージョン1.2.1 ではmax_retriesHTTPAdapter()クラスパラメーターを設定するオプションが追加されたため、代替のトランスポートアダプターを使用する必要があります(上記を参照)。HTTPAdapter.__init__()デフォルトにパッチを適用しない限り、モンキーパッチアプローチは機能しなくなります(ほとんど推奨されません)。


9
これが不要な場合は、すべてのサイトでこれを指定する必要はありません。session.mount('http://', HTTPAdapter(max_retries=10))これを行うだけで、すべてのhttp接続で機能します。httpsの場合も同様に、すべてのhttps接続で機能します。
user136036

1
@ user136036:はい、アダプタは最長のプレフィックスの一致によって検索されます。あなたはこれを適用したい場合は、すべてのその後のURL http://https://使用への最小限の接頭辞が、ドキュメントに答えのリンクを参照してください。
Martijn Pieters

1
HTTPAdapter(max_retries=5)特定のシナリオでのみ機能することに注意してください。以下からの要求ドキュメントNote, this applies only to failed DNS lookups, socket connections and connection timeouts, never to requests where data has made it to the server. By default, Requests does not retry failed connections.任意のステータスコードについての力の再試行するように、以下のdatashamanの答え@ご覧ください。
Steven Xu

@StevenXu:はい、Retry()どの障害シナリオが再試行されるかを変更するように構成できます。
Martijn Pieters

226

これにより、max_retriesが変更されるだけでなく、すべてのhttp://アドレスへのリクエストを一定時間(合計5回まで)再試行するバックオフ戦略が有効になります。

import requests
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter

s = requests.Session()

retries = Retry(total=5,
                backoff_factor=0.1,
                status_forcelist=[ 500, 502, 503, 504 ])

s.mount('http://', HTTPAdapter(max_retries=retries))

s.get('http://httpstat.us/500')

どおりのドキュメントはRetry:backoff_factorである場合0.1、その後スリープ()試行間[0.1秒、0.2秒、0.4秒、...]のためにスリープ状態になります。返されたステータスコードがある場合、それはまた、リトライを強制的に500502503または504

Retryより詳細な制御を可能にする他のさまざまなオプション:

  • total –許可する再試行の総数。
  • connect –再試行する接続関連エラーの数。
  • read –読み取りエラーで再試行する回数。
  • リダイレクト –実行するリダイレクトの数。
  • method_whitelist –再試行する必要がある大文字のHTTPメソッド動詞のセット。
  • status_forcelist –強制的に再試行する必要があるHTTPステータスコードのセット。
  • backoff_factor –試行の間に適用するバックオフ係数。
  • raise_on_redirect –リダイレクトの数を使い果たした場合、を発生させるMaxRetryErrorか、3xxの応答コードで応答を返すか範囲の。
  • raise_on_status -と同様の意味raise_on_redirect:私たちは例外を発生させ、またはステータスがに該当する場合、応答を返すべきかどうかstatus_forcelist範囲と再試行が使い果たされています。

注意raise_on_statusは比較的新しいものであり、urllib3またはリクエストのリリースにはまだ含まれていません。raise_on_statusキーワード引数はPythonのバージョン3.6で、最大で標準ライブラリにそれを作ったように見えます。

特定のHTTPステータスコードでリクエストを再試行するには、status_forcelistを使用します。たとえば、status_forcelist = [503]は、ステータスコード503(サービスを利用できません)で再試行します。

デフォルトでは、再試行は次の条件でのみ発生します。

  • プールから接続を取得できませんでした。
  • TimeoutError
  • HTTPException発生(Python 3のhttp.clientから、それ以外はhttplib)。これは、URLやプロトコルが正しく形成されていないなど、低レベルのHTTP例外のようです。
  • SocketError
  • ProtocolError

これらはすべて、通常のHTTP応答の受信を妨げる例外です。場合は任意の定期的な応答が生成され、何の再試行は行われません。status_forcelistを使用しない場合ないと、ステータス500の応答でさえ再試行されません。

それは、リモートAPIやWebサーバでの作業のために、より直感的であるように動作させるために、私は上記のコードを使用することになり、その力の状態に再試行500502503及び 504で珍しくありませんすべてが、十分に大きなバックオフ期間が与えられれば、Webおよび(おそらく)回復可能です。

EDITED:インポートRetryから直接クラスurllib3


1
私はあなたのロジックを実装しようとしていますが、resステータスが503であってもログに1つのリクエストしか表示されないので、それが機能しているかどうかわかりません。再試行が機能しているかどうかを確認するにはどうすればよいですか?コードを参照してください。pastebin.com/ rty4bKTw
Danilo Oliveira

1
添付されたコードは期待どおりに動作します。コツは、status_forcelistパラメータです。これにより、urllib3パッケージは特定のステータスコードを再試行します。コード:pastebin.com/k2bFbH7Z
datashaman

1
urllib3は、ステータス503が(デフォルトでは)例外であるとは考えていません(考えるべきではありません)。
datashaman 2016

1
@Connorいいえ、アダプターはセッションに接続されています。
datashaman 2018

1
urlib3.Retryはリクエストの一部ではなくなりました。これは直接インポートする必要があります。編集の提案
user2390183

59

注意してください、Martijn Pietersの回答はバージョン1.2.1+には適していません。ライブラリにパッチを適用せずにグローバルに設定することはできません。

代わりにこれを行うことができます:

import requests
from requests.adapters import HTTPAdapter

s = requests.Session()
s.mount('http://www.github.com', HTTPAdapter(max_retries=5))
s.mount('https://www.github.com', HTTPAdapter(max_retries=5))

22
良い解決策ですが、再試行の間に遅延がないことに注意してください。試行の合間に眠りたい場合は、自分で寝る必要があります。
nofinator 2013年

18

ここでいくつかの答えに少し苦労した後、私は自分の状況に適しているバックオフと呼ばれるライブラリを見つけました。基本的な例:

import backoff

@backoff.on_exception(
    backoff.expo,
    requests.exceptions.RequestException,
    max_tries=5,
    giveup=lambda e: e.response is not None and e.response.status_code < 500
)
def publish(self, data):
    r = requests.post(url, timeout=10, json=data)
    r.raise_for_status()

ライブラリのネイティブ機能を試すことをお勧めしますが、問題が発生したり、より広範な制御が必要な場合は、バックオフを選択できます。


1
素晴らしいライブラリ、ありがとう!この機能は以外のものに必要だったrequestsので、これは完全に機能します!
Dennis Golomazov

3

より高度な制御を得るためのより明確な方法は、再試行のものを関数にパッケージ化し、デコレータを使用してその関数を再試行可能にし、例外をホワイトリストに登録することです。

私はここで同じものを作成しました: http //www.praddy.in/retry-decorator-whitelisted-exceptions/

そのリンクのコードを再現する:

def retry(exceptions, delay=0, times=2):
"""
A decorator for retrying a function call with a specified delay in case of a set of exceptions

Parameter List
-------------
:param exceptions:  A tuple of all exceptions that need to be caught for retry
                                    e.g. retry(exception_list = (Timeout, Readtimeout))
:param delay: Amount of delay (seconds) needed between successive retries.
:param times: no of times the function should be retried


"""
def outer_wrapper(function):
    @functools.wraps(function)
    def inner_wrapper(*args, **kwargs):
        final_excep = None  
        for counter in xrange(times):
            if counter > 0:
                time.sleep(delay)
            final_excep = None
            try:
                value = function(*args, **kwargs)
                return value
            except (exceptions) as e:
                final_excep = e
                pass #or log it

        if final_excep is not None:
            raise final_excep
    return inner_wrapper

return outer_wrapper

@retry(exceptions=(TimeoutError, ConnectTimeoutError), delay=0, times=3)
def call_api():
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.