프록시를 변경하고 새로운 IP 주소 풀을 구매했지만 파서가 여전히 오류 429 Too Many Requests로 중단되나요? 이는 전형적인 상황입니다: 차단의 70%는 IP 주소와 관련이 없고 요청의 형식과 관련이 있습니다. 프록시를 변경한 후에도 사이트가 여전히 당신을 차단하는 6가지 실제 원인과 각 경우에 대한 해결 방법을 살펴보겠습니다.
오류 429의 의미와 프록시가 만병통치약이 아닌 이유
HTTP 코드 429 Too Many Requests는 형식적으로 "요청 한도를 초과했습니다"라는 의미입니다. 그러나 실제로는 Wildberries, Ozon, Avito, Yandex.Market와 같은 사이트가 이 코드를 "우리는 당신이 봇이라고 생각합니다"라는 보편적인 신호로 사용합니다. 원인은 요청 빈도일 수 있지만, 헤더, 브라우저 지문, 쿠키 세션의 부재 또는 IP가 아닌 계정에 연결된 제한일 수도 있습니다.
이러한 이유로 프록시를 변경해도 도움이 되지 않는 경우가 많습니다: 시스템이 IP가 아닌 요청 패턴(지문, 헤더, 클릭 속도)을 차단하는 경우, 새로운 IP 주소에서 몇 분 후에 동일한 429 오류를 받을 수 있습니다. 각 원인을 자세히 살펴보고, 새로운 프록시 풀을 구매하지 않고도 이를 확인하고 해결하는 방법을 보여드리겠습니다.
중요: 프록시는 여전히 필요한 도구입니다 — 그러나 시스템의 일부로서만 필요하며, 유일한 해결책으로는 필요하지 않습니다. 레지던셜 및 모바일 IP는 IP 평판으로 인한 블랙리스트에 올라갈 확률을 줄이지만, 행동이나 헤더로 인한 차단에서는 구제받지 못합니다.
원인 1: 너무 높은 요청 빈도
가장 명백하지만 자주 잘못 진단되는 원인입니다. 많은 사람들이 "각 요청마다 프록시를 변경하니 빈도는 중요하지 않다"고 생각합니다. 이는 잘못된 생각입니다. 현대의 안티봇 시스템(예: Wildberries와 Ozon은 Cloudflare 수준의 솔루션이나 자체 WAF를 사용합니다)은 단일 IP에서의 빈도뿐만 아니라 특정 API 엔드포인트 또는 제품 페이지에 대한 전체 부하를 시간 단위로 모든 출처에서 분석합니다.
만약 당신의 파서가 동일한 카탈로그 섹션에 대해 초당 50-100개의 요청을 한다면, 시스템은 다양한 IP를 사용하더라도 비정상적인 트래픽 급증을 감지합니다. 해결책은 프록시를 변경하는 것이 아니라 요청 간에 인위적인 지연(throttling)을 도입하는 것입니다: 고정 간격 대신 1-3초의 임의 지연을 추가하고, 429를 받을 경우 지연 시간을 두 배로 늘리는 지수적 백오프를 적용합니다.
import time, random
def safe_request(session, url):
for attempt in range(5):
response = session.get(url)
if response.status_code == 429:
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)
continue
return response
return None
만약 코드가 없는 준비된 파서를 사용하고 있다면(예: 클라우드 가격 모니터링 서비스), 요청 간의 간격 설정을 확인하세요 — 대부분의 도구에는 "스캔 속도" 슬라이더가 있습니다. 속도를 30-40% 줄이면 프록시를 변경하지 않고도 429를 완전히 제거할 수 있습니다.
원인 2: 잘못된 또는 누락된 헤더
많은 파서가 최소한의 헤더 세트로 요청을 보내거나 기본 라이브러리의 User-Agent를 사용합니다(예: "python-requests/2.28.1"). 이러한 헤더는 즉시 봇으로 식별됩니다 — 실제 브라우저는 Accept, Accept-Language, Accept-Encoding, Sec-Fetch-*, Referer 등 수십 개의 헤더를 엄격한 순서로 보냅니다.
Wildberries와 Ozon은 헤더 세트를 실제 Chrome 또는 Safari 브라우저의 예상 "지문"과 비교합니다. 헤더가 너무 적거나 순서가 맞지 않거나 User-Agent가 다른 매개변수와 일치하지 않는 경우(예: Windows에서 Chrome으로 선언되었지만 TLS 지문이 Python과 유사한 경우) — 요청은 IP와 관계없이 429로 차단됩니다.
| 헤더 | 일반적인 오류 | 해결책 |
|---|---|---|
| User-Agent | 구식 버전 또는 명백한 라이브러리 문자열 | 실제 Chrome/Safari의 최신 UA, 풀에서 회전 |
| Accept-Language | 누락되었거나 IP의 지리적 위치와 일치하지 않음 | 러시아 마켓플레이스를 위한 ru-RU |
| Referer | 비어 있음, 실제 전환은 항상 Referer가 있음 | 카탈로그의 이전 페이지를 지정 |
| Sec-Fetch-* | 완전히 누락됨 (브라우저 클라이언트 아님) | 실제 브라우저의 DevTools에서 전체 세트를 복사 |
가장 간단한 방법은 실제 브라우저의 DevTools에서 Network 탭을 열고 필요한 페이지를 수동으로 열어 전체 헤더 세트를 복사하여 파서에서 사용하는 것입니다 — 헤더의 순서를 고려하여, 라이브러리가 이를 허용하는 경우(예: curl_cffi 또는 httpx에서 명시적 순서).
원인 3: 세션 및 쿠키 회전 부족
자주 간과되는 오류: 파서는 각 요청마다 IP를 변경하지만 동일한 쿠키 세션을 사용하거나 쿠키를 전혀 저장하지 않습니다. 실제 사용자는 첫 방문 시 쿠키 세트(세션 토큰, 장치 ID, Cloudflare __cf_bm 또는 Ozon/WB의 유사한 안티봇 보호 태그)를 받고, 이후 모든 요청에서 이를 사용합니다.
쿠키 없이 요청을 보내면 안티봇 시스템은 "제로" 세션을 감지합니다 — 이는 즉시 의심스럽게 보이며, 특히 API 엔드포인트에 직접 접근할 때 더욱 그렇습니다. 해결책은 전체 시나리오를 에뮬레이션하는 것입니다: 먼저 메인 페이지 또는 카테고리 페이지를 로드하고 쿠키를 얻은 후 1-2초 기다린 다음, 필요한 API 또는 제품 카드에 접근하여 요청 체인 동안 동일한 세션에서 쿠키를 유지합니다.
import requests
session = requests.Session()
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"Accept-Language": "ru-RU,ru;q=0.9"
})
# 세션 예열
session.get("https://www.wildberries.ru/catalog/0/search.aspx")
time.sleep(1.5)
# 쿠키와 함께하는 주요 요청
response = session.get(target_url)
Dolphin Anty 또는 AdsPower와 같은 안티디텍트 브라우저를 사용하여 제품 카드를 수동으로 모니터링하거나 내장된 자동화를 통해 모니터링하는 경우, 프로필이 세션 간 쿠키를 저장하고 매번 "새로운 시작"으로 실행되지 않도록 확인하세요 — 이것도 시스템의 의심을 유발할 수 있습니다.
원인 4: 봇과 유사한 행동
완벽한 헤더와 쿠키를 가지고도 파서는 행동 패턴으로 자신을 드러낼 수 있습니다: 제품에 대한 요청의 엄격한 선형 순서(ID 증가 순서), 요청 간의 동일한 간격(밀리초 단위), 일반 브라우저가 자동으로 로드하는 정적 자원(이미지, CSS, JS)에 대한 "쓰레기" 요청의 부재.
Wildberries와 Ozon의 고급 보호 시스템은 HTTP 요청뿐만 아니라 페이지에서 JavaScript가 실행되었는지(헤드리스 감지), "마우스"가 움직였는지, 스크롤이 있었는지를 분석합니다. 만약 당신이 JS 렌더링 없이 순수 HTTP 요청을 한다면, 사이트가 토큰을 얻기 위해 스크립트 실행을 기대하는 경우(예: 안티봇 자바스크립트 챌린지), 이 스크립트를 실행하지 않은 요청은 자동으로 429 또는 403을 받습니다.
해결책은 규모에 따라 다릅니다: 소규모의 경우 마우스 움직임과 임의 지연을 에뮬레이션하는 헤드리스 브라우저(Playwright, Puppeteer)를 사용하는 것이 적합합니다. 산업 규모의 파싱을 위해서는 제품 탐색 순서의 무작위화, 부차적인 자원에 대한 요청의 "잡음" 추가, 고정 간격이 아닌 정규 분포에 따른 시간 간격의 변동성을 추가합니다.
원인 5: TLS/JA3 지문 및 HTTP/2
이는 90%의 파서 관련자들이 깊은 기술적 준비 없이 모르는 가장 "눈에 띄지 않는" 기술적 원인입니다. 각 TLS 클라이언트(요청 라이브러리, curl, urllib)는 HTTPS 연결을 설정할 때 고유한 지문을 남깁니다 — 지원하는 암호, 프로토콜 버전, TLS 확장 세트. 이 지문을 JA3/JA4 지문이라고 합니다.
Cloudflare, Akamai 및 대형 마켓플레이스의 자체 솔루션 수준의 안티봇 시스템은 JA3 지문을 알려진 봇 및 라이브러리 데이터베이스와 비교합니다. 표준 Python 요청 또는 Node.js https 모듈은 쉽게 감지되는 지문을 가지며, 실제 Chrome의 지문과 완전히 다릅니다. 완벽한 헤더와 쿠키가 있어도 요청은 TLS 핸드쉐이크 수준에서 차단되며, 서버가 HTTP 헤더를 보기 전에 차단됩니다.
추가로 많은 마켓플레이스는 특정 매개변수를 가진 HTTP/2를 요구합니다(프레임 SETTINGS 순서, 스트림 우선 순위 지정) — HTTP/1.1 기반 라이브러리는 이러한 배경에서 자동으로 드러납니다. 해결책은 실제 브라우저의 지문을 에뮬레이션하는 라이브러리를 사용하는 것입니다: curl_cffi(Chrome TLS 지문 에뮬레이션), tls-client 또는 Chromium 기반의 완전한 헤드리스 브라우저는 본질적으로 "진짜" 지문을 제공합니다.
pip install curl_cffi
예: curl_cffi.requests.get(url, impersonate="chrome120") — 라이브러리가 자동으로 실제 Chrome 120과 동일한 TLS 지문을 삽입합니다.
원인 6: 계정 또는 API 키 수준의 제한
마켓플레이스의 공식 또는 반공식 API를 통해 작업하는 경우(예: Wildberries의 판매자 API 또는 Ozon 판매자 API), 429는 IP가 아닌 판매자 계정이나 API 토큰에 연결될 수 있습니다. 이 경우 프록시를 변경해도 소용이 없습니다 — 제한은 서버 측에서 계정 ID에 연결되어 저장되며, 해당 계정의 모든 IP는 동일한 제한을 받습니다.
이러한 상황은 경쟁자의 가격을 모니터링하면서 개인 계정을 통해 API를 호출하는 판매자에게 일반적입니다 — 두 요청 흐름은 계정의 총 한도로 합산됩니다. 해결책은 시간을 분산시키고, 공식 API 쿼트를 더 경제적으로 사용하며(매 분마다 변경되지 않는 데이터를 캐시), 순수한 경쟁 가격 모니터링을 위해 판매자 계정에 연결되지 않은 별도의 비인증 요청 흐름을 사용하는 것입니다.
이를 확인하는 것은 간단합니다: 만약 429가 새로운 깨끗한 IP에서 이전 세션의 쿠키 없이도 계속 발생하지만, 당신이 다른 탭에서 개인 계정에 로그인되어 있다면 — 아마도 제한은 계정에 연결된 것입니다.
실제 원인을 진단하는 방법
인프라를 변경하기 전에 다음 알고리즘에 따라 진단을 수행하세요. 먼저 일반 브라우저에서 페이지를 수동으로 열고, 실제 행동을 할 때 429가 발생하지 않는지 확인하세요 — 이는 문제가 파서 측에 있다는 것을 확인시켜줄 것입니다, 글로벌 지역 차단이 아닙니다.
그런 다음 DevTools를 통해 실제 브라우저의 헤더와 파서의 헤더를 비교하세요(Network 탭 → Copy as cURL). 차이가 최소한이라면, tls.peet.ws와 같은 서비스로 TLS 지문을 확인하세요 — 당신의 라이브러리로 요청을 보내고 JA3 해시를 기준 브라우저 해시와 비교합니다. 요청이 TLS 핸드쉐이크 단계에서 실패한다면(HTTP 응답을 받기 전에 연결이 끊어짐) — 원인은 지문에 있으며, 빈도나 헤더가 아닙니다.
다음으로 제한이 IP에 연결되어 있는지 아니면 계정에 연결되어 있는지 확인하세요: 인증 없이 새로운 깨끗한 IP에서 요청을 하세요. 만약 429가 사라진다면 — 문제는 이전 IP의 평판이나 해당 주소의 빈도에 있었습니다. 만약 429가 계속된다면 — 원인을 헤더, TLS 또는 행동에서 찾아야 하며, 프록시에서 찾지 마세요.
프록시 변경 없이 429 해결을 위한 체크리스트
- 고정 간격 대신 요청 간에 1-4초의 임의 지연을 추가하세요.
- Sec-Fetch-* 및 Accept-Language를 포함한 실제 브라우저의 전체 헤더 세트를 복사하세요.
- 메인 페이지를 예열한 후, 하나의 세션 내에서 쿠키를 저장하고 전달하세요.
- 당신의 라이브러리의 TLS 지문을 확인하세요 — 순수 HTTP 클라이언트 대신 curl_cffi 또는 헤드리스 브라우저를 사용하세요.
- 페이지 탐색 순서를 무작위화하고 정적 자원에 대한 "잡음" 요청을 추가하세요.
- API 계정의 부하와 익명 가격 모니터링을 다른 흐름으로 분리하세요.
- 429를 받을 경우 즉각적인 요청 재시도 대신 지수적 백오프를 도입하세요.
- 위의 모든 항목을 확인한 후에만 — 프록시를 변경하거나 IP 풀을 확장하세요.
프록시가 여전히 필요한 경우와 선택 방법
모든 여섯 가지 원인을 해결한 후에도 프록시는 인프라의 중요한 요소로 남아 있습니다 — 그러나 이제는 단순한 차단 해결책이 아닌 확장 수단으로서 필요합니다. 만약 당신의 작업이 수천 개의 Wildberries 및 Ozon 카드의 병렬 모니터링이라면, 차단 이력을 한 주소에 축적하지 않기 위해 좋은 평판의 IP 풀을 확보해야 합니다.
대량의 가격 및 카탈로그 모니터링을 위해서는 레지던셜 프록시가 가장 적합합니다 — 이들은 실제 가정 ISP의 IP를 사용하므로 안티봇 시스템은 요청을 일반 구매자의 트래픽으로 인식합니다, 데이터 센터가 아닙니다. 이는 매우 중요합니다, 왜냐하면 Wildberries와 Ozon은 이미 데이터 센터 IP 범위에 대한 블랙리스트를 운영하고 있기 때문입니다.
만약 작업이 사이트의 모바일 버전을 확인하거나 마켓플레이스 앱을 통해 작업하거나 TikTok Ads 및 Facebook Ads에서 도시 단위의 지리적 정확도로 광고를 테스트하는 것과 관련이 있다면, 모바일 프록시가 적합합니다 — 이들은 대부분의 보호 시스템에서 최대 신뢰도를 가지고 있으며, IP는 실제 이동통신 사업자에게 속합니다.
덜 민감한 작업의 경우 — 예를 들어, 작은 규모의 비인증 공개 카탈로그 파싱 — 데이터 센터 프록시를 사용할 수 있습니다: 이들은 훨씬 저렴하고 빠르지만, 헤더 및 TLS 지문에 대한 더 세심한 설정이 필요하며, 스스로 더 높은 의심 위험을 동반합니다.
| 프록시 유형 | 429을 해결하는 경우 | 도움이 되지 않는 경우 |
|---|---|---|
| 레지던셜 | IP가 이미 평판으로 블랙리스트에 올라 있음 | TLS 지문 또는 헤더로 인한 차단 |
| 모바일 | 민감한 시나리오에 대한 최대 신뢰도가 필요함 | 제한이 IP가 아닌 계정에 연결됨 |
| 데이터 센터 | 비인증 상태에서 열린 페이지의 간단한 파싱 | 평판 범위 확인이 있는 엄격한 안티봇 시스템 |
결론
파싱 중 오류 429는 단순히 "프록시를 변경"하는 버튼 하나로 해결되지 않습니다. 대부분의 경우 문제는 요청 빈도, 불완전한 헤더, 쿠키 세션의 부재, 감지 가능한 TLS 지문, 행동 패턴 또는 계정에 연결된 제한에 있습니다. 이 기사에서 언급한 여섯 가지 항목에 대해 진단을 수행한 후에야 프록시 풀을 확장하는 예산을 낭비하지 마세요.
기술적인 부분이 올바르게 설정되면 — 헤더가 실제 브라우저에 일치하고, TLS 지문이 라이브러리를 드러내지 않으며, 요청이 사용자 행동을 모방하게 된다면 — 프록시는 실제로 효과적인 확장 도구가 됩니다. Wildberries와 Ozon에서 대량으로 가격 모니터링을 위해서는 레지던셜 프록시를 시작하는 것이 좋습니다: 이들은 비용과 안티봇 시스템의 신뢰도 간의 최상의 균형을 제공합니다.