비율 제한은 파서가 중단되거나 API 통합이 끊기고 자동화된 스크립트가 429 Too Many Requests 상태를 받는 가장 흔한 이유 중 하나입니다. 서버는 하나의 IP에서 너무 많은 요청을 감지하고 응답을 중단합니다. 이 기사에서는 프록시를 사용하여 차단 없이 요청 한도를 우회하는 방법을 올바르게 설정하는 방법을 다루며, Python 및 Node.js의 실제 코드 예제를 제공합니다.
비율 제한이란 무엇이며 일반적인 지연이 도움이 되지 않는 이유
비율 제한(요청 빈도 제한)은 특정 시간 동안 하나의 출처에서 발생하는 요청 수를 제한하는 서버 보호 메커니즘입니다. 출처는 일반적으로 IP 주소이지만, 고급 시스템은 인증 토큰, User-Agent, 쿠키 및 심지어 행동 패턴도 고려합니다.
스크립트가 한도를 초과하면 서버는 다음 중 하나의 응답을 반환합니다:
429 Too Many Requests— 비율 제한에 대한 표준 HTTP 상태503 Service Unavailable— 때때로 429 대신 사용됨403 Forbidden— IP가 이미 차단 목록에 있는 경우- 빈 응답 또는 타임아웃 — 공격적인 차단 시
대부분의 개발자들이 가장 먼저 생각하는 것은 요청 사이에 time.sleep(1)을 추가하는 것입니다. 이는 매우 부드러운 한도(예: 분당 60 요청)에서만 작동합니다. 그러나 실제 시나리오는 더 복잡합니다:
인기 플랫폼의 실제 한도:
- Twitter/X API (무료): 월 500,000 트윗, 하지만 15분마다 15 요청 초과 불가
- Google 검색: 인증 없이 하나의 IP에서 약 100 요청/일
- Wildberries, Ozon: 공격적인 비율 제한 — 분당 30–50 요청 후 차단
- GitHub API: 토큰 없이 60 요청/시간, 토큰으로 5000 요청/시간
- Cloudflare 보호 사이트: 분당 10–20 요청 후 차단 가능
마켓플레이스에서 100,000개의 상품 카드를 수집하거나 실시간으로 가격을 모니터링해야 하는 경우 — 지연은 도움이 되지 않습니다. 다른 아키텍처가 필요합니다. 바로 여기서 프록시는 선택이 아니라 필수가 됩니다.
비율 제한은 IP 주소에 연결되어 있다는 점을 이해하는 것이 중요합니다. 100개의 서로 다른 IP가 있다면 — 사실상 100개의 독립적인 "쿼터"가 있는 것입니다. 이것이 바로 프록시를 통한 제한 우회의 핵심 원칙입니다.
프록시가 요청 제한 문제를 해결하는 방법
메커니즘은 간단합니다: 대상 서버에 대한 각 요청은 서로 다른 IP 주소를 통해 전송됩니다. 서버의 입장에서는 서로 다른 사용자입니다. 각 사용자의 쿼터는 거의 소모되지 않으므로 차단이 발생하지 않습니다.
프록시 없이 작업하는 것과 프록시 풀을 사용하는 것의 차이를 구체적인 예를 통해 살펴보겠습니다. 서버가 하나의 IP에서 분당 10 요청을 허용한다고 가정해 보겠습니다:
| 시나리오 | 분당 요청 수 | 차단 | 10,000 요청 소요 시간 |
|---|---|---|---|
| 하나의 IP, 프록시 없음 | 10 | 예, 10 요청 후 | 약 16시간 |
| 10 프록시, 회전 | 100 | 아니오 | 약 1.7시간 |
| 100 프록시, 회전 | 1000 | 아니오 | 약 10분 |
대역폭을 확장하는 것 외에도, 프록시는 비율 제한 작업 시 몇 가지 추가 이점을 제공합니다:
- 세션 격리 — 하나의 IP가 차단되면 나머지는 계속 작동
- 지리적 분산 — 요청이 다양한 지역에서 발생하여 의심을 줄임
- 스티키 세션 — 다단계 시나리오(인증 + 작업)를 위해 하나의 IP에 "붙어있기" 가능
- 부하 제어 — 각 IP에 대한 요청을 정확히 조절하여 한도를 초과하지 않음
귀하의 작업에 적합한 프록시 유형 선택하기
모든 프록시가 비율 제한에 대해 동일하게 효과적이지는 않습니다. 유형 선택은 대상 사이트, 요청량 및 예산에 따라 다릅니다. 세 가지 주요 유형을 살펴보겠습니다:
주거용 프록시
실제 가정 사용자의 IP 주소입니다. 일반적인 인터넷 트래픽처럼 보이며 차단되는 경우가 매우 드뭅니다. 주거용 프록시는 공격적인 보호가 있는 사이트에 최적의 선택입니다: 마켓플레이스(Wildberries, Ozon), 소셜 미디어, Cloudflare 보호 리소스. 주요 단점은 데이터 센터 프록시보다 가격이 더 비쌉니다.
모바일 프록시
이동통신사의 IP 주소(3G/4G/5G). 이들의 특징은 하나의 IP가 수천 명의 실제 가입자에 의해 동시에 사용될 수 있으므로, 이러한 주소를 차단하려는 사이트는 극히 원하지 않습니다. 모바일 프록시는 주거용 프록시가 차단되기 시작하는 곳에서 가장 좋은 결과를 보여줍니다 — 예를 들어, Instagram의 고주파 파싱이나 연결 유형을 분석하는 플랫폼의 API 작업 시.
데이터 센터 프록시
빠르고 저렴한 서버 데이터 센터의 IP입니다. 심각한 보호가 없는 사이트의 파싱에 적합합니다: 공개 API, 뉴스 집계기, 공개 데이터베이스. 비율 제한이 있는 작업에는 더 많은 수가 필요하지만(차단 목록에 자주 올라가기 때문에), 올바른 회전을 통해 대량의 요청을 잘 처리할 수 있습니다. 자세한 내용은 데이터 센터 프록시 페이지를 참조하세요.
| 프록시 유형 | 익명성 | 속도 | 가격 | 최고의 시나리오 |
|---|---|---|---|---|
| 주거용 | 매우 높음 | 중간 | $$ | 마켓플레이스, 소셜 미디어, Cloudflare |
| 모바일 | 최대 | 중간 | $$$ | Instagram API, 고주파 파싱 |
| 데이터 센터 | 중간 | 높음 | $ | 공개 API, 공개 데이터 |
IP 회전 전략: 요청당, 스티키 세션, 라운드 로빈
프록시가 있다는 사실만으로는 문제가 해결되지 않습니다 — 올바르게 관리하는 것이 중요합니다. 여러 가지 회전 전략이 있으며, 각 전략은 특정 시나리오에 적합합니다.
요청당 회전 (각 요청에 새로운 IP)
각 HTTP 요청은 새로운 IP 주소를 통해 전송됩니다. 이는 비율 제한을 우회하는 가장 공격적인 전략입니다 — 서버는 하나의 IP에 대한 카운터를 축적할 시간이 없습니다. 다음과 같은 경우에 적합합니다:
- 상품 카드 파싱(각 카드가 개별 요청)
- 검색 엔진에서 데이터 수집
- 세션이 필요 없는 모든 stateless 요청
스티키 세션 (세션 동안 고정 IP)
하나의 IP가 세션 전체에 걸쳐 사용됩니다(일반적으로 1–30분). 인증이 필요한 시나리오에서는 매우 중요합니다: 계정에 로그인, 작업 수행, 로그아웃. 단계 간에 IP가 변경되면 서버가 세션을 의심스러운 것으로 차단할 수 있습니다.
요청 수에 한도를 둔 라운드 로빈
가장 정확한 전략입니다. 서버의 한도를 알고(예: 분당 10 요청) 요청을 프록시 풀에 분배하여 각 IP가 이 한도를 초과하지 않도록 합니다. 각 IP에 대한 마지막 요청 시간을 고려한 큐를 구현해야 합니다.
필요한 프록시 수 계산 공식:
N 프록시 = (목표 요청 속도/분) ÷ (IP당 서버 한도/분)
예: 분당 500 요청이 필요하고, 서버 한도가 10/분이라면 → 최소 50개의 프록시가 필요합니다.
차단을 대비해 20% 여유를 추가하세요: 총 60개의 프록시.
Python 코드 예제: requests, aiohttp, Scrapy
이제 실습으로 넘어가겠습니다. 아래는 세 가지 가장 인기 있는 Python 도구에 대한 준비된 템플릿입니다.
1. requests + 수동 프록시 회전
가장 간단한 방법은 프록시 목록과 각 요청에 대해 무작위 선택하는 것입니다:
import requests
import random
import time
PROXIES = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
# ... 필요한 수만큼 추가
]
def get_random_proxy():
proxy = random.choice(PROXIES)
return {"http": proxy, "https": proxy}
def fetch_with_retry(url, max_retries=3):
for attempt in range(max_retries):
proxy = get_random_proxy()
try:
response = requests.get(
url,
proxies=proxy,
timeout=10,
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}
)
if response.status_code == 429:
print(f"{proxy}에서 비율 제한됨, 전환 중...")
time.sleep(1)
continue
return response
except requests.RequestException as e:
print(f"{attempt+1}번째 시도 실패: {e}")
time.sleep(2)
return None
# 사용 예
urls = ["https://example.com/item/1", "https://example.com/item/2"]
for url in urls:
result = fetch_with_retry(url)
if result:
print(f"OK: {url} — {len(result.text)} 바이트")
2. 비율 한도를 고려한 스마트 프록시 풀
더 발전된 방법은 각 IP의 마지막 사용 시간을 추적하고 설정된 한도를 초과하지 않는 ProxyPool 클래스를 사용하는 것입니다:
import requests
import time
from collections import defaultdict
from threading import Lock
class ProxyPool:
def __init__(self, proxies, rate_limit=10, window=60):
"""
proxies: 'http://user:pass@host:port' 형식의 문자열 목록
rate_limit: IP당 window 초 동안 최대 요청 수
window: 초 단위의 시간 창
"""
self.proxies = proxies
self.rate_limit = rate_limit
self.window = window
self.usage = defaultdict(list) # proxy -> [timestamps]
self.lock = Lock()
def get_available_proxy(self):
now = time.time()
with self.lock:
for proxy in self.proxies:
# 오래된 타임스탬프 정리
self.usage[proxy] = [
t for t in self.usage[proxy]
if now - t < self.window
]
if len(self.usage[proxy]) < self.rate_limit:
self.usage[proxy].append(now)
return {"http": proxy, "https": proxy}
return None # 모든 프록시가 한도를 초과함
def fetch(self, url, **kwargs):
proxy = self.get_available_proxy()
if proxy is None:
print("모든 프록시가 비율 제한됨, 대기 중...")
time.sleep(5)
return self.fetch(url, **kwargs)
try:
response = requests.get(url, proxies=proxy, timeout=10, **kwargs)
return response
except requests.RequestException as e:
print(f"요청 실패: {e}")
return None
# 사용 예
pool = ProxyPool(
proxies=[
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
],
rate_limit=10, # IP당 분당 10 요청
window=60
)
for i in range(100):
r = pool.fetch(f"https://example.com/page/{i}")
if r:
print(f"페이지 {i}: {r.status_code}")
3. 비동기 파싱을 위한 aiohttp
비동기 접근 방식은 수십 개의 프록시를 동시에 사용하여 스레드를 차단하지 않고 작업할 수 있습니다:
import asyncio
import aiohttp
import itertools
PROXIES = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
proxy_cycle = itertools.cycle(PROXIES)
async def fetch(session, url, proxy):
try:
async with session.get(
url,
proxy=proxy,
timeout=aiohttp.ClientTimeout(total=10)
) as response:
if response.status == 429:
await asyncio.sleep(2)
return None
return await response.text()
except Exception as e:
print(f"오류: {e}")
return None
async def main(urls):
connector = aiohttp.TCPConnector(limit=50)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = [
fetch(session, url, next(proxy_cycle))
for url in urls
]
results = await asyncio.gather(*tasks)
return results
urls = [f"https://example.com/item/{i}" for i in range(200)]
results = asyncio.run(main(urls))
print(f"수집된 페이지 수: {sum(1 for r in results if r is not None)}")
4. 미들웨어를 통한 Scrapy의 회전
Scrapy에는 준비된 솔루션인 scrapy-rotating-proxies가 있습니다. 그러나 자체 미들웨어를 작성할 수도 있습니다:
# middlewares.py
import random
class RotatingProxyMiddleware:
def __init__(self, proxies):
self.proxies = proxies
@classmethod
def from_crawler(cls, crawler):
return cls(proxies=crawler.settings.getlist("PROXY_LIST"))
def process_request(self, request, spider):
proxy = random.choice(self.proxies)
request.meta["proxy"] = proxy
def process_response(self, request, response, spider):
if response.status == 429:
spider.logger.warning(f"비율 제한됨, 프록시: {request.meta.get('proxy')}")
# 문제 있는 프록시를 제외하는 로직을 추가할 수 있습니다.
return response
# settings.py
PROXY_LIST = [
"http://user:[email protected]:8080",
"http://user:[email protected]:8080",
]
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.RotatingProxyMiddleware": 350,
}
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 10
Node.js 코드 예제: axios, got, Puppeteer
Node.js는 브라우저 자동화 및 API 작업을 위한 인기 있는 선택입니다. 다음은 프록시 작업을 위한 준비된 패턴입니다.
1. axios와 프록시 회전
const axios = require('axios');
const { HttpsProxyAgent } = require('https-proxy-agent');
const proxies = [
'http://user:[email protected]:8080',
'http://user:[email protected]:8080',
'http://user:[email protected]:8080',
];
let proxyIndex = 0;
function getNextProxy() {
const proxy = proxies[proxyIndex % proxies.length];
proxyIndex++;
return proxy;
}
async function fetchWithProxy(url, retries = 3) {
for (let i = 0; i < retries; i++) {
const proxyUrl = getNextProxy();
const agent = new HttpsProxyAgent(proxyUrl);
try {
const response = await axios.get(url, {
httpsAgent: agent,
httpAgent: agent,
timeout: 10000,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
},
});
return response.data;
} catch (error) {
if (error.response?.status === 429) {
console.log(`비율 제한됨, 프록시 전환 중...`);
await new Promise(r => setTimeout(r, 1000));
continue;
}
console.error(`${i + 1}번째 시도 실패:`, error.message);
}
}
return null;
}
// 사용 예
(async () => {
const urls = Array.from({length: 50}, (_, i) => `https://example.com/item/${i}`);
const results = await Promise.allSettled(
urls.map(url => fetchWithProxy(url))
);
const successful = results.filter(r => r.status === 'fulfilled' && r.value).length;
console.log(`성공: ${successful}/${urls.length}`);
})();
2. Puppeteer와 프록시 및 비율 제한 우회
JavaScript 렌더링 및 Cloudflare 보호가 있는 사이트에는 헤드리스 브라우저가 필요합니다:
const puppeteer = require('puppeteer');
const proxies = [
'proxy1.example.com:8080',
'proxy2.example.com:8080',
];
async function scrapeWithProxy(url, proxyHost) {
const browser = await puppeteer.launch({
args: [
`--proxy-server=${proxyHost}`,
'--no-sandbox',
'--disable-setuid-sandbox',
],
headless: true,
});
const page = await browser.newPage();
// 프록시 인증
await page.authenticate({
username: 'user',
password: 'pass',
});
// 현실적인 User-Agent 설정
await page.setUserAgent(
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
);
try {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
// 비율 제한 확인
const status = await page.evaluate(() => document.title);
if (status.includes('429') || status.includes('Too Many')) {
console.log('비율 제한됨, 프록시 전환 필요');
return null;
}
const data = await page.evaluate(() => {
return document.querySelector('.price')?.textContent || null;
});
return data;
} finally {
await browser.close();
}
}
// 작업에 따라 회전
(async () => {
const urls = ['https://example.com/product/1', 'https://example.com/product/2'];
for (let i = 0; i < urls.length; i++) {
const proxy = proxies[i % proxies.length];
const result = await scrapeWithProxy(urls[i], proxy);
console.log(`${urls[i]}: ${result}`);
await new Promise(r => setTimeout(r, 500)); // 짧은 지연
}
})();
고급 기술: 헤더, 지문, Cloudflare 우회
IP 변경은 필요하지만 항상 충분한 조건은 아닙니다. 현대의 보호 시스템은 요청의 수십 가지 매개변수를 분석합니다. 무엇을 추가로 고려해야 하는지 살펴보겠습니다.
HTTP 헤더: 최소 필수 세트
정상적인 헤더 없이 요청하면 IP를 변경하더라도 봇처럼 보입니다. 항상 추가하세요:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "none",
"Cache-Control": "max-age=0",
}
Retry-After 헤더 처리
429 응답 시 서버는 대기해야 할 시간을 종종 지정합니다. 이 헤더를 올바르게 처리하면 요청을 낭비하지 않을 수 있습니다:
def handle_rate_limit(response):
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
if retry_after:
wait_time = int(retry_after)
print(f"비율 제한됨. {wait_time}초 대기 중...")
time.sleep(wait_time + 1) # +1초 버퍼
else:
# 헤더가 없을 경우 지수 대기
time.sleep(min(2 ** attempt, 60))
return True
return False
TLS 지문 및 우회 방법
고급 시스템(Cloudflare, Akamai, PerimeterX)은 TLS 연결의 고유한 "지문"을 분석합니다. 표준 requests 라이브러리는 쉽게 인식할 수 있는 지문을 가지고 있습니다. 해결책:
- curl_cffi (Python) — TLS 수준에서 Chrome/Firefox의 지문을 에뮬레이트
- tls-client (Go/Python) — 다양한 브라우저 프로필을 지원하는 유사한 도구
- Playwright/Puppeteer — 실제 브라우저, 기본적으로 이상적인 지문
# pip install curl-cffi
from curl_cffi import requests as cffi_requests
response = cffi_requests.get(
"https://cloudflare-protected-site.com/api/data",
impersonate="chrome120", # Chrome 120 에뮬레이트
proxies={"https": "http://user:[email protected]:8080"}
)
print(response.json())
쿠키 및 세션 관리
사이트가 세션 추적을 위해 쿠키를 사용하는 경우, 쿠키를 변경하지 않고 IP를 변경하는 것은 무의미합니다. 프록시를 전환할 때마다 새로운 세션을 생성하세요:
import requests
def create_fresh_session(proxy_url):
"""각 프록시에 대해 깨끗한 쿠키로 새로운 세션 생성"""
session = requests.Session()
session.proxies = {"http": proxy_url, "https": proxy_url}
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
})
# 이전 세션의 쿠키는 전송되지 않음
return session
# 각 새로운 IP에 대해 새로운 세션
for proxy in proxies:
session = create_fresh_session(proxy)
response = session.get("https://example.com/protected-page")
# 응답 처리...
프록시 및 비율 제한 작업 시 일반적인 실수
올바르게 설정된 프록시를 사용하더라도 개발자들은 종종 같은 실수를 반복합니다. 가장 흔한 실수와 이를 피하는 방법은 다음과 같습니다.
체크리스트: 파서를 실행하기 전에 확인할 사항
- ☐ 현실적인 HTTP 헤더가 추가됨 (User-Agent, Accept, Accept-Language)
- ☐ 프록시를 변경할 때 새로운 세션이 생성됨 (새로운 쿠키)
- ☐ 429, 503, 403 상태가 재시도 로직으로 처리됨
- ☐ 요청 사이에 지연이 구현됨 (최소 100–500ms)
- ☐ 프록시 수가 목표 요청 속도에 맞음
- ☐ 시작 전에 프록시 작동 확인 (헬스 체크)
- ☐ 각 프록시에 대한 오류 및 통계가 기록됨
- ☐ 요청에 대한 타임아웃이 설정됨 (15–30초 이내)
오류 1: "죽은" 프록시 사용
풀에 추가하기 전에 항상 프록시를 확인하고 작업 중에도 주기적으로 확인하세요. 하나의 작동하지 않는 프록시는 요청 손실 및 타임아웃을 초래합니다:
def check_proxy(proxy_url, test_url="https://httpbin.org/ip", timeout=5):
try:
r = requests.get(
test_url,
proxies={"http": proxy_url, "https": proxy_url},
timeout=timeout
)
return r.status_code == 200
except:
return False
# 시작 전에 작동하는 프록시 필터링
working_proxies = [p for p in PROXIES if check_proxy(p)]
print(f"작동하는 프록시: {len(working_proxies)}/{len(PROXIES)}")
오류 2: 프로토콜 유형 무시
HTTP 프록시는 HTTPS 트래픽을 직접 프록시할 수 없습니다(오직 CONNECT를 통해서만). SOCKS5 프록시는 전송 수준에서 작동하며 모든 프로토콜을 지원합니다. 대부분의 현대 사이트에서는 SOCKS5 또는 HTTPS 프록시를 사용하세요:
# requests에서 SOCKS5 프록시 사용 (pip install requests[socks] 필요)
proxies = {
"http": "socks5://user:[email protected]:1080",
"https": "socks5://user:[email protected]:1080",
}
# HTTPS 프록시
proxies = {
"http": "https://user:[email protected]:8080",
"https": "https://user:[email protected]:8080",
}
오류 3: 지수 백오프 없음
429 이후 즉시 요청을 반복하면 상황이 악화됩니다. 올바른 전략은 지수 대기와 지터(무작위 편차)를 사용하는 것입니다:
import random
def exponential_backoff(attempt, base=1, max_wait=60):
"""
attempt: 시도 번호 (0부터 시작)
base: 기본 대기 시간(초)
max_wait: 최대 대기 시간
"""
wait = min(base * (2 ** attempt), max_wait)
# 지터 ±25%로 천둥 무리 방지
jitter = wait * 0.25 * random.uniform(-1, 1)
return wait + jitter
# 재시도 로직에서 사용
for attempt in range(5):
response = requests.get(url, proxies=proxy)
if response.status_code == 429:
wait = exponential_backoff(attempt)
print(f"비율 제한됨. {wait:.1f}초 대기 중 (시도 {attempt+1})")
time.sleep(wait)
else:
break
오류 4: 모든 프록시에 하나의 스레드 사용
50개의 프록시가 있지만 실행 스레드가 하나뿐이라면 동시에 최대 1개의 프록시만 사용하고 있는 것입니다. ThreadPoolExecutor 또는 비동기 접근 방식을 사용하여 전체 풀을 병렬로 사용하세요:
from concurrent.futures import ThreadPoolExecutor, as_completed
def fetch_url(args):
url, proxy = args
try:
r = requests.get(url, proxies={"https": proxy}, timeout=10)
return url, r.status_code, len(r.text)
except Exception as e:
return url, None, str(e)
# 모든 프록시를 병렬로 사용
tasks = [(url, proxies[i % len(proxies)]) for i, url in enumerate(urls)]
with ThreadPoolExecutor(max_workers=len(proxies)) as executor:
futures = {executor.submit(fetch_url, task): task for task in tasks}
for future in as_completed(futures):
url, status, size = future.result()
print(f"{url}: {status} ({size})")
결론 및 권장 사항
비율 제한은 체계적으로 접근하면 해결할 수 있는 문제입니다. 이 가이드의 주요 결론은 다음과 같습니다:
- 프록시 풀, 단일 프록시가 아닌 — 진지한 작업을 위한 최소 단위입니다. 프록시 수는 공식에 따라 결정됩니다: 목표 속도 ÷ IP당 서버 한도.
- 회전 전략이 중요합니다 — stateless 요청의 경우 요청당, 인증된 시나리오의 경우 스티키 세션.
- IP는 유일한 매개변수가 아닙니다 — 헤더, 쿠키, TLS 지문 및 행동 패턴도 보호 시스템에 의해 분석됩니다.
- 429를 올바르게 처리하세요 — 지수 백오프, Retry-After 헤더, 차단 시 프록시 전환.
- 프록시 유형은 목표에 따라 다릅니다 — 공개 API에는 데이터 센터 프록시, 마켓플레이스에는 주거용 프록시, 최대 보호가 필요한 경우에는 모바일 프록시를 사용하세요.
마켓플레이스(Wildberries, Ozon) 파싱, 보호된 API에서 데이터 수집 또는 높은 속도로 자동화를 작업하는 경우 주거용 프록시로 시작하는 것이 좋습니다: 이들은 익명성과 속도 간의 최적의 균형을 제공하며, 이들의 IP 주소는 차단 목록에 거의 올라가지 않습니다. 높은 빈도의 요청에서 차단에 대한 최대 저항이 필요한 작업의 경우, 모바일 프록시를 고려하세요 — 이들의 IP는 수천 명의 실제 사용자와 공유되므로 어떤 사이트에서도 차단하기 매우 원치 않습니다.