Requests에서 ProxyError, SSLError 및 ConnectTimeout 해결하기
requests.exceptions.ProxyError는 증상일 뿐, 원인이 아닙니다. 여기서는 예외 계층 구조를 해독하고, 각 traceback을 실제 해결책과 매칭하며, 실패를 분류하고 죽은 출구를 회전하는 건강 검사 기능을 제공합니다.
requests.exceptions.ProxyError는 Python에서 가장 도움이 되지 않는 오류 메시지 중 하나입니다: 죽은 프록시, 잘못된 자격 증명, 잘못된 스킴, 과부하된 출구 및 방화벽 차단에 대해 거의 동일한 traceback을 발생시킵니다. 빠르게 해결하는 요령은 ProxyError가 근본 원인이 아니라는 것을 아는 것입니다 — 이는 범주입니다. Requests 소스에서 ProxyError, SSLError 및 ConnectTimeout은 모두 ConnectionError의 하위 클래스이며, 각각은 요청 수명 주기의 특정 단계에서 발생합니다. 단계를 읽으면 원인을 읽을 수 있습니다. 이 가이드는 예외 계층 구조를 해독하고, 일반적인 traceback을 실제 해결책과 매칭하며, 실패를 분류하고 자동으로 죽어가는 출구를 회전하는 건강 검사 기능을 제공합니다.
Requests 예외 계층 구조
Requests의 모든 프록시 관련 오류는 RequestException에서 파생됩니다. 디버깅에 유용한 분기는 ConnectionError입니다. 실제로 발생하는 세 가지 예외가 모두 그 아래에 있기 때문입니다:
- ProxyError — 프록시에 자체적으로 연결이 실패할 때 발생: 도달할 수 없는 호스트, 잘못된 포트, 거부된 연결 또는 거부된
407. 메시지는 종종HTTPSConnectionPool(...)래퍼 내부에Cannot connect to proxy를 중첩합니다. - SSLError — 프록시에 연결되었지만 대상과의 TLS 핸드셰이크가 실패했습니다. 프록시 설정에서 일반적인 트리거는
https키 안에https://가 아닌http://로 작성된 것입니다. - ConnectTimeout — 프록시가 연결 창 내에 응답하지 않았습니다. 이는
ConnectionError와Timeout모두의 하위 클래스이며, 명시적으로 재시도해도 안전하다고 문서화되어 있습니다. - ReadTimeout — 프록시가 연결되어 요청을 전달했지만 대상이 응답하기에 너무 느렸습니다. 이는 구성 버그가 아닌 대상 또는 출구 품질 문제입니다.
- MissingSchema / InvalidProxyURL — 프록시 URL이 잘못된 경우 네트워크 호출 전에 발생합니다. 이는 순수한 오타입니다.
첫 세 가지가 부모를 공유하기 때문에, 하나의 except requests.exceptions.ConnectionError가 모든 것을 재시도 로직으로 포착합니다 — 하위 클래스를 개별적으로 포착하면 각 실패 이유를 기록할 수 있습니다. 이러한 대부분을 피하는 깨끗한 설정은 우리의 Python Requests 프록시 가이드에 다루어져 있습니다; 이 게시물은 traceback이 이미 화면에 나타났을 때 해야 할 일에 관한 것입니다.
어떤 습관이 단일 해결책보다 더 많은 시간을 절약합니다: traceback을 아래에서 위로 읽으세요. Requests는 기본 urllib3 실패를 래핑하므로, 상위 프레임은 호출이 어디서 이루어졌는지를 설명하고 하위 프레임은 무엇이 잘못되었는지를 설명합니다. 원하는 줄은 가장 안쪽의 Caused by 절입니다 — 이는 외부 ProxyError 또는 ConnectionError가 단순히 다시 발생시키는 구체적인 실패(거부된 연결, 인증서 불일치, 구문 분석 오류)를 명명합니다. 그 줄을 읽을 수 있게 되면, 이 가이드의 나머지는 조회표입니다.
ProxyError 이전의 고전적인 ValueError
가장 많이 검색된 프록시 traceback은 ProxyError가 아닙니다 — 그것은 ValueError: invalid literal for int() with base 10이며, urllib3 깊숙이에서 발생합니다. 이는 스킴 없이 프록시 값에 자격 증명을 포함할 때 발생하며, 파서가 콜론 뒤의 텍스트를 포트 번호로 읽습니다:
# Broken — no scheme, so 'pass@host' is parsed as host:port
proxies = {"https": "user:pass@45.11.22.33:8000"}
# -> ValueError: invalid literal for int() with base 10: 'pass@45.11.22.33'
# Fixed — scheme in front, password URL-encoded if it has @ : or /
from urllib.parse import quote
pw = quote("p@ss:word", safe="")
proxies = {
"http": f"http://user:{pw}@gate.quantumproxies.io:PORT",
"https": f"http://user:{pw}@gate.quantumproxies.io:PORT",
}

