하얀설표 블로그




예제)요청 실패시 재시도 설정하는 방법(urllib3.util.Retry, requests.Session)





( 수정됨)


예시

단순히 재시도 횟수만 설정할 것이라면 다음과 같이 설정하면 된다.

import requests
from requests import adapters
with requests.Session() as s:
    s.mount('https://', adapters.HTTPAdapter(max_retries = 10))
    s.mount('http://', adapters.HTTPAdapter(max_retries = 10))

그러나 이번에는 단순 횟수 설정이 아닌 requests 개발자가 제안하는 재시도 방법을 어떻게 하는 것인지 알아볼 것이다.
공식 문서에서 제안하는 방법은 파이썬 urllib3 모듈에서 util.Retry 명령을 통해 요청 실패시 재시도할 조건을 설정하는 방법이다.
urllib3와 requests 모듈과 함께 사용한다면 다음과 같이 적용한다.

코드

import requests
from requests import adapters
from urllib3 import util
with requests.Session() as s:
    retries = util.Retry(
        total: bool | int | None = 10,
        connect: int | None = None,
        read: int | None = None,
        redirect: bool | int | None = None,
        status: int | None = None,
        other: int | None = None,
        allowed_methods: Collection[str] | None = DEFAULT_ALLOWED_METHODS, DEFAULT_ALLOWED_METHODS = frozenset({'DELETE', 'GET', 'HEAD', 'OPTIONS', 'PUT', 'TRACE'})
        status_forcelist: Collection[int] | None = None,
        backoff_factor: float = 0,
        backoff_max: float = DEFAULT_BACKOFF_MAX,
        raise_on_redirect: bool = True,
        raise_on_status: bool = True,
        history: tuple[RequestHistory, ...] | None = None,
        respect_retry_after_header: bool = True,
        remove_headers_on_redirect: Collection[str] = DEFAULT_REMOVE_HEADERS_ON_REDIRECT,
        backoff_jitter: float = 0
    )
    s.mount('https://', adapters.HTTPAdapter(max_retries = retries))
    s.mount('http://', adapters.HTTPAdapter(max_retries = retries))

각 인수별 설명

total

요청 실패시 재시도할 횟수. 기본값은 10이다.

connect, read

connect error, read error시 재시도할 횟수. 쉽게 말하자면 time out시 재시도할 횟수이다.
재시도 중 connect 또는 read error를 인정할 횟수를 지정할 뿐이지, 재시도 횟수를 조정하는 인수는 아니다.

만약 total = 3, connect = 2라면 최대 2번의 재시도를 수행하게 되고,
total = 3, connect = 4라면 최대 3번의 재시도를 수행한다.

기본값은 None이며, None으로 설정한 경우 total = connect = read 와 같은 설정이 적용된다.

예시

# total only
import requests
from requests import adapters
from urllib3 import util
import time
with requests.Session() as s:
    retries = util.Retry(
        total = 3,
        backoff_factor = 10
    )
    s.mount('https://', adapters.HTTPAdapter(max_retries = retries))
    t = time.time()
# white.seolpyo.com
    try:
        r = s.get('https://httpstat.us/200?sleep=4000', timeout = 1)
        print(r.status_code)
    except Exception as e:
        print(e)
    print(time.time() - t)

>> HTTPSConnectionPool(host='httpstat.us', port=443): Max retries exceeded with url: /200?sleep=4000 (Caused by ReadTimeoutError("HTTPSConnectionPool(host='httpstat.us', port=443): Read timed out. (read timeout=1)"))
66.14204597473145

# set read
import requests
from requests import adapters
from urllib3 import util
import time
with requests.Session() as s:
    retries = util.Retry(
        total = 3,
        read = 1,
        backoff_factor = 10
    )
    s.mount('https://', adapters.HTTPAdapter(max_retries = retries))
    t = time.time()
# white.seolpyo.com
    try:
        r = s.get('https://httpstat.us/200?sleep=4000', timeout = 1)
        print(r.status_code)
    except Exception as e:
        print(e)
    print(time.time() - t)

>> HTTPSConnectionPool(host='httpstat.us', port=443): Max retries exceeded with url: /200?sleep=4000 (Caused by ReadTimeoutError("HTTPSConnectionPool(host='httpstat.us', port=443): Read timed out. (read timeout=1)"))
3.1001927852630615

redircet

다음과 같은 설명으로 제공되는데, redirect 에러시 재시도를 수행할 회수를 뜻하는 듯..
적용 방식은 conncet, read와 비슷할 것으로 예상된다.

How many redirects to perform. Limit this to avoid infinite redirect loops.

status, status_forcelist

재시도를 요청할 응답 상태코드와 리스트.
status는 1개의 상태코드를, status_forcelist는 상태코드가 들어있는 list를 인수로 받는다.

만약 status_forcelist = [500, 504]일 경우 상태코드 404를 받았다면 재시도를 하지 않는다.

예시

