Profile picture

[Python] 파이썬 requests 사용시 'RemoteDisconnected' 문제 해결하는 방법

JaehyoJJAng2024년 07월 06일

개요

Requests 라이브러리를 사용하여 데이터 수집 중 아래와 같은 에러 로그가 발생하였다.

Connection aborted.', RemoteDisconnected('Remote end closed connection without response')

이는 서버 측에서 연결을 갑자기 끊었을 때 발생하는 에러이다.


왜 요청이 멈추는 걸까?

위 에러는 일반적으로 다음과 같은 이유로 발생할 수 있다고 한다.

  • 서버의 요청 제한: 많은 서버가 짧은 시간에 다수의 요청을 보내는 클라이언트를 차단하도록하는 로직이 구현되어 있는 경우에 너무 빠른 빈도로 요청을 보내게 되면 서버로부터 차단 당할 수 있다.
  • HTTP 헤더 부족: 서버로부터 요청할 때 헤더가 부족하면, 특히 User-Agent가 설정되지 않은 경우 일부 서버는 크롤링을 차단하려고 연결을 닫을 수 있다.
  • SSL 인증서 문제: HTTPS로 요청할 때 SSL 인증서 확인 과정에서 문제가 발생하면 연결이 강제로 종료될 수 있다.
    • 이때에는 requests.get(url=url, verify=False)와 같이 인증서를 무시할 수 있지만 안전한 방법은 아니다.
  • 서버의 시간 초과: 서버가 일정 시간 동안 응답을 받지 못하면 연결을 종료할 수 있다.
    • 이때에는 requests.get(url=url, timeout=(3,5))처럼 타임아웃 값을 짧게 설정하는 것도 방법이다.

requests 모듈의 예외 처리와 재시도 로직

requests 모듈은 HTTP 요청 중 발생하는 다양한 예외를 제공한다.

이를 활용하여 오류 발생 시 재시도하는 로직을 구현해보자.

import requests
from requests.exceptions import RequestException
import time

def fetch_url(url, retries=3, delay=2):
    attempt = 0
    while attempt < retries:
        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()  # HTTP 오류가 있으면 예외 발생
            return response
        except RequestException as e:
            attempt += 1
            print(f"시도 {attempt}/{retries} 실패: {e}")
            if attempt < retries:
                time.sleep(delay)  # 다음 시도 전 대기
            else:
                print("최대 재시도 횟수에 도달했습니다. 이 URL을 건너뜁니다.")
                return None

# 사용 예시
url = "https://example.com"
response = fetch_url(url)
if response:
    print("요청 성공!")
    # 추가 작업 수행
else:
    print("재시도 후에도 요청에 실패했습니다.")

코드 설명

함수 매개변수

  • url: 요청할 웹 페이지의 URL
  • retries: 최대 재시도 횟수로, 기본값은 3회
  • delay: 재시도 전 대기 시간(초)으로, 기본값은 2초

예외 처리

  • try 블록: requests.get()을 사용하여 HTTP 요청을 보낸다.
    • timeout=10을 설정하여 10초 이상 응답이 없으면 Timeout 예외를 발생시키도록 하였다.
  • response.raise_for_status(): 응답 코드가 4xx 또는 5xx인 경우 HTTPError 예외를 발생시킨다.
  • except RequestException as e: requests에서 발생할 수 있는 모든 예외를 포괄한다.

재시도 로직

  • 요청이 실패할 때마다 attempt 변수를 증가시켜 현재 시도 횟수를 추적한다.
  • 최대 재시도 횟수에 도달하지 않았으면 지정된 delay 시간만큼 대기한 후 다음 요청을 시도한다.
  • 최대 횟수에 도달하면 None을 반환하여 호출 측에서 후속 조치를 취할 수 있게 하였음.

추가 개선 사항

백오프 전략 적용

재시도 시마다 대기 시간을 늘려서 서버에 과부하를 주지 않도록 할 수도 있다.

def fetch_url(url, retries=3, delay=2, backoff=2):
    attempt = 0
    current_delay = delay
    while attempt < retries:
        try:
            # ... (생략)
        except RequestException as e:
            # ... (생략)
            time.sleep(current_delay)
            current_delay *= backoff  # 대기 시간 증가

특정 예외에 대한 처리

어떤 예외는 재시도해도 소용없을 수 있으므로, 예외 유형에 따라 다른 처리를 할 수도 있다.

from requests.exceptions import HTTPError, Timeout, ConnectionError

try:
    # ... (생략)
except Timeout:
    # 타임아웃 예외에 대한 재시도
except HTTPError as e:
    if e.response.status_code == 404:
        # 404 오류는 재시도하지 않음
        return None
    else:
        # 다른 HTTP 오류는 재시도

Loading script...