Scrapy는 프록시를 어떻게 처리하나요?
Scrapy에는 HttpProxyMiddleware가 기본으로 켜져 있어요. 요청의 meta 사전에 proxy 키로 주소를 넣으면 그 요청이 해당 프록시로 나가고, 주소 안의 아이디와 비밀번호는 미들웨어가 Proxy-Authorization 헤더로 바꿔 보내요. HTTPS 주소는 CONNECT 터널을 거쳐요.
Scrapy의 기본 다운로드 처리기는 HTTP 프록시를 지원해요. SOCKS5가 꼭 필요하다면 별도 처리기가 필요해서, Scrapy에서는 같은 IP의 HTTP 포트를 쓰는 것을 추천해요. 대상 사이트에서 보이는 IP는 같아요.
예시는 IP 198.51.100.23, HTTP 포트 8000, 아이디 spx_user, 비밀번호 spx_pass를 써요.
pip install scrapy
scrapy startproject shop
cd shop요청 하나에 프록시를 넣으려면 어떻게 하나요?
start_requests에서 Request를 만들 때 meta에 proxy를 넣으면 돼요. 콜백에서 다음 페이지를 요청할 때도 같은 방식으로 넣어 주세요. 같은 세션을 이어 가야 한다면 이전 응답의 meta에서 proxy 값을 그대로 넘기면 같은 IP가 유지돼요.
import scrapy
PROXY = "http://spx_user:spx_pass@198.51.100.23:8000"
class IpCheckSpider(scrapy.Spider):
name = "ip_check"
def start_requests(self):
yield scrapy.Request("https://api.ipify.org?format=json", meta={"proxy": PROXY})
def parse(self, response):
self.logger.info("seen as %s", response.json()["ip"])요청마다 IP를 순환하려면 어떻게 하나요?
다운로더 미들웨어를 하나 만들어 모든 요청에 다음 프록시를 넣으면 스파이더 코드를 바꾸지 않고 순환할 수 있어요. 아래 미들웨어는 시작할 때 API로 목록을 받고, meta에 proxy가 이미 있으면 그대로 두어 세션 고정과 순환을 함께 쓸 수 있게 했어요.
API 키는 settings.py에 두거나 환경 변수에서 읽게 하세요. 순서는 기본 HttpProxyMiddleware(750)보다 앞인 숫자로 등록해야 미들웨어가 넣은 주소를 기본 미들웨어가 처리해요.
import itertools
import requests
class RotatingProxyMiddleware:
def __init__(self, key):
text = requests.get(
"https://socks5proxy.io/api/v1/proxies/download",
params={"format": "user:pass@ip:port", "protocol": "http"},
headers={"Authorization": "Bearer " + key},
timeout=30,
).text
self.pool = itertools.cycle(["http://" + l.strip() for l in text.splitlines() if l.strip()])
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings.get("SPX_API_KEY"))
def process_request(self, request, spider):
if "proxy" not in request.meta: # keep a proxy that was set on purpose (sticky sessions)
request.meta["proxy"] = next(self.pool)SPX_API_KEY = "YOUR_API_KEY"
DOWNLOADER_MIDDLEWARES = {
"shop.middlewares.RotatingProxyMiddleware": 740,
}
CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 16
DOWNLOAD_DELAY = 0.25
AUTOTHROTTLE_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 522, 524, 408]
DOWNLOAD_TIMEOUT = 30속도와 재시도는 어떻게 맞추나요?
CONCURRENT_REQUESTS_PER_DOMAIN은 같은 도메인에 동시에 보낼 요청 수예요. 프록시 IP가 많을수록 이 값을 올려도 IP 하나에 가는 부담은 그대로예요. 예를 들어 IP 500개로 동시 요청 50개를 보내면 IP 하나는 평균 10번 요청에 한 번만 쓰여요.
AUTOTHROTTLE을 켜면 응답 속도에 맞춰 요청 간격을 자동으로 조절해요. 429 응답이 늘어나면 RETRY_HTTP_CODES로 다시 시도하되, 다시 시도할 때 미들웨어가 다음 프록시를 넣도록 meta의 proxy를 지우는 방법도 있어요.
수집한 결과가 확인 화면이 아닌 실제 페이지인지 검사하는 단계를 파이프라인에 두면 데이터 품질이 좋아져요. 원하는 요소가 없는 응답은 다른 IP로 다시 요청하게 하면 돼요.
- IP 수에 맞춰 도메인별 동시 요청 수 조절
- AUTOTHROTTLE로 응답 속도에 맞춘 간격
- 429와 5xx는 다른 IP로 재시도
- 응답 내용 검사로 확인 화면 걸러 내기
IP 인증으로 연결하면 무엇이 달라지나요?
대시보드에 Scrapy가 실행되는 서버의 IPv4를 등록하면 meta proxy에 http://198.51.100.23:8000처럼 아이디와 비밀번호 없이 넣을 수 있어요. 목록도 format=ip:port로 받으면 돼요. 여러 서버에서 스파이더를 돌린다면 각 서버의 IP를 모두 등록해 주세요. 최대 10개까지 등록할 수 있어요.
세션이 필요한 요청은 같은 IP로 묶으려면 어떻게 하나요?
로그인이나 장바구니처럼 쿠키가 이어지는 흐름은 같은 IP를 써야 해요. 첫 요청에 meta proxy를 직접 넣고, 콜백에서 다음 요청을 만들 때 response.meta의 proxy 값을 그대로 넘기면 순환 미들웨어가 바꾸지 않아요. Scrapy의 cookiejar meta 키를 함께 쓰면 쿠키도 세션마다 분리돼요.
세션 하나에 프록시 하나와 쿠키 묶음 하나를 짝지어 두면 여러 세션을 동시에 돌려도 서로 섞이지 않아요.
import scrapy
PROXIES = [
"http://spx_user:spx_pass@198.51.100.23:8000",
"http://spx_user:spx_pass@198.51.100.24:8000",
]
class SessionSpider(scrapy.Spider):
name = "sessions"
def start_requests(self):
for i, proxy in enumerate(PROXIES):
yield scrapy.Request("https://example.com/", meta={"proxy": proxy, "cookiejar": i}, dont_filter=True)
def parse(self, response):
# follow-up request keeps the same proxy and the same cookie jar
yield response.follow("/account", meta={"proxy": response.meta["proxy"], "cookiejar": response.meta["cookiejar"]}, callback=self.parse_account)
def parse_account(self, response):
self.logger.info("status %s via %s", response.status, response.meta["proxy"])실패한 프록시를 잠시 빼 두려면 어떻게 하나요?
미들웨어의 process_response에서 403이나 429 응답을 받으면 그 프록시를 몇 분 동안 쉬게 하고, 같은 요청을 다른 프록시로 다시 보내게 만들 수 있어요. 요청을 복사해 meta의 proxy를 지우고 dont_filter를 켠 뒤 돌려주면 다음 프록시가 들어가요.
쉬는 프록시 목록과 다시 쓸 시각을 사전으로 관리하면 간단해요. 같은 IP를 바로 다시 쓰지 않아 대상 사이트의 제한이 풀릴 시간을 줄 수 있어요.
Scrapy에서 대역폭을 아끼려면 무엇을 설정하나요?
Scrapy는 기본으로 HTML만 받아서 브라우저 도구보다 대역폭을 훨씬 적게 써요. 여기에 압축 응답을 받는 HttpCompressionMiddleware가 기본으로 켜져 있어 전송량이 더 줄어요. 공유 플랜의 월 대역폭 안에서 많은 페이지를 수집할 수 있는 이유예요.
같은 페이지를 다시 받지 않도록 HTTPCACHE_ENABLED를 개발 중에 켜 두면 시험할 때마다 대상 사이트와 프록시를 다시 부르지 않아요. 운영에서는 중복 주소 걸러 내기(기본 켜짐)를 유지하고, 정말 다시 받아야 하는 요청에만 dont_filter를 쓰세요.
필요 없는 하위 페이지로 퍼져 나가지 않도록 링크를 따라갈 범위를 좁히는 것도 중요해요. 수집 범위를 분명하게 정하면 시간과 대역폭, 대상 사이트의 부담이 모두 줄어요.
자주 나는 문제는 어떻게 해결하나요?
407 응답이 나오면 주소 안의 아이디와 비밀번호를 확인하세요. 비밀번호에 특수 문자가 있으면 퍼센트 인코딩해서 넣어야 해요. TunnelError는 프록시가 CONNECT 요청을 받지 못한 경우로, 포트가 HTTP 포트인지 확인해 주세요.
모든 요청이 같은 IP로 나간다면 미들웨어 순서 번호가 HttpProxyMiddleware보다 뒤에 있거나, 스파이더에서 meta proxy를 직접 넣고 있는 경우예요. settings.py의 순서와 스파이더 코드를 함께 확인하세요.