Skip to content

HTTPAdapter, Tenacity, 및 커스텀 로직을 사용한 リトライ 전략으로 Python에서 실패한 HTTP リクエスト를 처리하여 Webスクレイピングの信頼性を向上させます。

Notifications You must be signed in to change notification settings

bright-kr/manage-failed-python-requests

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 

Repository files navigation

Python에서 실패한 리クエスト 관리하기

Promo

이 가이드는 리トライ 전략과 커스텀 로직을 사용하여 Python에서 실패한 HTTP 리クエスト를 처리하는 방법을 설명합니다.

What Are Status Codes?

상태 코드는 여러 프로토콜에서 리クエスト의 결과를 나타내기 위해 사용되는 표준화된 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 업스트림 서버를 기다리다 タイムアウト되었습니다

Retry Strategies

Python에서 리ト라이 메커니즘을 구현할 때 HTTPAdapterTenacity 같은 사전 구축 라이브러리를 활용할 수 있습니다. 또는 특정 요구 사항에 따라 커스텀 리ト라이 로직을 개발할 수도 있습니다.

잘 설계된 리ト라이 전략에는 리ト라이 한도와 백오프(backoff) 메커니즘이 모두 포함되어야 합니다. 리ト라이 한도는 무한 루프를 방지하여 실패한 リクエスト가 무기한 계속되지 않도록 합니다. 리ト라이 간 지연 시간을 점진적으로 늘리는 백오프 전략은 차단되거나 서버에 과부하를 주는 과도한 リクエ스트를 방지하는 데 도움이 됩니다.

  • 리ト라이 한도: 리ト라이 한도를 정의하는 것이 필수적입니다. 지정된 시도 횟수(X) 이후에는 무한 루프를 피하기 위해 scraper가 리ト라이를 중단해야 합니다.
  • 백오프 알고리즘: 리ト라이 간 대기 시간을 점진적으로 증가시키면 서버에 과도한 부하를 주지 않게 됩니다. 0.3초와 같은 작은 지연으로 시작한 뒤 0.6초, 1.2초 등으로 점진적으로 늘리십시오.

HTTPAdapter

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 fail

Session 객체를 생성한 뒤에는 다음을 수행하십시오:

  • 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

Tenacity

또한 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[...]

Building a Custom Retry Mechanism

또한 커스텀 리트라이 메커니즘을 만들 수도 있으며, 이는 전문화된 코드로 작업할 때 종종 최선의 접근 방식입니다. 비교적 적은 코드만으로도 기존 라이브러리가 제공하는 것과 동일한 기능을 구현하면서, 특정 요구 사항에 맞게 조정할 수 있습니다.

아래 코드는 지수 백오프를 위해 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_codebad_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.

Conclusion

각종 실패한 리クエスト를 방지하기 위해, 저희는 Web Unlocker APIScraping Browser와 같은 제품을 개발했습니다. 이러한 도구는 アンチボット 조치, CAPTCHA 챌린지, IP 차단을 자동으로 처리하여, 가장 까다로운 웹사이트에서도 끊김 없이 효율적인 Webスク레イピング을 보장합니다.

지금 가입하고 오늘 무료 체험을 시작하십시오.

About

HTTPAdapter, Tenacity, 및 커스텀 로직을 사용한 リトライ 전략으로 Python에서 실패한 HTTP リクエスト를 처리하여 Webスクレイピングの信頼性を向上させます。

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published