# 레지덴셜 프록시 회전용 Scrapy 미들웨어

> 요청별 회전 및 고정 레지덴셜 IP, AutoThrottle과 재시도 설정, scrapy-playwright, 문법 검증된 코드.

- URL: https://crawlrelay.com/ko/docs/scrapy/
- 마지막 업데이트: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

30줄짜리 다운로드 미들웨어로 요청별 국가와 세션을 선택하며, robots.txt, 도메인별 동시 처리 제한, AutoThrottle과 재시도를 설정해 크롤 예의를 지킵니다.

**빠른 답변 — Scrapy에서 회전 프록시는 어떻게 쓰나요?**

Scrapy의 `HttpProxyMiddleware` 전에 작동하는 작고 간단한 다운로드 미들웨어에 `request.meta["proxy"]`를 `http://USERNAME:PASSWORD@gw.crawlrelay.com:7777`로 설정하세요; 그러면 Scrapy가 인증 정보를 전달합니다. **CrawlRelay** 레지덴셜 프록시는 사용자 이름에 국가와 고정 세션이 들어가 하나의 미들웨어가 둘 다 처리합니다. 트래픽 비용은 50GB부터 US$0.50/GB이고, KYC 없이 암호화폐로 결제 가능합니다.

## Scrapy 설정 한눈에 보기

| 속성 | 값 |
| --- | --- |
| 프록시 위치 | 미들웨어가 설정하는 `request.meta["proxy"]` |
| 미들웨어 우선순위 | 내 미들웨어가 내장된 `HttpProxyMiddleware` 우선순위 750 아래에 위치 |
| 인증 정보 | 프록시 URL에 넣으면 Scrapy가 `Proxy-Authorization` 헤더로 변환 |
| SOCKS5 | 기본 HTTP/1.1 핸들러에서는 불가; Scrapy 2.17+에서 HttpxDownloadHandler를 통해 SOCKS 지원 가능. HTTP 포트 7777가 가장 간단함 |
| 회전 | 새 연결마다 새로운 출구 IP; 요청별 고유 세션 ID가 기본적으로 다른 IP를 줌 |
| 예의 지키기 | `ROBOTSTXT_OBEY`, `CONCURRENT_REQUESTS_PER_DOMAIN`, `AUTOTHROTTLE_ENABLED` |
| 재시도 | 429와 5xx는 기본 `RETRY_HTTP_CODES`에 포함; Retry-After는 기본으로 읽지 않음 |

## 프록시 미들웨어는 어떻게 작성하나요?

클래스를 `middlewares.py`에 넣으세요. 설정과 세 가지 선택적 `meta` 키(`proxy_country`, `proxy_session`, `proxy_ttl`)로 사용자 이름을 만듭니다. `proxy_session`가 없는 요청은 회전하며, `proxy_session="new"`가 있는 요청은 새 세션을 얻어 보통 다른 출구 IP를 받습니다; 고정 세션 ID가 있는 요청은 같은 흐름의 다른 요청과 출구를 공유합니다. 미들웨어는 재시도를 포함해 매번 프록시를 재계산하며, 다른 코드가 설정한 프록시는 건드리지 않습니다. `CRAWLRELAY_USER`가 없으면 자신의 IP에서 크롤이 시작되어 멈춥니다.

*middlewares.py*

```python
import uuid
from urllib.parse import quote


class CrawlRelayProxyMiddleware:
    """Sets request.meta["proxy"]; Scrapy's HttpProxyMiddleware then sends the credentials."""

    def __init__(self, user, password, country):
        self.user, self.password, self.country = user, password, country

    @classmethod
    def from_crawler(cls, crawler):
        s = crawler.settings
        if not s.get("CRAWLRELAY_USER"):
            # Fail loudly: NotConfigured would only disable the middleware and crawl from your own IP
            raise ValueError("CRAWLRELAY_USER is not set: refusing to crawl without the proxy")
        return cls(s.get("CRAWLRELAY_USER"), s.get("CRAWLRELAY_PASSWORD", ""), s.get("CRAWLRELAY_COUNTRY", "us"))

    def process_request(self, request, spider=None):
        if "proxy" in request.meta and not request.meta.get("_crawlrelay"):
            return None  # a proxy set by other code: leave it alone
        parts = [self.user, "country", request.meta.get("proxy_country", self.country)]
        session = request.meta.get("proxy_session")
        if session == "new":  # a fresh session for this request (and for each of its retries)
            session = uuid.uuid4().hex[:12]
        if session:
            parts += ["session", session, "ttl", str(request.meta.get("proxy_ttl", 10))]
        username = "-".join(parts)
        request.meta["proxy"] = "http://%s:%s@gw.crawlrelay.com:7777" % (
            quote(username, safe=""), quote(self.password, safe=""))
        request.meta["_crawlrelay"] = True
        return None
```

