Pythonリクエストモジュールはシンプルでエレガントですが、1つ問題があります。次のようなメッセージでrequests.exception.ConnectionErrorを取得することが可能です。
Max retries exceeded with url: ...
これは、リクエストがデータへのアクセスを数回試行できることを意味します。しかし、ドキュメント内のどこにもこの可能性についての単一の言及はありません。ソースコードを見ると、デフォルト(おそらく0)の値を変更できる場所が見つかりませんでした。
リクエストの最大再試行回数を何らかの方法で設定することは可能ですか?
再試行を行うのは、基になるurllib3
ライブラリです。別の最大再試行回数を設定するには、 alternative transport adapters を使用します。
from requests.adapters import HTTPAdapter
s = requests.Session()
s.mount('http://stackoverflow.com', HTTPAdapter(max_retries=5))
max_retries
引数は整数または Retry()
object ;を取ります後者は、どの種類の障害が再試行されるかをきめ細かく制御できます(整数値は、接続障害のみを処理するRetry()
インスタンスに変換されます。接続が確立された後のエラーは、副作用を引き起こす可能性があるため、デフォルトでは処理されません)。
リクエスト1.2.1のリリースより前の古い回答:
requests
ライブラリーは、実際にこれを構成可能にするものでも、意図するものでもありません( this pull request を参照)。現在(要求1.1)、再試行回数は0に設定されています。本当に高い値に設定する場合は、これをグローバルに設定する必要があります。
import requests
requests.adapters.DEFAULT_RETRIES = 5
この定数は文書化されていません。将来のリリースではこの処理方法が変更される可能性があるため、ご自身の責任で使用してください。
Update:そして、このdidの変更;バージョン1.2.1では max_retries
パラメーターを設定するオプション が HTTPAdapter()
クラス に追加されたため、代替トランスポートアダプターを使用する必要があります。上記を参照してください。 HTTPAdapter.__init__()
のデフォルトもパッチしない限り、モンキーパッチアプローチは機能しなくなります(あまりお勧めしません)。
これにより、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が.1の場合、sleep()は再試行の間[0.1s、0.2s、0.4s、...]の間スリープします。また、返されるステータスコードが5、502、5、または504の場合、強制的に再試行します。
Retry
のその他のさまざまなオプションにより、よりきめ細かな制御が可能になります。
MaxRetryError
を上げるか、またはxxの範囲の応答コードで応答を返すかどうか。NB:raise_on_statusは比較的新しく、まだurllib3またはリクエストのリリースには至っていません。 raise_on_statusキーワード引数は、最大でpythonバージョン3.6で標準ライブラリに追加されたようです。
特定のHTTPステータスコードでリクエストを再試行するには、status_forcelistを使用します。たとえば、status_forcelist = [503]はステータスコードで再試行します5(サービスは利用できません)。
デフォルトでは、次の条件でのみ再試行が起動します。
TimeoutError
HTTPException
が発生しました(http.client in Python 3 else httplibから)。これは、URLやプロトコルが正しく形成されていないなど、低レベルのHTTP例外のようです。SocketError
ProtocolError
これらはすべて、通常のHTTP応答の受信を妨げる例外です。 any通常の応答が生成された場合、再試行は行われません。 status_forcelistを使用しないと、ステータス500の応答でさえ再試行されません。
リモートAPIまたはWebサーバーを操作するためにより直感的な方法で動作させるには、上記のコードスニペットを使用します。これは、ステータスの再試行を強制します5、502 =、5および504。これらはすべてウェブ上では珍しくなく、十分なバックオフ期間が与えられた場合は(おそらく)回復可能です。
編集:rllibからRetry
クラスを直接インポートします。
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))
ここでいくつかの答えに少し苦労した後、 backoff というライブラリが見つかりました。基本的な例:
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()
ライブラリのネイティブ機能を試してみることを引き続きお勧めしますが、問題が発生した場合やより広範な制御が必要な場合は、バックオフがオプションです。
より高度な制御を取得するためのよりクリーンな方法は、リトライスタッフを関数にパッケージ化し、デコレータを使用してその関数を再試行可能にし、例外をホワイトリストに登録することです。
ここで同じものを作成しました: 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():