Bisakah saya menetapkan max_retries untuk requests.request?

182

Modul permintaan Python sederhana dan elegan tetapi satu hal mengganggu saya. Dimungkinkan untuk mendapatkan requests.exception.ConnectionError dengan pesan seperti:

Max retries exceeded with url: ...

Ini menyiratkan bahwa permintaan dapat mencoba mengakses data beberapa kali. Tetapi tidak ada satu pun penyebutan kemungkinan ini di dokumen. Melihat kode sumber saya tidak menemukan tempat di mana saya dapat mengubah nilai default (mungkin 0).

Jadi, mungkinkah untuk menetapkan jumlah percobaan ulang maksimum untuk permintaan?

Kirill Zaitsev
sumber
9
Adakah pembaruan tentang hal ini dengan permintaan pada 2.x? Akan menyukai implementasi requests.get (url, max_retries = num_max_retries)).
paragbaxi
11
@paragbaxi: dan bahkan lebih baikrequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
WoJ
1
@ WoJ Saya mengambil contoh Anda dan menjadikannya kenyataan;) di just.getdan just.postdi github.com/kootenpv/just
PascalVKooten
2
Artikel bermanfaat tentang coba
Gokul

Jawaban:

161

Ini adalah urllib3pustaka dasar yang melakukan pengulangan. Untuk menetapkan jumlah coba ulang maksimum yang berbeda, gunakan adapter transportasi alternatif :

from requests.adapters import HTTPAdapter

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

The max_retriesArgumen mengambil integer atau Retry()objek ; yang terakhir memberi Anda kendali halus atas jenis kegagalan apa yang dicoba (nilai integer diubah menjadi Retry()instance yang hanya menangani kegagalan koneksi; kesalahan setelah koneksi dibuat secara default tidak ditangani karena ini dapat menyebabkan efek samping) .


Jawaban lama, sebelum rilis permintaan 1.2.1 :

The requestsperpustakaan tidak benar-benar membuat dikonfigurasi ini, juga tidak berniat untuk (melihat permintaan tarik ini ). Saat ini (permintaan 1.1), jumlah percobaan ulang diatur ke 0. Jika Anda benar-benar ingin mengaturnya ke nilai yang lebih tinggi, Anda harus mengatur ini secara global:

import requests

requests.adapters.DEFAULT_RETRIES = 5

Konstanta ini tidak didokumentasikan; gunakan dengan risiko Anda sendiri karena rilis mendatang dapat mengubah cara penanganannya.

Perbarui : dan ini memang berubah; dalam versi 1.2.1 opsi untuk mengatur max_retriesparameter pada HTTPAdapter()kelas telah ditambahkan, sehingga sekarang Anda harus menggunakan adapter transportasi alternatif, lihat di atas. Pendekatan monkey-patch tidak lagi berfungsi, kecuali jika Anda juga menambal HTTPAdapter.__init__()default (sangat tidak disarankan).

Martijn Pieters
sumber
9
Anda tidak harus menentukan ini untuk setiap situs jika ini tidak diperlukan. Anda hanya dapat melakukan session.mount('http://', HTTPAdapter(max_retries=10))ini akan bekerja untuk semua koneksi http. Hal yang sama dengan https akan berfungsi untuk semua koneksi https.
user136036
1
@ user136036: ya, adaptor dilihat oleh kecocokan awalan terpanjang; jika Anda ingin ini berlaku untuk semua url http://dan https://merupakan awalan minimal untuk digunakan, lihat dokumentasi tautan jawaban.
Martijn Pieters
1
Perhatikan bahwa HTTPAdapter(max_retries=5)hanya akan berfungsi untuk skenario tertentu. Dari permintaan doc , 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.Untuk mencoba lagi mencoba kode status, lihat jawaban @ datashaman di bawah ini.
Steven Xu
@StevenXu: ya, Anda bisa mengonfigurasi Retry()untuk mengubah skenario kegagalan apa yang dicoba.
Martijn Pieters
226

Ini tidak hanya akan mengubah max_retries tetapi juga mengaktifkan strategi backoff yang membuat permintaan ke semua http: // alamat tidur untuk jangka waktu tertentu sebelum mencoba lagi (hingga total 5 kali):

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

Sesuai dokumentasi untukRetry : jika backoff_factor adalah 0,1 , maka sleep () akan tidur selama [ 0,1 detik, 0,2 detik, 0,4 detik, ...] di antara percobaan ulang. Ini juga akan memaksa mencoba ulang jika kode status yang dikembalikan adalah 500 , 502 , 503 atau 504 .