## 프록시 크롤링을 예의 바르고 빠르게 만드는 Scrapy 설정은?

프록시는 요청을 여러 IP에 분산시키지만 타겟에는 총 요청 속도가 보입니다. 이 설정들은 각 도메인을 지속 가능한 속도로 유지하면서 응답이 느려질 때 AutoThrottle이 스스로 속도를 늦추게 합니다. 새 Scrapy 프로젝트가 기본으로 설정하는 `ROBOTSTXT_OBEY = True`는 꼭 유지하세요.

*settings.py (발췌)*

```python
import os

BOT_NAME = "myproject"
ROBOTSTXT_OBEY = True
USER_AGENT = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"

CRAWLRELAY_USER = os.environ.get("CRAWLRELAY_USER")  # e.g. the username from your dashboard
CRAWLRELAY_PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD")
CRAWLRELAY_COUNTRY = "us"

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.CrawlRelayProxyMiddleware": 350,  # runs before HttpProxyMiddleware (750)
}

CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 4
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0

RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 522, 524, 408]
DOWNLOAD_TIMEOUT = 45
```

## 페이지네이션에는 하나의 IP를 유지하고 항목 페이지에는 IP를 회전시키려면 어떻게 하나요?

이전 페이지에 의존하는 플로우 요청에는 고정된 `proxy_session`를 전달하고 독립적인 페이지에는 아무 것도 전달하지 마세요. 아래 스파이더는 스크래핑 연습용으로 공개된 샌드박스 사이트 books.toscrape.com에서 실행됩니다.

*spiders/catalog.py*

```python
import scrapy


class CatalogSpider(scrapy.Spider):
    name = "catalog"
    start_urls = ["https://books.toscrape.com/"]

    def parse(self, response):
        for href in response.css("article.product_pod h3 a::attr(href)").getall():
            # Product pages are independent: rotating exits
            yield response.follow(href, callback=self.parse_item)
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            # The listing flow keeps one sticky exit for 30 minutes
            yield response.follow(next_page, callback=self.parse,
                                  meta={"proxy_session": "listing1", "proxy_ttl": 30})

    def parse_item(self, response):
        yield {
            "title": response.css("div.product_main h1::text").get(),
            "price": response.css("p.price_color::text").get(),
            "url": response.url,
        }
```

## Scrapy가 연결을 재사용하며, 이것이 회전을 막나요?

네, Scrapy는 프록시와 대상 호스트별로 연결을 유지하고 재사용하므로 하나의 연결에서 연속 요청은 같은 출구 IP를 공유합니다. 대부분의 크롤링에서는 괜찮습니다: Scrapy는 사이트당 최대 `CONCURRENT_REQUESTS_PER_DOMAIN`개의 연결을 열고 각 새 연결은 보통 다른 레지덴셜 IP를 받습니다.

