HTTP를 다룰 때 실패한 요청은 피할 수 없는 현실이며 반드시 처리해야 합니다. 웹 개발에서 상태 코드 200은 정상 응답을 의미합니다. 그러나 항상 200을 받는 것은 아니며, 이 가이드는 200이 아닌 상태 코드를 처리하는 방법을 이해하는 데 도움이 될 것입니다.
Mozilla에 따르면 상태 코드는 다음과 같은 범주로 분류됩니다:
- 100-199: 정보성 응답
- 200-299: 성공 응답
- 300-399: 리다이렉션 메시지
- 400-499: 클라이언트 오류 메시지
- 500-599: 서버 오류 메시지
상태 코드란 무엇인가?
오류 코드는 중요합니다. 웹 스크레이퍼와 같은 클라이언트 측 프로그램을 구축할 때는 주로 400+ 및 500+ 범위의 상태 코드에 집중해야 합니다. 400번대 코드는 일반적으로 인증 문제, 속도 제한, 시간 초과, 그리고 악명 높은 404: 파일 미검출 오류와 같은 클라이언트 측 오류를 다룹니다. 500번대에서는 일반적으로 서버 문제를 살펴봅니다.
수십 년간 모질라는 W3C와 IETF의 웹 개발 표준을 문서화해 왔습니다. 아래는 자주 접할 수 있는 일반적인 오류 코드 목록입니다. 이 목록은 완전하지 않습니다. 이 오류들은 모질라의 공식 문서에서 가져온 것입니다. 대상 사이트에 따라 코드가 약간 다를 수 있지만 논리는 동일해야 합니다.
| 상태 코드 | 의미 | 설명 |
|---|---|---|
| 400 | 잘못된 요청 | 요청 형식을 확인하십시오 |
| 401 | Unauthorized | API 키를 확인하세요. |
| 403 | 금지됨 | 이 데이터에 액세스할 수 없습니다. |
| 404 | 찾을 수 없음 | 사이트/엔드포인트가 존재하지 않습니다 |
| 408 | 요청 시간 초과 | 요청 시간 초과, 다시 시도하십시오 |
| 429 | 요청이 너무 많습니다 | 요청 속도를 늦추십시오 |
| 500 | 내부 서버 오류 | 일반적인 서버 오류, 요청을 다시 시도하십시오. |
| 501 | 구현되지 않음 | 서버가 아직 이를 지원하지 않음 |
| 502 | Bad Gateway | 업스트림 서버에서 응답 실패 |
| 503 | 서비스를 사용할 수 없음 | 서버가 일시적으로 다운되었습니다. 나중에 다시 시도하세요. |
| 504 | 게이트웨이 시간 초과 | 업스트림 서버 대기 중 시간 초과 |
재시도 전략
재시도 메커니즘을 구현할 때는 HTTPAdapter나 Tenacity 같은 사전 구축된 라이브러리를 사용할 수 있습니다. 상황에 따라 자체 재시도 로직을 작성해야 할 수도 있습니다.
일반적으로 재시도 횟수 제한과 백오프 전략이 필요합니다. 무한 재시도 루프에 빠지지 않도록 제한이 필요하며, 호스트 서버를 존중하기 위해 점진적으로 속도를 늦춰야 합니다. 요청이 너무 빠르게 전송되면 차단되거나 서버에 과부하가 걸릴 수 있습니다.
- 재시도 제한: 제한을 설정해야 합니다. X회 재시도 후 스크레이퍼는 포기합니다.
- 백오프 알고리즘: 비교적 간단합니다. 작은 백오프 간격으로 시작하여 재시도할 때마다 점진적으로 증가시킵니다. 0.3초로 시작한 후 0.6초, 1.2초 등으로 증가시키는 식입니다.
요청은 특정 한계까지 재시도합니다. 실패한 요청 후에는 조금 더 긴 대기 시간을 두어야 합니다.
HTTPAdapter
HTTPAdapter를 사용할 때는 총 재시도 횟수( total), 백오프 계수(backoff_factor), 강제 상태 리스트( status_forcelist) 세 가지를 설정해야 합니다. 허용된 메서드(allowed_methods)는 필수 사항은 아니지만, 재시도 조건을 정의하는 데 도움을 주어 코드를 더 안전하게 만듭니다. 아래 코드에서는 httpbin을 사용하여 자동으로 오류를 발생시키고 재시도 로직을 트리거합니다.
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# 세션 생성
session = requests.Session()
# 재시도 설정 구성
retry = Retry(
total=3, # 최대 재시도 횟수
backoff_factor=0.3, # 재시도 간격 (지수적 백오프)
status_forcelist=(429, 500, 502, 503, 504), # 재시도를 유발하는 상태 코드
allowed_methods={"GET", "POST"}
)
# 사용자 정의 설정으로 어댑터 마운트
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
# 재시도 로직 적용하여 실제 요청 수행
try:
print("재시도 로직 적용하여 요청 중...")
response = session.get("https://httpbin.org/status/500")
response.raise_for_status()
print("✅ 요청 성공:", response.status_code)
except requests.exceptions.RequestException as e:
print("❌ 재시도 후 요청 실패:", e)
세션 객체를 생성한 후에는 다음과 같이 진행합니다:
Retry객체를 생성하고 다음을 정의합니다:total: 요청 재시도 최대 횟수.backoff_factor: 재시도 간 대기 시간. 재시도 횟수가 증가함에 따라 지수적으로 조정됩니다.status_forcelist: 불량 상태 코드 목록. 이 목록에 포함된 코드는 자동으로 재시도를 트리거합니다.
리트라이변수를 사용하여HTTPAdapter객체를 생성합니다:adapter = HTTPAdapter(max_retries=retry).어댑터를생성한 후session.mount()를 사용하여 HTTP 및 HTTPS 메서드에 어댑터를 마운트합니다.
이 코드를 실행하면총 3회의 재시도가 수행된 후 다음과 같은 결과가 출력됩니다.
재시도 로직으로 요청 중...
❌ 재시도 후 요청 실패: HTTPSConnectionPool(host='httpbin.org', port=443): url: /status/500에서 최대 재시도 횟수 초과 (Caused by ResponseError('too many 500 error responses'))
Tenacity
Python용 인기 오픈소스 재시도 라이브러리인 Tenacity를 사용할 수도 있습니다. HTTP에만 국한되지는 않지만, 재시도를 구현하는 데 표현력이 풍부하고 이해하기 쉬운 방법을 제공합니다.
먼저 설치해야 합니다.
pip install tenacity
설치 후, 데코레이터를 생성하고 이를 요청 함수를 감싸는 데 사용합니다. @retry 데코레이터를 통해 중지( stop), 대기(wait), 재시도 ( retry ) 인자를 추가합니다.
import requests
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type, RetryError
# 재시도 전략 정의
@retry(
stop=stop_after_attempt(3), # 최대 3회 재시도
wait=wait_exponential(multiplier=0.3), # 지수적 백오프
retry=retry_if_exception_type(requests.exceptions.RequestException), # 요청 실패 시 재시도)
def make_request():
print("재시도 로직으로 요청 중...")
response = requests.get("https://httpbin.org/status/500")
response.raise_for_status()
print("✅ 요청 성공:", response.status_code)
return response
# 요청 시도
try:
make_request()
except RetryError as e:
print("❌ 모든 재시도 후 요청 실패:", e)
여기서의 로직과 설정은 HTTPAdapter를 사용한 첫 번째 예제와 매우 유사합니다.
stop=stop_after_attempt(3): 재시도 3회 실패 후 포기하도록tenacity에지시합니다.wait=wait_exponential(multiplier=0.3)은 이전에 사용했던 것과 동일한 대기 시간을 사용합니다. 또한 이전과 마찬가지로 지수적으로 대기 시간을 늘립니다.retry=retry_if_exception_type(requests.exceptions.RequestException)은RequestException이발생할 때마다 이 로직을 사용하도록tenacity에지시합니다.make_request()는오류 엔드포인트에 요청을 생성합니다. 이 함수는 상단에 생성한 데코레이터의 모든 속성을 상속받습니다.
이 코드를 실행하면 유사한 출력이 나타납니다.
재시도 로직으로 요청 중...
재시도 로직으로 요청 중...
재시도 로직으로 요청 중...
❌ 모든 재시도 후 요청 실패: RetryError[<Future at 0x75e762970760 state=finished raised HTTPError>]
자체 재시도 메커니즘 구축
사용자 정의 재시도 메커니즘을 직접 구축할 수도 있습니다. 맞춤형 코드를 다룰 때는 이 방법이 종종 최선의 접근법이 됩니다. 비교적 적은 양의 코드로 이러한 라이브러리에서 얻는 것과 동일한 효과를 달성할 수 있습니다.
아래 코드에서는 지수적 백오프를 위해 sleep을 임포트해야 합니다. 다시 한번 총 시도 횟수( total), 백오프 계수(backoff_factor), 오류 코드(bad_codes)를 설정합니다. 그런 다음 while 루프를 사용해 재시도 로직을 구현합니다. 시도 횟수가 남아 있고 성공하지 못한 동안 요청을 계속 시도합니다.
import requests
from time import sleep
# 세션 생성
session = requests.Session()
# 재시도 설정 정의
total = 3
backoff_factor = 0.3
bad_codes = [429, 500, 502, 503, 504]
# 시도 횟수 카운터와 성공 여부 부울
current_tries = 0
success = False
# 성공하거나 시도 횟수가 다할 때까지 시도
while current_tries < total and not success:
try:
print("재시도 로직으로 요청 중...")
response = session.get("https://httpbin.org/status/500")
if response.status_code in bad_codes:
raise requests.exceptions.HTTPError(f"{response.status_code} 상태 코드 수신, 재시도 트리거")
print("✅ 요청 성공:", response.status_code)
success = True
except requests.exceptions.RequestException as e:
print(f"❌ 요청 실패: {e}, 남은 재시도 횟수: {total-current_tries}")
sleep(backoff_factor)
backoff_factor = backoff_factor * 2
current_tries+=1
여기서 실제 로직은 간단한 while 루프로 처리됩니다.
response.status_code가bad_codes목록에 있으면 예외를 발생시킵니다.- 요청이 실패하면 다음을 수행합니다:
- 콘솔에 오류 메시지를 출력합니다.
sleep(backoff_factor)는다음 요청을 보내기 전에 대기합니다.backoff_factor = backoff_factor * 2다음 시도를 위해backoff_factor를두 배로 늘립니다.- 무한 루프에 빠지지 않도록
current_tries를증가시킵니다.
사용자 정의 재시도 로직의 출력 결과입니다.
재시도 로직으로 요청 중... ❌ 요청 실패: 500 오류 수신, 재시도 트리거됨, 남은 재시도 횟수: 3
❌ 요청 실패: 500 오류 수신, 재시도 트리거, 남은 재시도 횟수: 3
재시도 로직으로 요청 중...
❌ 요청 실패: 500 오류 수신, 재시도 트리거, 남은 재시도 횟수: 2
재시도 로직으로 요청 중...
❌ 요청 실패: 500 오류 수신, 재시도 트리거, 남은 재시도 횟수: 1
차단 우회하기
실제 환경에서는 일부 사이트가 사용자를 차단할 수 있습니다. Python 요청 시 항상 프록시를 사용하는 것이 최선의 방법입니다. 프록시를 사용하면 요청이 다른 기기를 통해 라우팅됩니다. 이는 신원을 보호하고 대상 사이트에 의해 IP 주소가 차단되는 것을 방지합니다. IP 차단 극복에 대한 상세 가이드도 제공합니다. 당사의 주거용 프록시는 이러한 문제를 해결하도록 설계되었습니다.
결론
이제 Python에서 실패한 HTTP 요청을 처리하는 방법을 알게 되었습니다. 스크레이퍼, API 클라이언트 또는 자동화 도구를 작성하든, 이러한 문제를 처리하는 방법을 알게 되었습니다. 모든 종류의 실패한 요청을 피하기 위해, 저희는 Web Unlocker API 및 Scraping Browser와 같은 제품을 개발했습니다. 이러한 도구들은 자동으로 봇 방지 조치, CAPTCHA 챌린지 및 IP 차단을 처리하여 가장 까다로운 웹사이트에서도 원활하고 효율적인 웹 스크래핑을 보장합니다.
지금 가입하여 무료 체험을 시작하세요.