이 가이드는 리トライ 전략과 커스텀 로직을 사용하여 Python에서 실패한 HTTP 리クエスト를 처리하는 방법을 설명합니다.
상태 코드는 여러 프로토콜에서 리クエスト의 결과를 나타내기 위해 사용되는 표준화된 3자리 숫자입니다. Mozilla에 따르면 HTTP 상태 코드는 다음과 같은 범주로 나눌 수 있습니다:
- 100-199: 정보성 レスポンス
- 200-299: 성공 レスポンス
- 300-399: 리디렉션 메시지
- 400-499: 클라이언트 오류 메시지
- 500-599: 서버 오류 메시지
Webスクレ이ピング과 같은 클라이언트 측 애플리케이션을 개발할 때에는 400 및 500 범위의 상태 코드에 주의하는 것이 매우 중요합니다. 400대 코드는 일반적으로 인증 실패, レート制限, タイムアウト 또는 잘 알려진 404: Not Found error 와 같은 클라이언트 측 오류를 나타냅니다. 반면 500대 상태 코드는 리トライ 또는 대체 처리 전략이 필요할 수 있는 서버 측 문제를 의미합니다.
다음은 Webスクレ이ピング을 수행할 때 마주치게 되는 일반적인 오류 코드 목록입니다(Mozilla의 official documentation에서 발췌):
| Status Code | Meaning | Description |
|---|---|---|
| 400 | Bad Request | 리クエ스트 형식을 확인하십시오 |
| 401 | Unauthorized | API key를 확인하십시오 |
| 403 | Forbidden | 이 데이터에 접근할 수 없습니다 |
| 404 | Not Found | 사이트/エンドポイント가 존재하지 않습니다 |
| 408 | Request Timeout | リクエスト가 タイムアウト되었습니다. 다시 시도하십시오 |
| 429 | Too Many Requests | リクエスト 속도를 줄이십시오 |
| 500 | Internal Server Error | 일반적인 서버 오류입니다. リクエスト를 リトライ하십시오 |
| 501 | Not Implemented | 서버가 아직 이를 지원하지 않습니다 |
| 502 | Bad Gateway | 업스트림 서버에서 レスポンス 수신에 실패했습니다 |
| 503 | Service Unavailable | 서버가 일시적으로 다운되었습니다. 나중에 リトライ하십시오 |
| 504 | Gateway Timeout | 업스트림 서버를 기다리다 タイムアウト되었습니다 |
Python에서 리ト라이 메커니즘을 구현할 때 HTTPAdapter 및 Tenacity 같은 사전 구축 라이브러리를 활용할 수 있습니다. 또는 특정 요구 사항에 따라 커스텀 리ト라이 로직을 개발할 수도 있습니다.
잘 설계된 리ト라이 전략에는 리ト라이 한도와 백오프(backoff) 메커니즘이 모두 포함되어야 합니다. 리ト라이 한도는 무한 루프를 방지하여 실패한 リクエスト가 무기한 계속되지 않도록 합니다. 리ト라이 간 지연 시간을 점진적으로 늘리는 백오프 전략은 차단되거나 서버에 과부하를 주는 과도한 リクエ스트를 방지하는 데 도움이 됩니다.
- 리ト라이 한도: 리ト라이 한도를 정의하는 것이 필수적입니다. 지정된 시도 횟수(X) 이후에는 무한 루프를 피하기 위해 scraper가 리ト라이를 중단해야 합니다.
- 백오프 알고리즘: 리ト라이 간 대기 시간을 점진적으로 증가시키면 서버에 과도한 부하를 주지 않게 됩니다. 0.3초와 같은 작은 지연으로 시작한 뒤 0.6초, 1.2초 등으로 점진적으로 늘리십시오.
HTTPAdapter를 사용하려면 total, backoff_factor, status_forcelist의 세 가지를 구성해야 합니다. allowed_methods는 엄밀히 말해 필수는 아니지만, 리ト라이 조건을 정의하는 데 도움이 되므로 코드를 더 안전하게 만듭니다. 아래 코드에서는 오류를 자동으로 강제하여 리ト라이 로직을 트리거하기 위해 httpbin을 사용합니다.
import logging
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Create a session
session = requests.Session()
# Configure retry settings
retry = Retry(
total=3, # Maximum retries
backoff_factor=0.3, # Time between retries (exponential backoff)
status_forcelist=(429, 500, 502, 503, 504), # Status codes to trigger a retry
allowed_methods={"GET", "POST"} # Allow retries for GET and POST
)
# Mount the adapter with our custom settings
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
# Function to make a request and test retry logic
def make_request(url, method="GET"):
try:
logger.info(f"Making a {method} request to {url} with retry logic...")
if method == "GET":
response = session.get(url)
elif method == "POST":
response = session.post(url)
else:
logger.error("Unsupported HTTP method: %s", method)
return
response.raise_for_status()
logger.info("✅ Request successful: %s", response.status_code)
except requests.exceptions.RequestException as e:
logger.error("❌ Request failed after retries: %s", e)
logger.info("Retries attempted: %d", len(response.history) if response else 0)
# Test Cases
make_request("https://httpbin.org/status/200") # ✅ Should succeed without retries
make_request("https://httpbin.org/status/500") # ❌ Should retry 3 times and fail
make_request("https://httpbin.org/status/404") # ❌ Should fail immediately (no retries)
make_request("https://httpbin.org/status/500", method="POST") # ❌ Should retry 3 times and failSession 객체를 생성한 뒤에는 다음을 수행하십시오:
Retry객체를 생성하고 다음을 정의합니다:total: リクエスト를 리ト라이하는 최대 한도입니다.backoff_factor: 리ト라이 사이에 대기할 시간입니다. 리ト라이 횟수가 증가함에 따라 지수적으로 조정됩니다.status_forcelist: 불량 상태 코드 목록입니다. 이 목록에 포함된 모든 코드는 자동으로 리트라이를 트리거합니다.
retry변수를 사용하여HTTPAdapter객체를 생성합니다:adapter = HTTPAdapter(max_retries=retry).adapter를 생성한 후session.mount()를 사용하여 HTTP 및 HTTPS 메서드에 마운트합니다.
이 코드를 실행하면 세 번의 리ト라이(total=3)가 수행된 다음 아래와 같은 출력이 표시됩니다.
2024-06-10 12:00:00 - INFO - Making a GET request to https://httpbin.org/status/200 with retry logic...
2024-06-10 12:00:00 - INFO - ✅ Request successful: 200
2024-06-10 12:00:01 - INFO - Making a GET request to https://httpbin.org/status/500 with retry logic...
2024-06-10 12:00:02 - ERROR - ❌ Request failed after retries: 500 Server Error: INTERNAL SERVER ERROR for url: ...
2024-06-10 12:00:02 - INFO - Retries attempted: 3
2024-06-10 12:00:03 - INFO - Making a GET request to https://httpbin.org/status/404 with retry logic...
2024-06-10 12:00:03 - ERROR - ❌ Request failed after retries: 404 Client Error: NOT FOUND for url: ...
2024-06-10 12:00:03 - INFO - Retries attempted: 0
2024-06-10 12:00:04 - INFO - Making a POST request to https://httpbin.org/status/500 with retry logic...
2024-06-10 12:00:05 - ERROR - ❌ Request failed after retries: 500 Server Error: INTERNAL SERVER ERROR for url: ...
2024-06-10 12:00:05 - INFO - Retries attempted: 3
또한 Python에서 널리 사용되는 오픈 소스 리ト라이 라이브러리인 Tenacity를 사용할 수도 있습니다. 이는 HTTP에만 국한되지 않으며, 리트라이를 구현하는 표현력 있는 방법을 제공합니다.
먼저 Tenacity를 설치하십시오:
pip install tenacity설치가 완료되면 decorator 를 만들고 이를 사용하여 requests 함수를 래핑합니다. @retry decorator에서 stop, wait, retry 인자를 추가하십시오.
import logging
import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type, retry_if_result, RetryError
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Define a retry strategy
@retry(
stop=stop_after_attempt(3), # Retry up to 3 times
wait=wait_exponential(multiplier=0.3), # Exponential backoff
retry=(
retry_if_exception_type(requests.exceptions.RequestException) | # Retry on request failures
retry_if_result(lambda r: r.status_code in {500, 502, 503, 504}) # Retry on specific HTTP status codes
),
)
def make_request(url):
logger.info("Making a request with retry logic to %s...", url)
response = requests.get(url)
response.raise_for_status()
logger.info("✅ Request successful: %s", response.status_code)
return response
# Attempt to make the request
try:
make_request("https://httpbin.org/status/500") # Test with a failing status code
except RetryError as e:
logger.error("❌ Request failed after all retries: %s", e) 여기의 로직과 설정은 HTTPAdapter를 사용한 첫 번째 예시와 매우 유사합니다:
stop=stop_after_attempt(3):tenacity가 3번의 실패한 리트라이 후에 포기하도록 지시합니다.wait=wait_exponential(multiplier=0.3)는 이전에 사용했던 것과 동일한 대기를 사용합니다. 이전과 마찬가지로 지수적으로 백오프합니다.retry=retry_if_exception_type(requests.exceptions.RequestException)는RequestException이 발생할 때마다 이 로직을 사용하도록tenacity에 지시합니다.make_request()는 오류 エンドポイント에 리クエ스트를 보냅니다. 이는 위에서 작성한 decorator의 모든 특성을 상속받습니다.
이 코드를 실행하면 유사한 출력이 표시됩니다:
2024-06-10 12:00:00 - INFO - Making a request with retry logic to https://httpbin.org/status/500...
2024-06-10 12:00:01 - WARNING - Retrying after 0.3 seconds...
2024-06-10 12:00:01 - INFO - Making a request with retry logic to https://httpbin.org/status/500...
2024-06-10 12:00:02 - WARNING - Retrying after 0.6 seconds...
2024-06-10 12:00:02 - INFO - Making a request with retry logic to https://httpbin.org/status/500...
2024-06-10 12:00:03 - ERROR - ❌ Request failed after all retries: RetryError[...]
또한 커스텀 리트라이 메커니즘을 만들 수도 있으며, 이는 전문화된 코드로 작업할 때 종종 최선의 접근 방식입니다. 비교적 적은 코드만으로도 기존 라이브러리가 제공하는 것과 동일한 기능을 구현하면서, 특정 요구 사항에 맞게 조정할 수 있습니다.
아래 코드는 지수 백오프를 위해 sleep을 import하고, 구성(total, backoff_factor, bad_codes)을 설정한 다음, while 루프를 사용하여 리트라이 로직을 유지하는 방법을 보여줍니다. 시도 횟수가 남아 있고 아직 성공하지 못했다면 リクエスト를 시도합니다.
import logging
import requests
from time import sleep
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Create a session
session = requests.Session()
# Define retry settings
TOTAL_RETRIES = 3
INITIAL_BACKOFF = 0.3
BAD_CODES = {429, 500, 502, 503, 504}
def make_request(url):
current_tries = 0
backoff = INITIAL_BACKOFF
success = False
while current_tries < TOTAL_RETRIES and not success:
try:
logger.info("Making a request with retry logic to %s...", url)
response = session.get(url)
if response.status_code in BAD_CODES:
raise requests.exceptions.HTTPError(f"Received {response.status_code}, triggering retry")
response.raise_for_status()
logger.info("✅ Request successful: %s", response.status_code)
success = True
return response
except requests.exceptions.RequestException as e:
logger.error("❌ Request failed: %s, retries left: %d", e, TOTAL_RETRIES - current_tries - 1)
if current_tries < TOTAL_RETRIES - 1:
logger.info("⏳ Retrying in %.1f seconds...", backoff)
sleep(backoff)
backoff *= 2 # Exponential backoff
current_tries += 1
logger.error("🚨 Request failed after all retries.")
return None
# Test Cases
make_request("https://httpbin.org/status/500") # ❌ Should retry 3 times and fail
make_request("https://httpbin.org/status/200") # ✅ Should succeed without retries여기의 실제 로직은 간단한 while 루프로 처리됩니다.
response.status_code가bad_codes목록에 있으면 스크립트가 예외를 발생시킵니다.- 리クエスト가 실패하면 스크립트는 다음을 수행합니다:
- 콘솔에 오류 메시지를 출력합니다.
sleep(backoff_factor)로 다음 リクエスト를 보내기 전에 대기합니다.backoff_factor = backoff_factor * 2로 다음 시도를 위한backoff_factor를 두 배로 늘립니다.current_tries를 증가시켜 루프가 무기한 유지되지 않도록 합니다.
다음은 커스텀 리ト라이 코드의 출력입니다.
2024-06-10 12:00:00 - INFO - Making a request with retry logic to https://httpbin.org/status/500...
2024-06-10 12:00:01 - ERROR - ❌ Request failed: Received 500, triggering retry, retries left: 2
2024-06-10 12:00:01 - INFO - ⏳ Retrying in 0.3 seconds...
2024-06-10 12:00:02 - INFO - Making a request with retry logic to https://httpbin.org/status/500...
2024-06-10 12:00:03 - ERROR - ❌ Request failed: Received 500, triggering retry, retries left: 1
2024-06-10 12:00:03 - INFO - ⏳ Retrying in 0.6 seconds...
2024-06-10 12:00:04 - INFO - Making a request with retry logic to https://httpbin.org/status/500...
2024-06-10 12:00:05 - ERROR - ❌ Request failed: Received 500, triggering retry, retries left: 0
2024-06-10 12:00:05 - ERROR - 🚨 Request failed after all retries.
각종 실패한 리クエスト를 방지하기 위해, 저희는 Web Unlocker API 및 Scraping Browser와 같은 제품을 개발했습니다. 이러한 도구는 アンチボット 조치, CAPTCHA 챌린지, IP 차단을 자동으로 처리하여, 가장 까다로운 웹사이트에서도 끊김 없이 효율적인 Webスク레イピング을 보장합니다.
지금 가입하고 오늘 무료 체험을 시작하십시오.
