Tôi có thể đặt max_retries cho request.request không?


182

Mô-đun yêu cầu Python đơn giản và thanh lịch nhưng có một điều khiến tôi khó chịu. Có thể nhận được request.exception.ConnectionError với một thông báo như:

Max retries exceeded with url: ...

Điều này ngụ ý rằng các yêu cầu có thể cố gắng truy cập dữ liệu nhiều lần. Nhưng không có một đề cập nào về khả năng này ở bất cứ đâu trong các tài liệu. Nhìn vào mã nguồn tôi không tìm thấy nơi nào có thể thay đổi giá trị mặc định (có lẽ là 0).

Vì vậy, bằng cách nào đó có thể thiết lập số lần thử lại tối đa cho các yêu cầu?


9
Bất kỳ cập nhật về điều này với các yêu cầu tại 2.x? Rất thích triển khai request.get (url, max_retries = num_max_retries)).
paragbaxi

11
@paragbaxi: và thậm chí tốt hơn arequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
WoJ

1
@WoJ Tôi lấy ví dụ của bạn và biến nó thành hiện thực;) trong just.getjust.posttrong github.com/kootenpv/just
PascalVKooten

2
Bài viết hữu ích về thử lại với Yêu cầu: peterbe.com/plog/best-practice-with-retries-with-requests
Gokul

Câu trả lời:


161

Đây là urllib3thư viện cơ bản thực hiện thử lại. Để đặt số lần thử lại tối đa khác nhau, hãy sử dụng bộ điều hợp vận chuyển thay thế :

from requests.adapters import HTTPAdapter

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

Đối max_retriessố lấy một số nguyên hoặc một Retry()đối tượng ; cái sau cung cấp cho bạn quyền kiểm soát chi tiết về các loại lỗi được thử lại (một giá trị số nguyên được biến thành một Retry()thể hiện chỉ xử lý các lỗi kết nối; các lỗi sau khi kết nối được tạo theo mặc định không được xử lý vì chúng có thể dẫn đến các tác dụng phụ) .


Câu trả lời cũ, trước khi phát hành các yêu cầu 1.2.1 :

Các requeststhư viện không thực sự làm cho cấu hình này, nó cũng không phải có ý định (xem yêu cầu kéo này ). Hiện tại (yêu cầu 1.1), số lần thử lại được đặt thành 0. Nếu bạn thực sự muốn đặt nó thành giá trị cao hơn, bạn sẽ phải đặt giá trị này trên toàn cầu:

import requests

requests.adapters.DEFAULT_RETRIES = 5

Hằng số này không được ghi lại; sử dụng nó trong tình trạng nguy hiểm của riêng bạn vì các bản phát hành trong tương lai có thể thay đổi cách xử lý việc này.

Cập nhật : và điều này đã thay đổi; trong phiên bản 1.2.1 , tùy chọn đặt max_retriestham số trên HTTPAdapter()lớp đã được thêm vào, do đó bây giờ bạn phải sử dụng các bộ điều hợp vận chuyển thay thế, xem ở trên. Phương pháp vá khỉ không còn hoạt động, trừ khi bạn cũng vá các HTTPAdapter.__init__()mặc định (rất không được khuyến nghị).


9
Bạn không phải chỉ định điều này cho mọi trang web nếu điều này là không cần thiết. Bạn chỉ có thể làm session.mount('http://', HTTPAdapter(max_retries=10))điều này sẽ làm việc cho tất cả các kết nối http. Tương tự với https sau đó sẽ hoạt động cho tất cả các kết nối https.
dùng136036

1
@ user136036: có, bộ điều hợp được tra cứu bằng cách khớp tiền tố dài nhất; nếu bạn muốn áp dụng điều này cho tất cả các url http://https://là tiền tố tối thiểu để sử dụng, hãy xem tài liệu hướng dẫn trả lời.
Martijn Pieters

1
Lưu ý rằng HTTPAdapter(max_retries=5)sẽ chỉ làm việc cho kịch bản nhất định. Từ yêu cầu 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.Để buộc thử lại bất kỳ mã trạng thái nào, hãy xem câu trả lời của @ datashaman bên dưới.
Steven Xu

@StevenXu: có, bạn có thể định cấu hình Retry()để thay đổi kịch bản lỗi nào được thử lại.
Martijn Pieters

226

Điều này sẽ không chỉ thay đổi max_retries mà còn cho phép chiến lược backoff khiến các yêu cầu cho tất cả các địa chỉ http: // ngủ trong một khoảng thời gian trước khi thử lại (tổng cộng 5 lần):

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

Theo tài liệu choRetry : nếu backoff_factor là 0,1 , thì ngủ () sẽ ngủ trong [ 0,1s , 0,2s , 0,4 , ...] giữa các lần thử lại. Nó cũng sẽ buộc thử lại nếu mã trạng thái được trả về là 500 , 502 , 503 hoặc 504 .

