Scrapy 프록시 미들웨어: 완벽 설정 가이드 (2026)

2026년 6월 4일 게시 · 약 11분 소요

Scrapy는 2026년에도 여전히 프로덕션 크롤의 일꾼입니다 — 그리고 여전히 프록시 설정이 사람들을 가장 혼란스럽게 하는 프레임워크입니다. 프록시를 꽂을 수 있는 자리가 네 군데나 있는데 그중 셋은 대부분의 프로젝트에 맞지 않기 때문입니다. 이 가이드는 올바른 한 가지를 알려 드립니다: per-request 라우팅, sticky 세션, ban 탐지, 합리적인 재시도 동작을 갖춘 작은 커스텀 미들웨어.

대신 순수 requests/httpx/aiohttp를 쓰고 있다면 Python에서 프록시 로테이션하는 법을 참고하세요. 이 글은 Scrapy 전용입니다.

30초 버전

로테이팅 residential 게이트웨이라면, 최소한의 동작 가능한 설정은 요청당 한 줄입니다 — 미들웨어가 필요 없습니다:

def start_requests(self):
    for url in self.urls:
        yield scrapy.Request(
            url,
            meta={"proxy": "http://USERNAME:[email protected]:913"},
        )

Scrapy에 내장된 HttpProxyMiddlewarerequest.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은 충분히 이르고 예측 가능하게 유지해 줍니다.

Sticky vs Rotating: 어떤 스파이더에 어떤 모드를

크롤 유형모드구현
상태 없는 페이지 수집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를 태워 먹는 방식입니다.

대역폭 절제 (Residential GB는 곧 돈입니다)

흔한 실패와 그것이 실제로 의미하는 것

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로 확인하세요.

스파이더가 10분간 잘 돌다가 갑자기 전부 403

sticky 세션이 환영받는 기한을 넘겼거나, 도메인별 속도가 너무 뜨겁습니다. 위의 ban 인지 미들웨어가 첫 번째 경우를 처리하고, 두 번째는 CONCURRENT_REQUESTS_PER_DOMAIN을 낮추세요. JA4를 검사하는 대상이라면 Scrapy의 TLS 스택 자체가 단서일 수 있습니다 — 왜 어떤 프록시로도 그걸 못 고치는지는 JA3/JA4 설명을 보세요.

무료 도구 · 가입 불필요

크롤 전에 프록시 목록을 검증하세요

엔드포인트를 Proxy Checker에 붙여넣으세요: 연결성, 지연시간, 익명성 수준, exit IP 유형을 일괄로 테스트합니다 — Scrapy가 재시도를 낭비하기 전에 죽었거나 잘못 분류된 프록시를 잡아냅니다.

내 프록시 확인하기 →

무료 목록을 돌보는 데 지치셨나요? residential 게이트웨이 하나가 그 모든 것을 대체합니다 — 500MB 무료 트래픽 받기 →

요약

스파이더를 진짜 풀로 향하게 하세요

하나의 게이트웨이에서 rotating과 sticky residential 세션 모두. 크롤에 쓸 500MB 무료 트래픽.

무료 체험 시작

모든 IP 제품 · 언제든 이용 가능한 방대한 노드 풀

지금 가입하고 충전 금액의 최대 100%를 돌려받으세요

신규 사용자는 가입 시 500MB를 받고, 첫 충전 시 추가 보너스를 받습니다. 기간 한정 혜택입니다.