Scrapy는 2026년에도 여전히 프로덕션 크롤의 일꾼입니다 — 그리고 여전히 프록시 설정이 사람들을 가장 혼란스럽게 하는 프레임워크입니다. 프록시를 꽂을 수 있는 자리가 네 군데나 있는데 그중 셋은 대부분의 프로젝트에 맞지 않기 때문입니다. 이 가이드는 올바른 한 가지를 알려 드립니다: per-request 라우팅, sticky 세션, ban 탐지, 합리적인 재시도 동작을 갖춘 작은 커스텀 미들웨어.
대신 순수 requests/httpx/aiohttp를 쓰고 있다면 Python에서 프록시 로테이션하는 법을 참고하세요. 이 글은 Scrapy 전용입니다.
로테이팅 residential 게이트웨이라면, 최소한의 동작 가능한 설정은 요청당 한 줄입니다 — 미들웨어가 필요 없습니다:
def start_requests(self):
for url in self.urls:
yield scrapy.Request(
url,
meta={"proxy": "http://USERNAME:[email protected]:913"},
)
Scrapy에 내장된 HttpProxyMiddleware는 request.meta["proxy"]를 읽고 URL에서 인증을 처리합니다. 게이트웨이가 exit IP를 대신 로테이션해 줍니다. 필요한 게 이게 전부라면 여기서 멈추세요. 이 가이드의 나머지는 제어가 필요할 때를 위한 것입니다: sticky 세션, 국가 라우팅, ban을 인지하는 로테이션, 동시성 튜닝.
이것을 middlewares.py에 넣으세요. 도메인별로 sticky 세션을 할당하고, ban 시 로테이션하며, 어느 세션이 무엇을 가져왔는지 디버깅할 수 있도록 모든 요청에 태그를 답니다:
import random
import string
GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME" # move to settings.py / env in real projects
PASSWORD = "PASSWORD"
def _new_session(n=8):
return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))
class JibaoProxyMiddleware:
"""Sticky session per domain; rotate session on ban."""
def __init__(self):
self.sessions = {} # domain -> session id
def _proxy_url(self, session_id):
user = f"{USERNAME}-session-{session_id}"
return f"http://{user}:{PASSWORD}@{GATEWAY}"
def process_request(self, request, spider):
domain = request.url.split("/")[2]
session = self.sessions.setdefault(domain, _new_session())
request.meta["proxy"] = self._proxy_url(session)
request.meta["proxy_session"] = session
def rotate(self, domain):
"""Call when a session is burned."""
self.sessions[domain] = _new_session()
그리고 ban을 탐지해 새 세션에서 재시도하는 동반 downloader 미들웨어:
from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message
BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")
class BanAwareRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
banned = (
response.status in BAN_CODES
or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
)
if banned:
domain = request.url.split("/")[2]
proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
for mw in proxy_mw:
if hasattr(mw, "rotate"):
mw.rotate(domain) # burn the session
reason = response_status_message(response.status)
return self._retry(request, reason, spider) or response
return super().process_response(request, response, spider)
둘 다 settings.py에서 연결하세요:
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.JibaoProxyMiddleware": 350,
"scrapy.downloadermiddlewares.retry.RetryMiddleware": None, # replace stock retry
"myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2
우선순위가 중요합니다: 프록시 미들웨어는 Scrapy의 HttpProxyMiddleware(750)보다 먼저 실행되어야 하므로 750 미만이면 무엇이든 동작합니다. 350은 충분히 이르고 예측 가능하게 유지해 줍니다.
| 크롤 유형 | 모드 | 구현 |
|---|---|---|
| 상태 없는 페이지 수집 | Rotating | 순수 username, 게이트웨이가 요청마다 로테이션 |
| 로그인 + 인증 뒤 크롤 | 계정별 Sticky | -session-{account_id}, 로그인 도중 절대 로테이션하지 않음 |
| 페이지네이션이 많은 목록 | 도메인별 Sticky, ban 시 로테이션 | 위의 미들웨어 |
| 지역별 가격 | Rotating + 국가 고정 | USERNAME-country-de 형식의 파라미터 |
이 트레이드오프에 대한 더 깊은 설명: Sticky vs Rotating 프록시 세션.
Scrapy 기본값은 단일 IP로 예의 바르게 크롤하도록 튜닝되어 있습니다. 로테이팅 풀 뒤에서는 훨씬 더 강하게 밀어붙일 수 있습니다 — 하지만 대상은 집계된 행동을 보기 때문에 도메인별 제한은 여전히 중요합니다:
# settings.py - sane starting point behind a residential pool
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # what the target experiences
DOWNLOAD_DELAY = 0.25 # jitter applied per slot
RANDOMIZE_DOWNLOAD_DELAY = True # 0.5x-1.5x the delay
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True
CONCURRENT_REQUESTS_PER_DOMAIN은 현재 수준에서 수천 건의 요청 동안 403 비율을 지켜본 뒤에만 올리세요. "어차피 프록시가 로테이션되니까"라며 8 → 32로 올리는 것이야말로 사람들이 재시도에 GB를 태워 먹는 방식입니다.
IMAGES_STORE 파이프라인이 datacenter 프록시나 직접 연결을 통해 가져오도록 설정하세요 — 미디어가 GB의 대부분을 차지하며 보호되는 경우는 드뭅니다.HTTPCACHE_ENABLED = True를 켜면 풀을 통해 다시 가져오는 대신 디스크에서 응답을 재생합니다. 이 설정 하나가 보통 프로젝트의 대역폭 비용을 절반으로 줄입니다.COMPRESSION_ENABLED = True(기본값)를 유지하세요 — gzip 처리된 HTML은 전송 시 5–10배 더 작습니다.407 Proxy Authentication Required자격 증명이 프록시에 도달하지 못했습니다. meta["proxy"]의 URL에 넣으세요(http://user:pass@host:port) — Scrapy가 이를 파싱해 Proxy-Authorization을 대신 설정합니다. 헤더를 수동으로 설정하면서 동시에 URL 자격 증명을 쓰면 이중 인증으로 인한 이상 동작이 발생합니다. 하나만 고르세요.
TunnelError: Could not open CONNECT tunnel거의 항상 host/port 오타이거나, 해당 port에서 CONNECT를 허용하지 않는 엔드포인트를 통한 HTTPS 대상입니다. 먼저 Scrapy 밖에서 curl -x로 확인하세요.
sticky 세션이 환영받는 기한을 넘겼거나, 도메인별 속도가 너무 뜨겁습니다. 위의 ban 인지 미들웨어가 첫 번째 경우를 처리하고, 두 번째는 CONCURRENT_REQUESTS_PER_DOMAIN을 낮추세요. JA4를 검사하는 대상이라면 Scrapy의 TLS 스택 자체가 단서일 수 있습니다 — 왜 어떤 프록시로도 그걸 못 고치는지는 JA3/JA4 설명을 보세요.
meta["proxy"]가 설정의 전부입니다.신규 사용자는 가입 시 500MB를 받고, 첫 충전 시 추가 보너스를 받습니다. 기간 한정 혜택입니다.