Nhiều tùy chọn khác để Retrycho phép kiểm soát chi tiết hơn:

  • tổng cộng - Tổng số lần thử lại cho phép.
  • kết nối - Có bao nhiêu lỗi liên quan đến kết nối để thử lại.
  • đọc - Bao nhiêu lần để thử lại đọc lỗi.
  • redirect - Có bao nhiêu chuyển hướng để thực hiện.
  • method_whlistist - Tập hợp các động từ phương thức HTTP mà chúng ta nên thử lại.
  • status_forcelist - Một tập hợp các mã trạng thái HTTP mà chúng ta nên buộc thử lại.
  • backoff_factor - Một yếu tố backoff để áp dụng giữa các lần thử.
  • nâng_on_redirect - Cho dù, nếu số lượng chuyển hướng đã hết, để tăng MaxRetryErrorhoặc trả về phản hồi với mã phản hồi trong phạm vi 3xx .
  • grow_on_status - Có nghĩa tương tự như grow_on_redirect : chúng ta nên đưa ra một ngoại lệ hay trả về một phản hồi, nếu trạng thái rơi vào phạm vi status_forcelist và thử lại đã hết.

NB : grow_on_status tương đối mới và chưa đưa nó vào bản phát hành urllib3 hoặc yêu cầu. Đốisố từ khóa grow_on_status dường như đã đưa nó vào thư viện chuẩn nhiều nhất trong phiên bản python 3.6.

Để thực hiện yêu cầu thử lại trên các mã trạng thái HTTP cụ thể, hãy sử dụng status_forcelist . Ví dụ: status_forcelist = [503] sẽ thử lại trên mã trạng thái 503 (dịch vụ không khả dụng).

Theo mặc định, thử lại chỉ kích hoạt các điều kiện sau:

  • Không thể có được kết nối từ hồ bơi.
  • TimeoutError
  • HTTPExceptionđược nâng lên (từ http.client trong Python 3 khác omeplib ). Đây có vẻ là trường hợp ngoại lệ HTTP cấp thấp, như URL hoặc giao thức không được hình thành chính xác.
  • SocketError
  • ProtocolError

Lưu ý rằng đây là tất cả các ngoại lệ ngăn không nhận được phản hồi HTTP thông thường. Nếu bất kỳ phản hồi thường xuyên được tạo ra, không có thử lại được thực hiện. Không sử dụng status_forcelist , ngay cả phản hồi với trạng thái 500 cũng sẽ không được thử lại.

Để làm cho nó hoạt động theo cách trực quan hơn khi làm việc với API hoặc máy chủ web từ xa, tôi sẽ sử dụng đoạn mã trên, buộc phải thử lại các trạng thái 500 , 502 , 503504 , tất cả đều không phổ biến trên web và (có thể) có thể phục hồi được trong một khoảng thời gian chờ đủ lớn.

EDITED : Nhập Retrylớp trực tiếp từ urllib3 .


1
Tôi đang cố gắng thực hiện logic của bạn, nhưng tôi không biết liệu nó có hoạt động không vì nhật ký chỉ hiển thị một yêu cầu ngay cả trạng thái res là 503. Làm thế nào tôi có thể biết liệu thử lại có hoạt động không? Xem mã: pastebin.com/rty4bKTw
Danilo Oliveira

1
Mã đính kèm hoạt động như mong đợi. Thủ thuật là tham số status_forcelist . Điều này báo cho gói urllib3 thử lại mã trạng thái cụ thể. Mã: pastebin.com/k2bFbH7Z
datashaman

1
urllib3 không (và không nên) nghĩ rằng trạng thái 503 là một ngoại lệ (theo mặc định).
datashaman

1
@Connor không, bộ chuyển đổi được gắn vào phiên.
datashaman

1
urlib3.R tem không còn là một phần của yêu cầu. cái này phải nhập trực tiếp. Đề xuất chỉnh sửa
user2390183

59

Hãy cẩn thận, câu trả lời của Martijn Pieters không phù hợp với phiên bản 1.2.1+. Bạn không thể thiết lập nó trên toàn cầu mà không cần vá thư viện.

Bạn có thể làm điều này thay vào đó:

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
Giải pháp tốt đẹp nhưng lưu ý rằng không có sự chậm trễ giữa các lần thử lại. Nếu bạn muốn ngủ giữa các lần thử, bạn sẽ cần phải tự lăn.
sáng lập

18

Sau khi vật lộn một chút với một số câu trả lời ở đây, tôi tìm thấy một thư viện gọi là backoff hoạt động tốt hơn cho tình huống của tôi. Một ví dụ cơ bản:

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

Tôi vẫn khuyên bạn nên cung cấp cho chức năng riêng của thư viện một cú đánh, nhưng nếu bạn gặp phải bất kỳ vấn đề nào hoặc cần kiểm soát rộng hơn, thì backoff là một lựa chọn.


1
thư viện tuyệt vời, cảm ơn bạn! Tôi cần chức năng này cho một cái gì đó khác requests, vì vậy nó hoạt động hoàn hảo!
Dennis Golomazov

3

Một cách sạch hơn để có được sự kiểm soát cao hơn có thể là đóng gói các công cụ thử lại vào một chức năng và làm cho chức năng đó có thể truy xuất được bằng cách sử dụng một trình trang trí và liệt kê các ngoại lệ.

Tôi đã tạo ra điều tương tự ở đây: http://www.praddy.in/retry-decorator-whlististed-exceptions/

Tái tạo mã trong liên kết đó:

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():
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.