Berbagai opsi lain untuk Retrymemungkinkan kontrol yang lebih terperinci:

  • total - Total jumlah percobaan yang diizinkan.
  • connect - Berapa banyak kesalahan yang berhubungan dengan koneksi untuk mencoba kembali.
  • baca - Berapa kali untuk mencoba lagi kesalahan baca.
  • redirect - Berapa banyak pengalihan yang harus dilakukan.
  • method_whitelist - Kumpulan kata kerja metode HTTP huruf besar yang harus kita coba lagi.
  • status_forcelist - Satu set kode status HTTP yang harus kita coba lagi coba.
  • backoff_factor - Faktor backoff untuk diterapkan di antara upaya.
  • rais_on_redirect - Apakah, jika jumlah pengalihan habis, untuk meningkatkan MaxRetryError, atau untuk mengembalikan respons dengan kode respons dalam kisaran 3xx .
  • rais_on_status - Arti yang mirip dengan rais_on_redirect : apakah kita harus meningkatkan pengecualian, atau mengembalikan respons, jika statusnya jatuh dalam rentang status_forcelist dan coba lagi telah habis.

NB : meningkatkan_on_status relatif baru, dan belum membuatnya menjadi rilis urllib3 atau permintaan. The raise_on_status argumen kata kunci tampaknya telah berhasil masuk ke perpustakaan standar paling di python versi 3.6.

Untuk membuat permintaan coba lagi pada kode status HTTP tertentu, gunakan status_forcelist . Misalnya, status_forcelist = [503] akan mencoba lagi pada kode status 503 (layanan tidak tersedia).

Secara default, coba lagi hanya untuk kondisi berikut:

  • Tidak bisa mendapatkan koneksi dari kolam.
  • TimeoutError
  • HTTPExceptiondimunculkan (dari http.client dengan Python 3 lain httplib ). Ini tampaknya merupakan pengecualian HTTP tingkat rendah, seperti URL atau protokol yang tidak dibentuk dengan benar.
  • SocketError
  • ProtocolError

Perhatikan bahwa ini adalah semua pengecualian yang mencegah respons HTTP reguler diterima. Jika ada respons reguler yang dihasilkan, coba ulang tidak dilakukan. Tanpa menggunakan status_forcelist , bahkan respons dengan status 500 tidak akan dicoba lagi.

Untuk membuatnya berperilaku dengan cara yang lebih intuitif untuk bekerja dengan API atau server web jarak jauh, saya akan menggunakan cuplikan kode di atas, yang memaksa mencoba lagi pada status 500 , 502 , 503 dan 504 , yang semuanya tidak biasa pada web dan (mungkin) dapat dipulihkan mengingat periode backoff yang cukup besar.

Diedit : Retrykelas impor langsung dari urllib3 .

datashaman
sumber
1
Saya mencoba menerapkan logika Anda, tetapi saya tidak tahu apakah itu berfungsi karena log hanya menampilkan satu permintaan bahkan status res adalah 503. Bagaimana saya bisa tahu jika coba lagi bekerja? Lihat kode: pastebin.com/rty4bKTw
Danilo Oliveira
1
Kode terlampir berfungsi seperti yang diharapkan. Caranya adalah parameter status_forcelist . Ini memberi tahu paket urllib3 untuk mencoba lagi kode status tertentu. Kode: pastebin.com/k2bFbH7Z
datashaman
1
urllib3 tidak (dan seharusnya tidak) berpikir bahwa status 503 adalah pengecualian (secara default).
datashaman
1
@Connor no, adaptor terpasang ke sesi.
datashaman
1
urlib3.Retry tidak lagi menjadi bagian dari permintaan. ini harus diimpor langsung. Edit yang disarankan
user2390183
59

Hati-hati, jawaban Martijn Pieters tidak cocok untuk versi 1.2.1+. Anda tidak dapat mengaturnya secara global tanpa menambal perpustakaan.

Anda bisa melakukan ini sebagai gantinya:

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))
gizmondo
sumber
22
Solusi yang bagus tetapi perhatikan bahwa tidak ada penundaan antara percobaan ulang. Jika Anda ingin tidur di antara upaya, Anda harus menggulung sendiri.
nofinator
18

Setelah berjuang sedikit dengan beberapa jawaban di sini, saya menemukan sebuah perpustakaan bernama backoff yang berfungsi lebih baik untuk situasi saya. Contoh dasar:

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

Saya masih merekomendasikan untuk memberikan fungsionalitas asli perpustakaan, tetapi jika Anda mengalami masalah atau memerlukan kontrol yang lebih luas, backoff adalah sebuah opsi.

Brad Koch
sumber
1
perpustakaan yang bagus, terima kasih! Saya membutuhkan fungsi ini untuk hal lain selain requests, jadi ini berfungsi dengan sempurna!
Dennis Golomazov
3

Cara yang lebih bersih untuk mendapatkan kontrol yang lebih tinggi mungkin untuk mengemas barang coba lagi ke dalam suatu fungsi dan membuat fungsi itu dapat dipulihkan menggunakan dekorator dan membuat daftar putih pengecualian.

Saya telah membuat yang sama di sini: http://www.praddy.in/retry-decorator-whitelisted-exceptions/

Mereproduksi kode di tautan itu:

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():
praddy
sumber