빠른 답변
레지덴셜 프록시로 몇 개의 동시 요청을 실행할 수 있나요?
대상마다 허용 가능한 만큼: CrawlRelay은 스레드나 연결이 아닌 GB당 레지덴셜 트래픽을 과금합니다(US$0.50/GB은 50 GB부터). Little 법칙으로 동시성 크기를 정하세요. 진행 중 요청 수 = 페이지당 초 × 초당 페이지 수, 그런 다음 호스트별 2~4개로 제한을 두고 429 또는 503 응답이 나타나면 줄이세요. 결제는 KYC 없이 암호화폐로 됩니다.
동시성 한눈에 보기
| 과금 | 트래픽 GB당 과금; 스레드 및 연결은 과금하지 않음 |
|---|---|
| 크기 산정 규칙 | 진행 중 요청 수 = 처리량 × 대기 시간 (Little 법칙) |
| 호스트별 시작점 | 동시 요청 2–4개; 작은 사이트는 1개 요청 후 지연 |
| 속도 늦춰야 할 신호 | 429, 503, 증가하는 응답 시간, 챌린지 페이지 |
| 대역폭 계산법 | 초당 페이지 수 × 페이지당 바이트 × 3,600 = 시간당 바이트 |
| 레지덴셜 중간 응답 시간 | 프록시 네트워크에서 0.93초, 실제 대상에서 3.21초 (Proxyway 2026) |
필요한 처리량에서 동시성을 어떻게 산정하나요?
Little 법칙을 사용하세요: 진행 중인 요청 수는 원하는 처리율에 각 요청에 걸리는 시간을 곱한 값과 같습니다. Proxyway의 Proxy Market Research 2026에 따르면 실제 사이트 대상의 레지덴셜 프록시 요청 평균 시간이 3.21초였으니, 네트워크 단독의 1초 미만 수치가 아닌 실제 사이트 기준 2~4초로 계획하세요.
| 목표 | 요청 당 시간 | 진행 중 요청 수 |
|---|---|---|
| 초당 5 페이지 | 2초 | 10 |
| 초당 20 페이지 | 2초 | 40 |
| 초당 20 페이지 | 4초 | 80 |
| 초당 100 페이지 | 3초 | 300 |
그 요청들을 여러 호스트에 분산하세요. 200개 사이트에 걸쳐 진행 중인 40개 요청은 원만하지만, 하나의 작은 사이트에 40개 요청은 동의하지 않은 부하 테스트입니다.
그 처리량은 대역폭을 얼마나 사용하나요?
초당 페이지 수에 페이지당 바이트를 곱하세요. 초당 20개의 HTML 페이지와 페이지당 50KB로 크롤링하면 초당 1MB, 즉 시간당 3.6GB, 하루 약 86GB가 됩니다. 렌더링 페이지당 2MB라면 동일한 비율로 초당 40MB가 됩니다.
결과에 요금제의 GB당 가격 US$0.50/GB (50GB 이상 CrawlRelay)를 곱해 시간당 비용을 계산하세요. 대역폭 계산기 가이드에서는 페이지 유형별 평균 페이지 무게를 제공합니다.
호스트당 어떤 제한을 시작해야 하나요?
| 대상 | 호스트당 병렬 요청 수 | 요청 사이 지연 | 비고 |
|---|---|---|---|
| 작은 사이트 (블로그, 지역 상점) | 1 | 2–5초 | robots.txt에 Crawl-delay가 있다면 준수 |
| 중간 규모 사이트 | 2–4 | 0.5–2초 | 응답 시간을 확인하고 두 배로 늘어나면 속도를 줄이세요 |
| CDN이 포함된 대형 플랫폼 | 4–16 | AutoThrottle 또는 적응형 | 429 및 5xx 응답 비율이 안정적일 때만 증가 |
| 공용 API | 문서화된 제한 | 문서대로 | 제공자가 주는 API 키와 제한을 사용하세요 |
동시성을 자동으로 조절하려면 어떻게 하나요?
TCP 혼잡 제어 규칙을 사용해 가감법을 적용하세요: 정상 응답이 연속되면 슬롯을 하나씩 추가하고 429 또는 503 응답 시 제한을 절반으로 줄입니다. 아래 제한기는 각 호스트별 제한을 관리하고 해당 호스트로의 요청을 Retry-After 지연 후 일시 중지하며, 한 번 재시도하고 설정한 최대 한도를 초과하지 않습니다. 네트워크 오류는 값으로 반환되어 한 번의 타임아웃이 배치를 중단하지 않습니다.
import asyncio
import random
from urllib.parse import urlsplit
import httpx
PROXY = "http://USERNAME-country-us:[email protected]:7777"
class AdaptiveLimit:
"""One per host: +1 slot after 20 clean responses, halve on 429/503, pause for Retry-After."""
def __init__(self, start=2, low=1, high=16):
self.limit, self.low, self.high = start, low, high
self.in_flight, self.clean = 0, 0
self.pause_until = 0.0
self.cond = asyncio.Condition()
async def __aenter__(self):
loop = asyncio.get_running_loop()
while True:
async with self.cond:
await self.cond.wait_for(lambda: self.in_flight < self.limit)
delay = self.pause_until - loop.time()
if delay <= 0:
self.in_flight += 1
return self
await asyncio.sleep(delay) # the host asked for a pause: wait outside the lock
async def __aexit__(self, *exc):
async with self.cond:
self.in_flight -= 1
self.cond.notify_all()
def record(self, status, retry_after=None):
if status in (429, 503):
self.limit, self.clean = max(self.low, self.limit // 2), 0
wait = int(retry_after) if retry_after and retry_after.isdigit() else 5 + random.random()
now = asyncio.get_running_loop().time()
self.pause_until = max(self.pause_until, now + wait)
elif status < 400:
self.clean += 1
if self.clean >= 20 and self.limit < self.high:
self.limit, self.clean = self.limit + 1, 0
gates = {}
def gate_for(url):
host = urlsplit(url).hostname
if host not in gates:
gates[host] = AdaptiveLimit()
return gates[host]
async def fetch(client, url, attempts=2):
gate = gate_for(url)
status = None
for _ in range(attempts):
try:
async with gate:
r = await client.get(url)
except httpx.HTTPError as exc:
return "error: %s" % type(exc).__name__
status = r.status_code
gate.record(status, r.headers.get("Retry-After"))
if status not in (429, 503):
break # the retry, if any, waits for the host's pause in __aenter__
return status
async def main(urls):
timeout = httpx.Timeout(30.0, connect=10.0)
async with httpx.AsyncClient(proxy=PROXY, timeout=timeout) as client:
print(await asyncio.gather(*(fetch(client, u) for u in urls)))
print({host: gate.limit for host, gate in gates.items()})
asyncio.run(main(["https://example.com/"] * 10))대상 외에 프록시 크롤링 속도를 느리게 하는 원인은?
- TLS 핸드셰이크. 매 요청마다 새 연결을 생성하면(새 IP가 필요할 때) 사이트와 매번 핸드셰이크가 발생합니다. IP 다양성이 중요하지 않다면 하나 사이트 내 여러 페이지에서 지속 연결(keep-alive)로 세션 유지하세요.
- 느린 출구. 레지덴셜 프록시와 4G 모바일 프록시는 실제 가정 및 셀룰러 링크입니다. 타임아웃과 새 연결 재시도를 사용하세요.
- 좁은 타깃팅. 소도시나 단일 ASN은 국가보다 출구가 적습니다. 데이터가 변할 때만 좁게 타깃팅하세요.
- 렌더링. 헤드리스 브라우저는 HTTP 클라이언트보다 페이지당 CPU, 메모리, 트래픽 비용이 훨씬 높습니다. 자바스크립트가 필요한 페이지만 렌더링하세요.
- DNS.
socks5h://또는 HTTP 포트로 DNS는 출구에서 해석됩니다; 대상 호스트에 로컬 DNS 캐시가 필요 없습니다.
동시성 관련 질문
CrawlRelay가 스레드나 연결 수를 제한하나요?
요금제는 트래픽 GB당 청구되며, 스레드, 연결, 요청당 청구가 아니며 공개 요금제에는 스레드 수 제한이 없습니다. 실질적 제한은 각 대상의 허용치와 자신의 기기 능력입니다. 대규모 크롤링 계획 시 [email protected]에 먼저 문의해 타깃 커버리지를 확인하세요.
스레드를 더 추가하니 크롤이 느려진 이유는?
대부분 대상이 제한을 걸기 시작했기 때문입니다: 응답 시간이 늘고 429 또는 503 응답이 나타나며 재시도가 부하를 가중시킵니다. 또 다른 원인은 헤드리스 브라우저를 쓴 기기의 CPU 또는 소켓 부족입니다. 스레드 추가 전 호스트별 지연과 오류율을 측정하세요.
요청당 한 연결이 keep-alive보다 느린가요?
네, 요청마다 TCP와 TLS 핸드셰이크가 추가되어 일반적으로 레지덴셜 프록시 출구에서 수백 밀리초 지연됩니다. 이는 매 요청마다 새 IP를 쓰는 대가입니다. IP가 중요하지 않은 동일 사이트 내 다수 페이지에는 지속 연결이 더 빠르고 트래픽도 적게 씁니다.
병렬 크롤러에서 robots.txt의 crawl-delay를 어떻게 준수하나요?
호스트당 robots.txt를 한 번 파싱하고, RFC 9309 권고처럼 최대 24시간 캐시하며, Crawl-delay 값을 각 호스트별 요청 최소 간격(동시성 1 기준)으로 변환하세요. Python의 urllib.robotparser는 이를 crawl_delay()에 노출합니다.
GB당 과금으로 확장하세요
US$0.50/GB에서 50 GB에서 1 TB까지, 만료 없는 트래픽.