파서를 확장할 때의 전형적인 실수는 "눈대중"으로 프록시를 구매하는 것입니다: 100개의 IP를 가져와서 파서를 실행했더니 30분 만에 차단되었습니다. 문제는 주소의 수가 아니라, 각 IP에 대한 실제 부하를 계산하지 않기 때문입니다. RPS(초당 요청 수), 지연 시간 및 대상 사이트의 제한을 기반으로 한 프록시 풀 계산 공식을 살펴보겠습니다 — 구체적인 숫자와 Python 코드를 사용하여.
IP 수로 풀을 계산하는 것이 잘못된 이유
파싱 초보자의 전형적인 접근 방식: "50,000개의 상품 카드를 수집해야 하므로 500개의 프록시를 구매하고 부하를 분산시킵니다." 이 논리는 합리적으로 보이지만, Wildberries 및 Ozon과 같은 사이트는 요청의 절대 수가 아니라 요청의 강도에 따라 차단합니다. 즉, 각 20개의 요청을 초당 보내는 500개의 프록시는 즉시 차단됩니다 — 안티봇 시스템은 DDoS와 유사한 패턴을 감지합니다.
반면에, 50개의 프록시가 있지만 각 프록시가 5-10초마다 랜덤한 간격으로 1개의 요청을 보내는 경우, 사람의 행동을 모방하여 몇 주 동안 단 한 번의 차단 없이 안정적으로 파싱할 수 있습니다. IP 수는 안정성의 원인이 아니라 올바르게 계산된 부하의 결과입니다. 그렇기 때문에 공식은 "얼마나 많은 IP를 구매할 것인가"가 아니라 "얼마나 많은 RPS에 도달해야 하고 하나의 IP에 대한 안전한 부하는 얼마인가"에서 출발해야 합니다.
또 다른 점은, 다양한 유형의 프록시가 하나의 IP에 대해 다른 "내구성"을 가지고 있다는 것입니다. 데이터센터 프록시는 높은 요청 빈도에서 더 빨리 차단되며, 그들의 서브넷은 호스팅으로 쉽게 인식됩니다. 레지던스 및 모바일 IP는 일반 사용자처럼 보이며, 차단 위험 없이 조금 더 높은 빈도를 허용할 수 있지만 — 이는 제한을 완전히 무시할 수 있다는 의미는 아닙니다.
프록시 풀 계산의 기본 공식
프록시 풀의 수를 계산하는 공식은 다음과 같습니다:
N = (RPS_target × Delay_per_ip) / Concurrency_per_ip
여기서:
- N — 풀에 필요한 프록시 수;
- RPS_target — 파싱의 목표 속도 (시스템 전체의 초당 요청 수);
- Delay_per_ip — 하나의 IP에서 요청 간의 최소 안전 간격 (초 단위);
- Concurrency_per_ip — 하나의 IP에서 허용하는 병렬 스레드 수 (보통 1, 레지던스 프록시의 경우 최대 2).
논리는 간단합니다: 만약 총 10개의 요청을 초당 유지하고 싶고, 하나의 IP에서 요청 간의 안전한 간격이 8초라면, 하나의 IP는 물리적으로 8초에 1개의 요청만 보낼 수 있으므로, 즉 0.125 RPS입니다. 총 10 RPS를 얻으려면 10 / 0.125 = 80개의 프록시가 필요합니다. 이것이 "500 IP를 만일을 위해"라는 추측을 대체하는 계산입니다.
파싱을 위한 목표 RPS 계산 방법
풀을 계산하기 전에 RPS_target을 정의해야 합니다 — 데이터를 합리적인 시간 내에 수집하기 위해 실제로 필요한 초당 요청 수입니다. 여기서 공식은 반대입니다:
RPS_target = Total_requests / Time_budget_seconds
예: Wildberries에서 8시간(28,800초) 동안 100,000개의 상품 카드를 수집해야 합니다. 각 카드에 1개의 요청이 소요된다면, RPS_target = 100,000 / 28,800 ≈ 3.47 요청/초입니다. 처음에는 그렇게 많지 않다고 생각할 수 있지만 — 많은 사람들이 필요한 속도를 과대평가하고 과도한 수의 프록시를 구매합니다.
만약 작업에 여러 유형의 요청이 포함된다면 (예: 먼저 카테고리 목록을 가져오고, 그 다음 카드, 마지막으로 리뷰를 가져오는 경우), 각 단계에 대해 별도로 RPS를 계산하세요 — 이들은 서로 다른 프록시 풀과 함께 병렬로 진행될 수 있으며, 플랫폼에 대한 총 부하는 서로 다른 엔드포인트에 분산될 것입니다.
하나의 IP에 대한 제한: 분당 안전한 요청 수
Delay_per_ip는 공식에서 가장 중요한 매개변수이며, 각 사이트에 대해 경험적으로 정의해야 합니다. 마켓플레이스 파싱 경험을 기반으로 한 일반적인 기준은 다음과 같습니다:
| 플랫폼 | 1 IP에서 요청 간의 안전한 간격 | 1 IP에서 최대 요청 수/분 |
|---|---|---|
| Wildberries (상품 카드 API) | 4-6초 | 10-15 |
| Ozon (상품 페이지) | 5-8초 | 8-12 |
| Avito (광고) | 6-10초 | 6-10 |
| Yandex.Market | 5-7초 | 8-12 |
이 숫자는 시작점이며, 절대적인 규칙이 아닙니다. 보수적인 값(최대 간격)으로 시작하고, 429 및 403 오류 비율을 모니터링하며, 차단이 증가하지 않는다면 점차 간격을 줄이세요. RPS를 급격하게 증가시키는 것은 하루 만에 모든 프록시 풀을 소진하는 가장 흔한 방법입니다.
실제 계산: Wildberries, Ozon, Avito
공식에 따른 세 가지 실제 시나리오를 분석해 보겠습니다.
시나리오 1: Wildberries의 가격 모니터링. 20,000개의 상품 가격을 2시간마다 업데이트해야 합니다. RPS_target = 20,000 / (2 × 3600) ≈ 2.78 RPS. 1 IP에서 5초의 간격과 Concurrency = 1일 때: N = (2.78 × 5) / 1 ≈ 14 프록시. IP의 일부가 차단될 경우를 대비하여 1.5-2배의 풀을 추천하므로, 즉 21-28 프록시를 가져가는 것이 좋습니다.
시나리오 2: Ozon 카탈로그의 일회성 수집. 500,000개의 카드가 24시간 동안 수집됩니다. RPS_target = 500,000 / 86,400 ≈ 5.79 RPS. 6초의 간격일 때: N = (5.79 × 6) / 1 ≈ 35 프록시. 여유를 두고 50-60 프록시를 추천합니다.
시나리오 3: Avito에서 경쟁업체 모니터링. 5,000개의 광고를 15분마다 업데이트합니다. RPS_target = 5,000 / 900 ≈ 5.56 RPS. 8초의 간격일 때: N = (5.56 × 8) / 1 ≈ 45 프록시. Avito는 데이터센터 서브넷을 적극적으로 차단하므로, 이러한 작업에는 레지던스 또는 모바일 IP를 사용하는 것이 더 합리적입니다.
공식에서의 레지던스, 모바일 및 데이터센터 프록시
프록시 유형은 Delay_per_ip에 직접적인 영향을 미치며, 따라서 공식에서의 최종 N에 영향을 미칩니다. 데이터센터 프록시는 더 저렴하고 빠르지만, 요청 간의 간격이 더 길어야 하며, 높은 빈도에서 더 자주 차단됩니다 — 사실상, 동일한 5 RPS를 위해서는 레지던스 IP보다 2-3배 더 많은 데이터센터 IP가 필요할 수 있습니다.
| 프록시 유형 | 평균 Delay_per_ip | 언제 사용해야 하는가 |
|---|---|---|
| 데이터센터 프록시 | 8-15초 | 공개 API, 엄격한 안티봇이 없는 사이트 |
| 레지던스 프록시 | 4-8초 | Wildberries, Ozon, Avito 및 기타 안티봇이 있는 마켓플레이스 |
| 모바일 프록시 | 3-6초 | 가장 공격적인 보호, 소셜 미디어, 모바일 API |
Wildberries 및 Ozon과 같은 마켓플레이스를 파싱할 때 최적의 선택은 일반적으로 레지던스 프록시입니다 — 이들은 가격과 안정성을 균형 있게 유지하며, 차단 증가 없이 더 짧은 간격을 유지할 수 있게 해줍니다. 데이터센터 프록시는 공격적인 안티봇이 없는 플랫폼이나 낮은 RPS의 일회성 작업에 대해서만 고려해야 합니다.
Python에서의 풀 구현: 회전 코드
아래는 각 IP에 대한 요청 빈도를 제어하는 프록시 풀의 간단한 구현입니다. 논리는 각 프록시가 마지막 사용 시간을 저장하고, 스케줄러가 마지막 요청 이후 충분한 시간이 지난 IP만 선택하는 것입니다.
import time
import random
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class ProxyNode:
address: str
delay_per_ip: float # 안전한 간격 (초 단위)
last_used: float = field(default=0.0)
class ProxyPool:
def __init__(self, proxies: List[str], delay_per_ip: float):
self.nodes = [ProxyNode(address=p, delay_per_ip=delay_per_ip) for p in proxies]
def get_available_proxy(self) -> Optional[ProxyNode]:
now = time.time()
available = [
node for node in self.nodes
if now - node.last_used >= node.delay_per_ip
]
if not available:
return None
# 대기열 패턴을 피하기 위해 사용 가능한 것 중에서 무작위로 선택
node = random.choice(available)
node.last_used = now
return node
def size(self) -> int:
return len(self.nodes)
def calculate_pool_size(rps_target: float, delay_per_ip: float, concurrency: int = 1) -> int:
"""공식: N = (RPS_target * Delay_per_ip) / Concurrency_per_ip"""
return max(1, int((rps_target * delay_per_ip) / concurrency))
# 사용 예
rps_target = 5.79 # 작업의 양과 시간에서 계산됨
delay_per_ip = 6.0 # Ozon의 안전한 간격
n_proxies = calculate_pool_size(rps_target, delay_per_ip)
print(f"풀에 필요한 프록시 수: {n_proxies}") # ~35, 여유를 두고 50-60
실제 파서에서는 이 논리를 작업 대기열(예: asyncio 또는 multiprocessing를 통해)으로 보완해야 하며, 작업자가 자유로운 프록시가 나타날 때까지 기다리도록 해야 합니다. 또한 429/403 오류를 받을 때 지수 백오프를 추가하는 것이 좋습니다 — 이는 차단 징후가 있을 때 특정 IP에 대한 간격을 자동으로 늘려줍니다.
모니터링 및 풀의 동적 조정
공식에 따른 정적 계산은 출발점일 뿐이며, 실제 운영에서는 세 가지 주요 메트릭을 추적해야 합니다:
- 성공률 — 전체 요청 수에 대한 성공적인 요청(코드 200)의 비율. 90-95% 이하로 떨어지면 Delay_per_ip를 늘리거나 풀에 프록시를 추가해야 한다는 신호입니다;
- 차단률 — 지난 한 시간 동안 차단 징후(캡차, 403, 인증 양식으로의 리디렉션)를 보인 프록시의 비율;
- 실제 RPS — 타임아웃 및 재시도 때문에 계산된 것과 다를 수 있는 실제 요청 처리 속도.
실용적인 규칙: 만약 차단률이 시간당 5-7%를 초과하면 Delay_per_ip를 20-30% 증가시키고 공식을 다시 계산하세요. 성공률이 몇 시간 동안 98% 이상 안정적으로 유지된다면, 간격을 점차 줄이고 풀의 크기를 줄일 수 있습니다 — 이는 차단 위험 없이 프록시에 대한 예산을 직접적으로 절약하는 방법입니다.
좋은 관행은 각 IP에 대해 별도의 로그를 유지하는 것입니다: 마지막 성공적인 요청 시간, 연속 오류 수, 평균 응답 시간. 이는 "손상된" 프록시를 30-60분 동안 회전에서 자동으로 제외할 수 있게 해주며, 계속해서 요청을 보내고 매 단계에서 캡차를 받는 것을 피할 수 있습니다.
프록시 풀 계산 시 자주 발생하는 실수
공식을 알고 있어도 계산의 세부 사항에서 쉽게 실수할 수 있습니다. 다음은 전형적인 실수 목록입니다:
- 차단에 대한 여유를 무시하기. 이상적인 계산을 하더라도 5-10%의 프록시는 차단이나 네트워크 문제로 인해 일시적으로 사용할 수 없습니다. 항상 기본 N에 1.3-2배의 계수를 추가하세요;
- 모든 엔드포인트에 대해 동일한 Delay_per_ip 사용. 카테고리 페이지와 상품 카드 페이지는 서로 다른 제한을 가질 수 있습니다 — 별도로 계산하세요;
- 테스트 없이 Concurrency를 1 이상 설정. 하나의 IP에서의 병렬 요청은 차단 위험을 급격히 증가시킵니다, 특히 레지던스 프록시의 경우 — Concurrency = 1로 시작하세요;
- User-Agent 및 헤더 회전을 하지 않음. 올바르게 계산된 프록시 풀도 모든 요청이 동일한 브라우저 지문으로 이루어진다면 구제할 수 없습니다;
- 지터 없이 고정된 간격. 요청 간의 간격이 엄격히 동일하다면(예: 정확히 5.0초) — 이는 안티봇에 의해 쉽게 인식되는 패턴입니다. ±20-30%의 랜덤 편차를 추가하세요.
결론
공식 N = (RPS_target × Delay_per_ip) / Concurrency_per_ip는 프록시 풀 계산을 추측에서 공학 문제로 전환하여 구체적인 숫자로 변환합니다. 먼저 데이터 양과 시간을 기준으로 목표 파싱 속도를 정의한 후, 특정 플랫폼에 대한 안전한 간격을 경험적으로 찾고, 그 후에 차단 및 오류를 고려하여 30-100%의 여유를 두고 필요한 IP 수를 계산하세요.
이러한 접근 방식은 예산을 절약합니다: "만일을 위해" 과도한 수의 IP를 구매하는 대신, 목표 RPS를 달성하는 데 필요한 정확한 양의 프록시에 대해서만 비용을 지불합니다. 마켓플레이스 및 기타 보호된 사이트를 파싱할 때는 레지던스 프록시로 시작하는 것이 좋습니다 — 이는 Wildberries, Ozon 및 Avito의 안티봇 시스템과 작업할 때 안정성과 비용 간의 최상의 균형을 제공합니다.