Scrapy 프록시 미들웨어: 차단 없이 프록시 회전하기

Scrapy는 버전 0.8부터 프록시 지원을 제공했지만, 문서에서는 대규모로 회전, 재시도 및 차단을 피하는 방법에 대해 설명하지 않습니다. 여기서는 미들웨어의 구조, 코드 및 크롤링 완료 여부를 결정하는 설정에 대해 설명합니다.

Scrapy는 버전 0.8부터 프록시 미들웨어를 제공해왔기 때문에 'Scrapy가 프록시를 지원하나요'라는 질문은 15년 전에 해결되었습니다. 문서에서 완전히 설명하지 않는 것은 실제 프로덕션 환경입니다: 내장된 Scrapy 프록시 미들웨어가 자격 증명을 실제로 처리하는 방법, 요청별로 프록시를 설정할 때와 전역적으로 설정할 때의 차이, 목표가 크롤링 중간에 출구를 차단하기 시작할 때 회전하고 복구하는 방법입니다. 이 가이드는 전체 체인을 설명합니다 — 내장된 HttpProxyMiddleware, 요청별 meta, 차단 처리 기능이 있는 사용자 정의 회전 미들웨어 및 10만 페이지 크롤링이 완료될지 새벽 3시에 중단될지를 결정하는 설정입니다.

내장된 Scrapy 프록시 미들웨어의 작동 방식

scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddlewareDOWNLOADER_MIDDLEWARES_BASE에서 기본적으로 우선순위 750으로 활성화됩니다. Scrapy 2.17에서 약 100줄의 소스를 읽으면 디버깅에 필요한 모든 정보를 얻을 수 있습니다:

실질적인 결과: 거의 제3자 프록시 패키지가 필요하지 않습니다. 우선순위 750 이전에 유효한 프록시 URL을 request.meta['proxy']에 넣는 것은 인증 및 헤더 처리를 무료로 제공합니다.

요청별 프록시와 메타

가장 가벼운 통합은 스파이더에서 meta를 직접 설정하는 것입니다 — 일부 요청에만 프록시가 필요하거나, 다른 목표가 다른 출구 국가를 필요로 할 때 유용합니다:

import scrapy


class PricesSpider(scrapy.Spider):
    name = "prices"

    def start_requests(self):
        yield scrapy.Request(
            "https://example.com/product/1",
            meta={"proxy": "http://USER:PASS@gate.quantumproxies.io:PORT"},
        )

요청별 메타는 프록시 선택 자체가 데이터 기반일 때 빛을 발합니다: 독일 제품 페이지를 독일 타겟 사용자 이름을 통해 라우팅하고, 이미지 다운로드는 저렴한 데이터 센터 출구를 통해 보내고, HTML은 주거용으로 보내거나, 두 번째 시도에서 고집스러운 URL을 더 끈적한 세션으로 승격합니다. 메타는 재시도 및 리디렉션을 통해 살아남기 때문에 요청을 만들 때 내린 결정이 전체 다운로드 사이클을 통해 따라옵니다. 수천 개의 요청에 대해 메타를 수동으로 설정하는 것은 확장되지 않습니다 — 이를 위해 다운로더 미들웨어가 존재합니다.

가장 간단한 프로덕션 설정: 회전 게이트웨이

단일 게이트웨이 엔드포인트 뒤에 회전 프록시를 사용하면 회전이 서버 측에서 발생합니다: 동일한 URL을 통해 모든 요청이 200개 이상의 국가에 걸친 90M+ 주거용 풀의 다른 IP에서 종료됩니다. 미들웨어는 세 줄로 줄어들고, 건강 검진, 가지치기 또는 새로 고침할 목록이 없습니다:

# middlewares.py
class RotatingGatewayMiddleware:
    PROXY = "http://USER:PASS@gate.quantumproxies.io:PORT"

    def process_request(self, request, spider):
        request.meta["proxy"] = self.PROXY