실패를 분류하는 프록시 건강 검사
추측 대신 각 예외 유형을 포착하고 이를 평범한 영어 판결로 변환하세요. 이 함수는 성공 시 출구 IP를 반환하고 실패 시 라벨이 붙은 이유를 반환합니다 — 스크랩을 시작하기 전에 프록시가 살아 있는지 확인하기 위해 이를 사용하세요. 이는 주거용 프록시를 포함한 모든 인증 게이트웨이에 대해 작동합니다:
import requests
def check_proxy(proxies, url="https://httpbin.org/ip", timeout=(5, 20)):
try:
r = requests.get(url, proxies=proxies, timeout=timeout)
r.raise_for_status()
return True, r.json().get("origin")
except requests.exceptions.ProxyError as e:
return False, f"proxy unreachable or auth rejected: {e}"
except requests.exceptions.SSLError as e:
return False, f"TLS failed (https:// in the https key?): {e}"
except requests.exceptions.ConnectTimeout:
return False, "proxy did not answer within the connect window"
except requests.exceptions.ReadTimeout:
return False, "target too slow after connect (exit quality)"
except requests.exceptions.RequestException as e:
return False, f"other request error: {e}"
ok, detail = check_proxy(proxies)
print("OK" if ok else "FAIL", detail)
curl은 작동하지만 Python은 ProxyError를 발생시키는 경우
같은 자격 증명이 curl에서는 성공하지만 Python에서는 ProxyError를 발생시키는 경우, 환경 변수가 거의 항상 당신의 딕셔너리를 재정의하고 있습니다. Requests는 셸에서 HTTP_PROXY, HTTPS_PROXY, NO_PROXY를 읽으며, 오래된 기업 값이 모든 호출을 조용히 재라우팅합니다. 실제로 사용 중인 것을 보려면 session.proxies를 출력한 다음 환경 조회를 완전히 비활성화하세요:
import requests
session = requests.Session()
session.trust_env = False # ignore HTTP_PROXY / HTTPS_PROXY from the shell
session.proxies = {
"http": "http://USER:PASS@gate.quantumproxies.io:PORT",
"https": "http://USER:PASS@gate.quantumproxies.io:PORT",
}
print(session.get("https://httpbin.org/ip", timeout=(5, 20)).json())
올바른 자격 증명이 있는 407이 지속된다면, 등록되지 않은 주소에서 호출된 IP 화이트리스트 계획을 가리킵니다 — 원인 전체 목록은 우리의 407 Proxy Authentication Required 가이드에 있습니다.
간헐적인 ProxyError: 재시작하지 말고 회전하세요
짜증나는 경우는 코드가 20분 동안 실행된 후 ProxyError를 발생시키고, 다시 작동하는 경우입니다. 이는 스크립트의 버그가 아닙니다 — 이는 실행 중간에 죽거나 속도 제한된 단일 출구 IP입니다. 해결책은 회전으로 재시도하는 것입니다: 호출을 래핑하고, ConnectionError를 포착하며, 회전 게이트웨이가 다음 시도에서 새로운 IP를 제공하도록 하세요. 회전 프록시를 통해 모든 재시도는 다른 출구를 통해 이동하므로, 하나의 죽은 주소가 요청을 두 번 실패시킬 수 없습니다:
import requests
def get_with_rotation(url, proxies, attempts=4):
last = None
for _ in range(attempts):
try:
r = requests.get(url, proxies=proxies, timeout=(5, 20))
if r.status_code not in (429, 500, 502, 503, 504):
return r
last = r.status_code
except requests.exceptions.ConnectionError as e: # Proxy/SSL/ConnectTimeout
last = e
raise RuntimeError(f"failed after {attempts} attempts: {last}")
ProxyError가 여러 새로운 IP에서 지속된다면, 문제는 풀에서 대상로 이동한 것입니다: 차단되고 있는 것입니다, 연결이 끊어진 것이 아닙니다. 이는 다른 싸움입니다 — 페이싱, 헤더 및 세션 위생에 대한 차단 방지 체크리스트를 참조하세요.
내부화할 가치가 있는 또 다른 구별이 있습니다, 왜냐하면 그것이 당신의 대응 방식을 바꾸기 때문입니다. ProxyError 또는 ConnectTimeout은 요청이 완료되지 않았음을 의미하므로, 재시도가 안전합니다 — POST에도 안전합니다 — 반대편에서 아무 일도 일어나지 않았습니다. ReadTimeout은 반대로 대상이 요청을 받았고 단순히 응답하는 데 너무 오래 걸렸음을 의미합니다; 비멱등적 쓰기에 대한 재시도는 이중 제출을 초래할 수 있습니다. 재시도 루프를 구축할 때, 연결 단계 실패를 자유롭게 재시도할 수 있는 것으로 취급하고, 읽기 단계 실패는 GET 및 HEAD에 대해서만 재시도할 수 있는 것으로 취급하세요. 이 단일 규칙은 불안정한 프록시가 하나의 체크아웃을 세 개로 변환하는 미묘한 버그를 방지합니다.