# status in force_list
import requests
from requests import adapters
from urllib3 import util
import time
# white.seolpyo.com
with requests.Session() as s:
    retries = util.Retry(
        total = 3,
        status_forcelist = [502],
        backoff_factor = 3
    )
    s.mount('https://', adapters.HTTPAdapter(max_retries = retries))
    t = time.time()
    try:
        r = s.get('https://httpstat.us/502')
        print(r.status_code)
    except Exception as e:
        print(e)
    print(time.time() - t)

>> HTTPSConnectionPool(host='httpstat.us', port=443): Max retries exceeded with url: /502 (Caused by ResponseError('too many 502 error responses'))
19.433527946472168

# status not in force_list
import requests
from requests import adapters
from urllib3 import util
import time
# white.seolpyo.com
with requests.Session() as s:
    retries = util.Retry(
        total = 3,
        status_forcelist = [500],
        backoff_factor = 3
    )
    s.mount('https://', adapters.HTTPAdapter(max_retries = retries))
    t = time.time()
    try:
        r = s.get('https://httpstat.us/502')
        print(r.status_code)
    except Exception as e:
        print(e)
    print(time.time() - t)

>> 502
0.6870925426483154

other

인수로 제공되는 것들 외에 재시도를 수행할 이유를 전달하는 목적으로 제공되는 인수.

allowed_methods

재시도를 할 요청 방법.
allowed_methods = ['GET']인데 post 요청을 한 경우, 재시도를 하지 않는다.

backoff_factor

재시도 전 time.sleep할 시간.

다음 계산공식을 따라 계산된 시간을 적용한다.
backoff_factor * (2 ** (consecutive_errors_len - 1)) = {입력값} * (2^({남은 재시도 횟수} - 1))

backoff_max

재시도전 time.sleep할 시간의 최대치.
backoff_factor가 backoff_max보다 크다면 backoff_max만큼의 시간을 time.sleep한다.

기본값은 120으로 되어있다.

backoff_jitter

random.random() * backoff_jitter의 시간만큼 time.sleep한다.

backoff_jitter의 값이 0이 아닌 경우 backoff_factor의 값은 무시하지만, 계산된 값이 backoff_max보다 클 경우 backoff_max가 적용된다.

rasise_on_status, rasise_on_redirect

재시도에도 요청에 실패하는 경우 상태코드를 반환할지, raise할지 선택하는 인수.
rasise_on_status는 status 또는 status_forcelist에 속한 상태코드를 그대로 반환할지를,
rasise_on_redirect는 redicrect 응답을 반환할지 raise할지를 선택하는 인수이다.

True 시 raise, False시 응답을 return한다.

예시

# set raise
import requests
from requests import adapters
from urllib3 import util
import time
with requests.Session() as s:
    retries = util.Retry(
        total = 3,
        status_forcelist = [502],
        backoff_factor = 3,
        raise_on_status = True
    )
# white.seolpyo.com
    s.mount('https://', adapters.HTTPAdapter(max_retries = retries))
    t = time.time()
    try:
        r = s.get('https://httpstat.us/502')
        print(r.status_code)
    except Exception as e:
        print(e)
    print(time.time() - t)

>> HTTPSConnectionPool(host='httpstat.us', port=443): Max retries exceeded with url: /502 (Caused by ResponseError('too many 502 error responses'))
19.26778817176819

# set return
import requests
from requests import adapters
from urllib3 import util
import time
with requests.Session() as s:
    retries = util.Retry(
        total = 3,
        status_forcelist = [502],
        backoff_factor = 3,
        raise_on_status = False
    )
# white.seolpyo.com
    s.mount('https://', adapters.HTTPAdapter(max_retries = retries))
    t = time.time()
    try:
        r = s.get('https://httpstat.us/502')
        print(r.status_code)
    except Exception as e:
        print(e)
    print(time.time() - t)

>> 502
19.258716344833374

history

다음과 같은 설명이 있었는데 이해하지 못했다.
설명만 봐서는 재시도를 하는 동안의 기록이라는 것 같은데, 이런건 return 객체에 있었다면 이게 맞긴한데, 이건 인수로 제공되고 있다.

The history of the request encountered during each call to increment(). The list is in the order the requests occurred. Each list item is of class RequestHistory.

respect_retry_after_header

다음과 같은 설명이 있었지만 이해하지 못했다.
요청시 설정한 헤더를 적용할지, 응답받은 헤더를 적용할지 정하는 것 같다.

Whether to respect Retry-After header on status codes defined as Retry.RETRY_AFTER_STATUS_CODES or not.

remove_headers_on_redirect

redirect가 이루어진 경우 헤더에서 제거할 키값??
다음과 같은 설명이 있었는데, 기본값이 frozenset(["Authorization"])으로 설정되어있는 것을 봐서는 제거할 헤더의 key list를 전달하라는 것 같다.

Sequence of headers to remove from the request when a response indicating a redirect is returned before firing off the redirected request.


공감 : 0







white.seolpyo.com