새 IP를 통해 요청을 보내야 할 때, 예를 들어 실패한 출구에서 502](https://crawlrelay.com/ko/docs/errors/502-bad-gateway/) 오류가 발생한 후 재시도 시 `response.request.replace(meta={**response.meta, "proxy_session": "new"}, dont_filter=True)`를 사용해 다시 보내세요. 미들웨어가 새 세션 사용자 이름을 생성하는데, 다른 사용자 이름은 다른 연결 풀을 의미하므로 Scrapy는 새 연결을 열고 게이트웨이는 보통 다른 출구 IP를 할당합니다.

## Scrapy에서 429 응답 시 Retry-After를 어떻게 준수하나요?

Scrapy 내장 `RetryMiddleware`는 429 응답을 재시도하지만 `Retry-After` 지연을 기다리지 않습니다. AutoThrottle이 시간이 지날수록 도메인 속도를 늦추는데 보통 이게 충분합니다. 긴 `Retry-After` 값을 보내는 API는 헤더를 읽어 지연 후 재시도를 예약하는 작은 미들웨어를 추가하거나 해당 도메인의 `CONCURRENT_REQUESTS_PER_DOMAIN`를 낮추세요. [429 페이지](https://crawlrelay.com/ko/docs/errors/429-too-many-requests/)에서 헤더 의미를 설명합니다.

## scrapy-playwright와 프록시 사용 방법은?

자바스크립트가 필요한 페이지에 대해 scrapy-playwright는 설정을 통해 Playwright 실행 옵션을 전달합니다. 자격증명을 별도 필드로 입력하세요: Chromium은 서버 URL에 포함한 자격증명을 무시합니다. 여러 페이지에 걸쳐 로그아웃 상태의 플로우 렌더링 시에는 세션마다 하나의 컨텍스트를 사용하세요.

*scrapy-playwright용 settings.py*

```python
import os

DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

PLAYWRIGHT_LAUNCH_OPTIONS = {
    "proxy": {
        "server": "http://gw.crawlrelay.com:7777",
        "username": os.environ.get("CRAWLRELAY_USER", "USERNAME") + "-country-us",
        "password": os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD"),
    },
}
```

## Scrapy와 프록시: 자주 묻는 질문

### scrapy-rotating-proxies나 프록시 목록이 필요한가요?

아닙니다. 해당 패키지는 개별 프록시 주소 목록을 회전시키고 실패한 프록시를 차단합니다. 레지덴셜 게이트웨이에서는 하나의 주소 뒤에서 회전이 일어나므로 각 새 연결이 보통 다른 레지덴셜 IP를 받습니다. `request.meta["proxy"]`를 설정하는 단일 미들웨어만 있으면 충분합니다.

### 내 프록시에서 Scrapy가 TunnelError를 표시하는 이유는?

`TunnelError: Could not open CONNECT tunnel`는 프록시가 HTTPS 사이트의 CONNECT 요청을 거부하거나 실패했음을 의미합니다. 메시지 상태 코드는 이유를 알려줍니다: 407은 자격증명 문제, 502 또는 504는 출구 혹은 대상 문제입니다. [407](https://crawlrelay.com/ko/docs/errors/407-proxy-authentication-required/) 및 [502](https://crawlrelay.com/ko/docs/errors/502-bad-gateway/) 페이지를 참고하세요.

### 도메인별 동시 요청 수는 얼마나 해야 하나요?

도메인당 2~4부터 시작하고 AutoThrottle에 맡기세요. 429, 403, 5xx 응답 비율이 일정할 때만 숫자를 올리고 사이트 응답 시간도 살피세요. 대형 사이트는 더 많이 견디고 소형 사이트는 요청 간 지연과 하나의 연결이 필요할 수 있습니다.

### 더 많은 데이터를 얻으려면 ROBOTSTXT_OBEY를 비활성화해야 하나요?

아니요. robots.txt는 사이트가 자동 클라이언트가 접근할 수 있는 경로를 명시하는 방법이며 2022년 RFC 9309에서 표준화되었습니다. 이를 무시하면 법적·윤리적 위험이 증가하고, 사이트가 제외한 콘텐츠에 접근할 때 우리의 [Acceptable Use Policy](https://crawlrelay.com/ko/acceptable-use/)를 위반합니다.

### Scrapy 크롤링은 얼마나 많은 트래픽을 사용하나요?

Scrapy는 HTML만 다운로드하므로 페이지당 보통 수십 KB의 데이터가 압축되어 전송됩니다. 크롤링 종료 시 출력되는 통계에서 `downloader/response_bytes` 값을 확인하고 응답 수로 나누세요. [대역폭 가이드](https://crawlrelay.com/ko/guides/bandwidth-calculator/)가 비용 계산을 도와줍니다.

**Scrapy 프로젝트에 레지덴셜 IP를 연결하세요** US$5.94에서 10 GB를 테스트하고, 50 GB에서 US$0.50/GB를 받으세요. [가격 확인](https://crawlrelay.com/ko/pricing/) · [동시성 가이드](https://crawlrelay.com/ko/docs/concurrency/)

---

CrawlRelay: CrawlRelay는 개발자 중심의 회전형 레지덴셜 프록시 및 4G/5G 모바일 프록시 서비스로, 스크래퍼와 AI 데이터 파이프라인을 위한 비만료 선불 과금 트래픽을 US$0.50/GB부터 암호화폐로 결제하며 신분증 업로드가 없습니다. KYC 없이 — 신분증, 셀카, 문서 없이 이용 가능. 암호화폐로 결제하세요.