# settings.py
DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.RotatingGatewayMiddleware": 350,
}

우선순위 350이 중요합니다: 미들웨어는 750에서 내장된 것보다 먼저 실행되어야 하므로 HttpProxyMiddleware가 여전히 자격 증명을 인증 헤더로 변환할 수 있습니다. 이 조합 — 게이트웨이 회전과 Scrapy의 기본 재시도 기계 — 는 코드 한 줄당 가장 높은 신뢰성을 제공합니다, 왜냐하면 모든 재시도가 자동으로 새로운 출구 IP를 통해 이동하기 때문입니다.

Scrapy 프록시 미들웨어 체인의 흐름도: 스파이더 요청, 사용자 정의 미들웨어가 메타 프록시 설정, HttpProxyMiddleware가 우선순위 750에서 인증 추가, 다운로더가 회전 게이트웨이를 통해 종료
미들웨어는 meta['proxy']만 설정하면 됩니다; 우선순위 750에서 내장된 것이 자격 증명을 처리하고, 재시도는 새로운 IP에서 체인에 다시 들어갑니다.

차단 처리 기능이 있는 사용자 정의 회전 프록시 미들웨어

자체 프록시 목록 — 정적 ISP 주소 또는 혼합 풀 — 을 운영하는 경우, (무료 목록 프록시가 너무 자주 실패하기 때문에 RETRY_TIMES = 10을 추천한 scrapy-proxies 패키지로 유명해진) 고전적인 패턴은 다음과 같습니다: 요청별로 무작위로 선택하고, 차단 신호에서 제거하고, 요청을 다시 큐에 넣습니다:

import random


class ProxyListMiddleware:
    BAN_CODES = {403, 429}

    def __init__(self, proxies):
        self.proxies = list(proxies)

    @classmethod
    def from_crawler(cls, crawler):
        return cls(crawler.settings.getlist("PROXY_LIST"))

    def process_request(self, request, spider):
        if self.proxies and "proxy" not in request.meta:
            request.meta["proxy"] = random.choice(self.proxies)

    def process_response(self, request, response, spider):
        if response.status in self.BAN_CODES:
            bad = request.meta.get("proxy")
            if bad in self.proxies and len(self.proxies) > 1:
                self.proxies.remove(bad)
                spider.logger.warning("Evicted %s (%d left)", bad, len(self.proxies))
            return request.replace(dont_filter=True)  # re-queue on a new proxy
        return response

게이트웨이가 무료로 제공하는 모든 것을 이 미들웨어가 해야 한다는 점을 주목하세요: 풀 상태 추적, 소진된 IP 제거, 요청 다시 큐에 넣기. 공격적인 목표에서는 20개의 정적 IP 풀도 몇 분 만에 증발할 수 있습니다. 더 넓은 차단 방지 플레이북 (속도 조절, 헤더, 세션 규율)은 우리의 IP 차단 방지 체크리스트에 있습니다.

크롤링 결과를 결정하는 Scrapy 설정

미들웨어는 프록시를 배치하고, 설정은 크롤링이 어떻게 작동할지를 결정합니다. 다음이 가장 중요합니다:

# settings.py — a sane baseline for proxied crawls
RETRY_TIMES = 5
RETRY_HTTP_CODES = [429, 403, 500, 502, 503, 504, 408]

CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 8
DOWNLOAD_TIMEOUT = 30

AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 4.0
프록시 회전을 위한 DIY Scrapy 프록시 목록 미들웨어와 회전 주거용 게이트웨이의 비교
DIY 목록은 건강 검진, 제거 로직 및 오래된 IP를 의미합니다. 게이트웨이는 서버 측에서 회전하는 하나의 엔드포인트입니다 — 재시도는 기본적으로 새로운 IP에 도착합니다.

상태 코드 이상의 차단 감지

