ฉันสามารถตั้งค่า max_retries สำหรับคำร้องขอได้หรือไม่


182

โมดูลคำขอ Python นั้นเรียบง่ายและสวยงาม แต่มีสิ่งหนึ่งที่ทำให้ฉันรำคาญ เป็นไปได้ที่จะได้รับการร้องขอการยกเว้นการเชื่อมต่อกับข้อความเช่น:

Max retries exceeded with url: ...

แสดงว่าคำขอสามารถพยายามเข้าถึงข้อมูลได้หลายครั้ง แต่ไม่มีการกล่าวถึงความเป็นไปได้นี้ในเอกสาร ดูซอร์สโค้ดฉันไม่พบที่ที่ฉันสามารถเปลี่ยนค่าเริ่มต้น (สมมุติ 0)

ดังนั้นเป็นไปได้หรือไม่ที่จะกำหนดจำนวนการลองใหม่สูงสุดสำหรับคำขอ


9
มีการปรับปรุงใด ๆ เกี่ยวกับคำขอที่ 2.x? จะรักการใช้งาน request.get (url, max_retries = num_max_retries))
paragbaxi

11
@paragbaxi: และดียิ่งขึ้นrequests.get(url, max_retries=num_max_retries, dely_between_retries=3))
58 เวลา 15.26

1
@WoJ ฉันเอาตัวอย่างของคุณและทำให้มันเป็นจริง;) ในjust.getและjust.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_retriesพารามิเตอร์ในHTTPAdapter()คลาสดังนั้นตอนนี้คุณต้องใช้อะแดปเตอร์การขนส่งทางเลือกดูด้านบน วิธีการแก้ลิงไม่ทำงานอีกต่อไปเว้นแต่ว่าคุณจะแก้ไขHTTPAdapter.__init__()ค่าเริ่มต้นด้วย (ไม่แนะนำอย่างมาก)


9
คุณไม่จำเป็นต้องระบุสิ่งนี้สำหรับทุก ๆ ไซต์หากไม่จำเป็น คุณสามารถทำสิ่งsession.mount('http://', HTTPAdapter(max_retries=10))นี้ได้สำหรับการเชื่อมต่อ http ทั้งหมด เช่นเดียวกันกับ https จะใช้งานได้สำหรับการเชื่อมต่อ https ทั้งหมด
136036

1
@ user136036: ใช่อะแดปเตอร์ค้นหาโดยการจับคู่คำนำหน้าที่ยาวที่สุด หากคุณต้องการให้สิ่งนี้ใช้กับURL ทั้งหมดhttp://และhttps://เป็นคำนำหน้าขั้นต่ำสุดที่จะใช้ดูเอกสารประกอบคำตอบของลิงก์
Martijn Pieters

1
โปรดทราบว่าHTTPAdapter(max_retries=5)จะใช้งานได้กับบางสถานการณ์เท่านั้น จากการร้องขอ 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.ต้องการบังคับให้ลองใหม่อีกครั้งสำหรับรหัสสถานะใด ๆ ดู @ คำตอบ datashaman ด้านล่าง
Steven Xu

@StevenXu: ใช่คุณสามารถกำหนดค่าRetry()ให้แก้ไขสถานการณ์ความล้มเหลวที่ลองใหม่ได้
Martijn Pieters

228

สิ่งนี้จะไม่เพียงเปลี่ยนmax_retriesแต่ยังเปิดใช้งานกลยุทธ์ backoff ซึ่งทำให้การร้องขอไปยัง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ดังนั้น sleep () จะนอนสำหรับ [0.1s, 0.2s, 0.4s, ... ] ระหว่างการลองใหม่ นอกจากนี้ยังจะบังคับให้ลองใหม่อีกครั้งถ้ารหัสสถานะกลับมาเป็น500 , 502 , 503หรือ504

ตัวเลือกอื่น ๆ เพื่อRetryให้สามารถควบคุมได้ละเอียดยิ่งขึ้น:

  • ทั้งหมด - จำนวนการลองใหม่ทั้งหมดที่อนุญาต
  • เชื่อมต่อ - จำนวนข้อผิดพลาดเกี่ยวกับการเชื่อมต่อที่ต้องลองอีกครั้ง
  • อ่าน - กี่ครั้งที่จะลองอ่านข้อผิดพลาดอีกครั้ง
  • เปลี่ยนเส้นทาง - กี่เปลี่ยนเส้นทางที่จะดำเนินการ
  • method_whitelist - ชุดของคำกริยา HTTP ตัวพิมพ์ใหญ่ที่เราควรลองใหม่
  • status_forcelist - ชุดรหัสสถานะ HTTP ที่เราควรบังคับให้ลองใหม่
  • backoff_factor - ปัจจัย backoff ที่จะใช้ระหว่างความพยายาม
  • lift_on_redirect - หากจำนวนการเปลี่ยนเส้นทางหมดลงเพื่อเพิ่มMaxRetryErrorหรือเพื่อตอบกลับด้วยรหัสตอบกลับในช่วง3xx
  • boost_on_status - ความหมายที่คล้ายกันกับra_on_redirect : ไม่ว่าเราควรจะเพิ่มข้อยกเว้นหรือกลับคำตอบถ้าสถานะอยู่ในช่วงstatus_forcelistและลองใหม่ได้หมดลง