자주 묻는 질문
requests.exceptions.ProxyError: 프록시에 연결할 수 없음의 원인은 무엇입니까?
프록시 호스트 또는 포트가 잘못되었거나, 프록시가 다운되었거나, 방화벽이 요청이 떠나기 전에 연결을 차단하고 있습니다. 동일한 자격 증명을 사용하여 curl -x로 엔드포인트를 확인하세요; curl도 실패하면 프록시에 도달할 수 없으며, curl이 성공하면 환경 변수나 Python의 잘못된 딕셔너리가 원인입니다.
왜 ProxyError가 HTTPSConnectionPool에 래핑되어 있습니까?
그 래퍼는 단지 urllib3가 대상을 도달하기 위해 사용한 연결 풀의 이름을 지정합니다 — 이는 내부에 중첩된 실제 메시지 주위의 잡음입니다. 가장 안쪽의 Caused by 절을 읽으세요: Cannot connect to proxy는 연결 실패를 의미하며, 동일한 풀 내의 407 또는 SSL 메시지는 인증 또는 TLS/스킴 문제를 가리킵니다.
Requests가 환경에서 프록시를 읽지 않도록 하려면 어떻게 해야 합니까?
session.trust_env = False를 세션에 설정하거나, trust_env=False를 전달하여 Requests가 HTTP_PROXY 및 HTTPS_PROXY를 무시하도록 하세요. 이는 한 셸에서는 프록시가 작동하지만 다른 셸에서는 ProxyError를 발생시키거나, 기업 변수가 프록시를 사용하도록 구성하지 않은 스크래퍼를 납치할 때의 해결책입니다.
ConnectTimeout은 안전하게 재시도할 수 있습니까?
예 — Requests 문서는 ConnectTimeout이 서버에 도달하지 않았기 때문에 안전하게 재시도할 수 있다고 표시합니다, 따라서 부작용이 발생할 수 없습니다. 이를 재시도하세요, 이상적으로는 회전 게이트웨이를 통해 다음 시도가 다른, 더 빠른 출구를 사용하도록 하세요. ReadTimeout은 POST와 같은 비멱등적 메서드에 대해 맹목적으로 재시도하기에는 더 위험합니다.
ProxyError를 단일 오류로 취급하는 것을 멈추고 수명 주기 단계별로 읽기 시작하면, 해결책은 기계적입니다: 스킴 오타는 네트워크 전에 발생하고, 연결 실패는 죽은 프록시를 명명하며, SSL 오류는 잘못된 키를 명명하고, 간헐적인 실패는 재시작이 아닌 회전을 원합니다. 깨끗한 출구는 대부분을 완전히 제거합니다.