정교한 목표는 정직한 403을 보내지 않습니다. CAPTCHA 페이지, 빈 제품 그리드, 또는 제거된 템플릿을 포함한 200을 제공합니다. 견고한 스파이더는 상태뿐만 아니라 콘텐츠를 확인합니다 — 실제 페이지에만 존재하는 요소를 확인하고 없을 경우 다시 큐에 넣습니다. 좋은 프록시를 통해서도 구조적으로 빈 페이지가 반환된다면, 콘텐츠는 아마도 클라이언트 측에서 렌더링됩니다; 스크래퍼가 빈 페이지를 받는 이유를 참조하세요. 그리고 어떤 도메인이 IP와 상관없이 평범한 HTTP를 무력화한다면, 그 도메인을 JavaScript를 렌더링하고 깨끗한 HTML 또는 markdown을 반환하는 Scraper API에 맡기세요 — Scrapy는 다른 응답처럼 이를 소비하고, 파이프라인을 유지할 수 있습니다.

def parse(self, response):
    if not response.css("div.product-grid"):
        # 200 OK but the real content is missing: soft ban or JS wall
        yield response.request.replace(dont_filter=True)
        return
    for product in response.css("div.product-grid article"):
        yield {"name": product.css("h2::text").get()}

자주 묻는 질문

Scrapy는 기본적으로 프록시를 지원하나요?

네. HttpProxyMiddleware는 우선순위 750에서 활성화되어 제공됩니다: http_proxy / https_proxy 환경 변수를 읽고, 요청별로 request.meta['proxy']를 준수하며, user:pass@ 자격 증명을 포함하여 이를 Proxy-Authorization 헤더로 변환합니다. 각 요청에 어떤 프록시를 사용할지 결정하는 코드만 작성하면 됩니다.

Scrapy에서 단일 요청에 프록시를 설정하려면 어떻게 해야 하나요?

요청의 메타에 전달하세요: scrapy.Request(url, meta={'proxy': 'http://user:pass@host:port'}). 메타는 항상 환경 수준의 프록시보다 우선하며, 값을 None으로 설정하면 해당 요청이 직접 연결되도록 강제합니다 — 하나의 스파이더에서 프록시 트래픽과 비프록시 트래픽을 혼합하는 데 유용합니다.

Scrapy에서 프록시를 회전하려면 어떻게 해야 하나요?

다운로더 미들웨어를 작성하여 목록에서 요청별로 프록시를 선택하고 차단된 프록시를 제거하거나, 모든 요청을 서버 측에서 새로운 출구 IP를 할당하는 회전 게이트웨이 엔드포인트에 지정하세요. 게이트웨이 접근 방식은 세 줄의 미들웨어가 필요하며, 건강 검진이 필요 없고, 모든 Scrapy 재시도를 무료로 회전으로 전환합니다.

왜 내 Scrapy 프록시가 407을 반환하나요?

프록시가 인증을 거부했습니다. 자격 증명이 메타의 프록시 URL에 포함되어 있는지, 특수 문자가 URL 인코딩되어 있는지, 계획이 IP 인증을 사용하는 경우 서버 IP가 허용 목록에 있는지 확인하세요. 자격 증명에 비ASCII 문자가 포함되어 있다면, 제공자에 맞춰 HTTPPROXY_AUTH_ENCODING을 설정하세요.

기억해야 할 패턴: meta['proxy']를 일찍 설정하고, 750에서 내장된 미들웨어가 인증을 수행하도록 하며, 429와 403을 재시도 코드에 추가하고, 목록 관리보다 서버 측 회전을 선호하세요. 크롤링의 일부가 JavaScript를 필요로 한다면, 하나를 사용하기 전에 헤드리스 브라우저와 HTTP 요청의 비용을 비교하세요 — 대부분의 Scrapy 프로젝트는 브라우저가 아닌 더 나은 IP가 필요합니다.

회전 프록시를 Scrapy 프로젝트에 연결하세요