HTTP 오류 심층 분석
HTTP 오류는 요청이 실패했음을 알려줍니다. 보통 왜 실패했는지는 알려주지 않습니다. 403이 발생했다고 해서 "키가 죽었다"라고 결론 내리는 것은 숫자에 불과한 추측입니다.
실제 사례(2026-07-30)에서 비롯되었습니다: 타사 연락처 보강 API가 6일 동안 모든 호출에 403 error code: 1010을 반환했습니다. 이는 "키에 권한이 없음", "크레딧 소진", "월별 할당량 초과"로 해석되었습니다. 이 믿음을 바탕으로 756줄의 브라우저 자동화 우회 방법이 구축되었고, 고객에게는 괜찮은 플랜을 확인하라고 요청할 뻔했습니다. 실제 원인은 User-Agent 누락으로 인해 CDN이 클라이언트를 에지에서 차단한 것이었습니다. 하나의 헤더로 해결되었습니다: 200 OK, 전체 데이터, 크레딧 잔액은 처음부터 거기에 있었습니다.
진단 전에 다음 세 가지 제어를 실행하세요
1. 잘못된 자격 증명 제어. 동일한 요청을 의도적으로 잘못된 자격 증명으로 보내고, 또 아예 자격 증명 없이 보냅니다.
for label, key in [("real", REAL_KEY), ("garbage", "0"*36), ("empty", "")]:
... # 동일한 URL, 동일한 메서드, 동일한 헤더
세 가지 모두 동일한 상태와 본문을 반환한다면, 오류는 자격 증명과 관련이 없습니다. 권한, 할당량 또는 유효성에 대한 정보를 전혀 제공하지 않습니다. 키에 대한 추론을 중단하세요.
2. 무의미한 경로 제어. 분명히 존재하지 않는 경로와 사이트 루트를 요청합니다.
GET https://api.example.com/definitely-not-a-real-path-12345
GET https://api.example.com/
GET https://www.example.com/ # 그들의 마케팅 사이트
API 오류가 마케팅 사이트에서도 동일하게 발생한다면, 원본 앞에서 차단되고 있는 것입니다. API 호출에 관한 어떤 것도 도움이 되지 않습니다.
3. 응답 본문뿐만 아니라 헤더도 읽으세요.
except urllib.error.HTTPError as e:
print(e.code, e.headers.get("server"), e.read()[:200])
"인증" 오류에 server: cloudflare가 있으면 결정적인 단서입니다. cf-ray, x-amzn-waf, akamai도 마찬가지입니다. 이들은 인프라스트럭처이며 API가 아닙니다.
알려진 패턴
| 증상 | 실제 원인 | 해결 방법 |
|---|---|---|
403 + 본문 error code: 1010, server: cloudflare | Cloudflare가 클라이언트 서명을 차단함 | 실제 브라우저 User-Agent 전송 |
모든 경로(/ 포함)에 대해 동일한 오류 | 에지/WAF 차단 또는 IP 차단 | UA 변경 후 IP/프록시 변경 |
402 | 실제로 크레딧 소진: 요청이 인증됨 | 충전; 키는 정상 |
성공 직후 429 | 실제 속도 제한 | 요청 속도 조절, 백오프 추가 |
필드 메시지가 있는 400 | API에 도달했으며 인증이 통과됨 | 페이로드 수정; 이는 좋은 신호 |
400 유효성 검사 오류는 심층 분석에 성공 신호입니다. 인증이 수락되었음을 증명합니다.
서버 측 API 호출을 위한 기본 헤더
Python의 urllib는 User-Agent: Python-urllib/3.x를 보내는데, 많은 WAF에서 이를 무조건 차단합니다. 항상 설정하세요:
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36",
"Accept": "application/json",
"Content-Type": "application/json",
}
그런 다음 대규모 실행 전에 비용을 측정하세요
일단 작동하면, 지출을 추정하지 마세요. 한 번 호출하기 전후의 사용량을 읽고, 응답에서 제공업체 자체 청구 필드를 읽으세요.
before = usage(); result = call(); after = usage()
print("charged:", result.get("billing"), "| measured:", before - after)
보고할 내용
무엇을 테스트했고 무엇이 아직 알려지지 않았는지 명시하세요. "두 키 모두 403"은 빈 키가 동일한 403을 반환하는 경우 결과가 아닙니다. 대신 이렇게 말하세요: "여기서 403은 정보를 제공하지 않습니다. null 자격 증명도 동일하게 반환되며, 원인은 인증 상류에 있습니다."