หมายเหตุ : Ra_on_statusค่อนข้างใหม่และยังไม่ได้เปิดตัว urllib3 หรือคำขอ raise_on_statusโต้แย้งคำหลักที่ดูเหมือนจะทำให้มันเป็นห้องสมุดมาตรฐานที่มากที่สุดในหลามรุ่น 3.6

หากต้องการส่งคำขอลองใหม่อีกครั้งเกี่ยวกับรหัสสถานะเฉพาะ HTTP ใช้status_forcelist ตัวอย่างเช่นstatus_forcelist = [503]จะลองอีกครั้งในรหัสสถานะ503 (บริการไม่พร้อมใช้งาน)

ตามค่าเริ่มต้นการลองใหม่จะเริ่มขึ้นสำหรับเงื่อนไขเหล่านี้:

  • ไม่สามารถรับการเชื่อมต่อจากพูล
  • TimeoutError
  • HTTPExceptionยกระดับ (จากhttp.clientใน Python 3 else httplib ) ดูเหมือนว่าจะเป็นข้อยกเว้น HTTP ระดับต่ำเช่น URL หรือโปรโตคอลไม่ได้เกิดขึ้นอย่างถูกต้อง
  • SocketError
  • ProtocolError

โปรดสังเกตว่าสิ่งเหล่านี้เป็นข้อยกเว้นทั้งหมดที่ป้องกันไม่ให้ได้รับการตอบกลับ HTTP ปกติ หากมีการตอบสนองปกติใด ๆจะไม่มีการลองใหม่ โดยไม่ใช้status_forcelistแม้แต่การตอบกลับด้วยสถานะ 500 จะไม่ถูกลองใหม่

เพื่อให้มันทำงานในลักษณะที่ใช้งานง่ายยิ่งขึ้นสำหรับการทำงานกับรีโมต API หรือเว็บเซิร์ฟเวอร์ฉันจะใช้ข้อมูลโค้ดข้างต้นซึ่งบังคับให้ลองใหม่ในสถานะ500 , 502 , 503และ504ซึ่งทั้งหมดนี้ไม่ใช่เรื่องแปลกสำหรับ เว็บและ (อาจ) สามารถกู้คืนได้เมื่อกำหนดระยะเวลาแบ็คออฟที่เพียงพอ

แก้ไขนำเข้า: Retryระดับโดยตรงจากurllib3


1
ฉันพยายามที่จะใช้ตรรกะของคุณ แต่ฉันไม่รู้ว่ามันใช้งานได้หรือไม่เพราะบันทึกแสดงเพียงคำขอเดียวแม้สถานะ res คือ 503 ฉันจะทราบได้อย่างไรว่าการลองใหม่ใช้งานได้หรือไม่ ดูรหัส: pastebin.com/rty4bKTw
Danilo Oliveira

1
รหัสที่แนบมาทำงานได้ตามที่คาดไว้ เคล็ดลับคือพารามิเตอร์status_forcelist สิ่งนี้จะบอกแพ็คเกจ urllib3 ให้ลองรหัสสถานะเฉพาะอีกครั้ง รหัส: pastebin.com/k2bFbH7Z
datashaman

1
urllib3 ไม่ได้ (และไม่ควร) คิดว่าสถานะ 503 เป็นข้อยกเว้น (โดยค่าเริ่มต้น)
datashaman

1
@ ไม่ได้มีอะแดปเตอร์ต่ออยู่กับเซสชัน
datashaman

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

18

หลังจากดิ้นรนเล็กน้อยกับคำตอบที่นี่ฉันพบห้องสมุดชื่อ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()

ฉันยังคงแนะนำให้ให้ฟังก์ชั่นพื้นฐานของห้องสมุด แต่ถ้าคุณพบปัญหาหรือต้องการการควบคุมที่กว้างกว่า Backoff เป็นตัวเลือก


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.