Scrapy Proxy Middleware: Rotacja Proxy Bez Blokad
Scrapy obsługuje proxy od wersji 0.8, ale dokumentacja nigdy nie wyjaśnia, jak rotować, ponawiać i unikać blokad na dużą skalę. Oto anatomia middleware, kod i ustawienia, które decydują, czy Twój crawl zakończy się sukcesem.
Scrapy obsługuje middleware proxy od wersji 0.8, więc pytanie 'czy Scrapy obsługuje proxy' zostało rozstrzygnięte piętnaście lat temu. Dokumentacja nigdy jednak nie przedstawia pełnego obrazu produkcyjnego: jak wbudowane middleware proxy Scrapy faktycznie przetwarza dane uwierzytelniające, kiedy ustawiać proxy per request, a kiedy globalnie, oraz jak rotować i odzyskiwać, gdy cel zaczyna blokować wyjścia w trakcie crawl. Ten przewodnik przeprowadza przez cały łańcuch — wbudowane HttpProxyMiddleware, per-request meta, niestandardowe middleware do rotacji z obsługą blokad i ustawienia, które decydują, czy crawl na 100k stron zakończy się sukcesem, czy padnie o 3 nad ranem.
Jak działa wbudowane middleware proxy Scrapy
scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware jest domyślnie włączone z priorytetem 750 w DOWNLOADER_MIDDLEWARES_BASE. Analiza jego kodu źródłowego (około 100 linii w Scrapy 2.17) dostarcza wszystkiego, co potrzebne do debugowania:
- Podczas uruchamiania wywołuje
urllib.request.getproxies(), więc zmienne środowiskowehttp_proxy/https_proxykonfigurują pająka bez potrzeby pisania kodu.no_proxyjest honorowane tylko dla schematów http i https. - Dla każdego żądania,
request.meta['proxy']zawsze ma pierwszeństwo przed środowiskiem. Ustawienie go naNonewymusza bezpośrednie połączenie dla tego żądania. - Dane uwierzytelniające osadzone w URL proxy (
http://user:pass@host:port) są usuwane, kodowane w Base64 i wysyłane jako nagłówekProxy-Authorization: Basic. Kodowanie domyślnie to latin-1 i można je skonfigurować za pomocąHTTPPROXY_AUTH_ENCODING. - Middleware dekoduje dane uwierzytelniające przed ich kodowaniem, więc specjalne znaki, takie jak
@w hasłach, powinny być zakodowane w URL — przechodzą poprawnie. - Jeśli ponowne próby zmieniają proxy, middleware usuwa nieaktualny nagłówek uwierzytelniający, zamiast przekazywać go do nowego proxy — poprawka higieny danych uwierzytelniających we współczesnym Scrapy i dobry powód, by nie używać starożytnych wersji.
Praktyczna konsekwencja: prawie nigdy nie potrzebujesz zewnętrznego pakietu proxy. Wszystko, co umieszcza prawidłowy URL proxy w request.meta['proxy'] przed priorytetem 750, otrzymuje obsługę uwierzytelniania i nagłówków za darmo.
Proxy per-request z meta
Najlżejsza integracja to ustawienie meta bezpośrednio w pająku — przydatne, gdy tylko niektóre żądania potrzebują proxy, lub różne cele wymagają różnych krajów wyjścia:
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"},
)
Meta per-request błyszczy, gdy wybór proxy jest oparty na danych: kieruj niemieckie strony produktów przez użytkownika z Niemiec, wysyłaj pobieranie obrazów przez tanie wyjścia z centrów danych, podczas gdy HTML idzie przez rezydencjalne, lub eskaluj uparty URL do bardziej trwałej sesji przy drugiej próbie. Ponieważ meta przetrwa ponowne próby i przekierowania, decyzja podjęta podczas budowy żądania towarzyszy mu przez cały cykl pobierania. Ustawianie meta na tysiącach żądań ręcznie nie skaluje się jednak — do tego służą downloader middlewares.
Najprostsza konfiguracja produkcyjna: rotacyjna brama
Z rotacyjnymi proxy za jednym punktem końcowym bramy, rotacja odbywa się po stronie serwera: każde żądanie przez ten sam URL wychodzi z innego IP w puli rezydencjalnej 90M+ obejmującej ponad 200 krajów. Twoje middleware kurczy się do trzech linii, a nie ma listy do sprawdzenia zdrowia, przycinania czy odświeżania:
# 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,
}
Priorytet 350 ma znaczenie: Twoje middleware musi działać przed wbudowanym na 750, aby HttpProxyMiddleware mogło nadal przekształcać dane uwierzytelniające w nagłówek uwierzytelniający. To połączenie — rotacja bramy plus standardowa mechanika ponownych prób Scrapy — to najwyższa niezawodność na linię kodu, ponieważ każda ponowna próba automatycznie przechodzi przez nowe wyjście IP.

Niestandardowe middleware do rotacji proxy z obsługą blokad
Jeśli prowadzisz własną listę proxy — statyczne adresy ISP lub mieszana pula — klasyczny wzorzec (spopularyzowany przez pakiet scrapy-proxies, który zalecał RETRY_TIMES = 10 ponieważ proxy z darmowej listy zawodzą tak często) to: wybierz losowo per request, usuń na sygnały blokady, ponownie umieść żądanie w kolejce:
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
Zauważ wszystko, co to middleware musi zrobić, co brama robi za darmo: śledzić zdrowie puli, usuwać spalone IP, ponownie umieszczać żądania w kolejce. Kurczy się również pod ostrzałem — pula 20 statycznych IP może wyparować w ciągu minut na agresywnym celu. Szerszy podręcznik antyblokadowy (tempo, nagłówki, dyscyplina sesji) znajduje się w naszym checkliście unikania blokad IP.
Ustawienia Scrapy, które decydują o wyniku
Middleware umieszcza proxy; ustawienia decydują, czy crawl zachowuje się poprawnie. Te są najważniejsze:
# 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
- RETRY_HTTP_CODES — dodaj 429 i 403, aby miękkie blokady wywoływały ponowną próbę (i, z rotacją, nowe IP) zamiast nieudanego elementu.
- RETRY_TIMES — 3-5 jest odpowiednie dla jakościowej puli; potrzeba 10 to znak, że sama pula jest niezdrowa.
- CONCURRENT_REQUESTS_PER_DOMAIN — prawdziwe pokrętło uprzejmości. Rotacja rozkłada obciążenie na IP, ale cel nadal widzi całkowitą objętość.
- DOWNLOAD_TIMEOUT — domyślne 180 sekund pozwala jednemu wolnemu wyjściu zajmować slot współbieżności przez trzy minuty. 30 jest wystarczające.
- AUTOTHROTTLE — dostosowuje tempo do obserwowanej latencji; dobrze współpracuje z rotacją na celach z ograniczeniami szybkości. Nadal toniesz w 429? Nasz przewodnik po ograniczeniach szybkości obejmuje budżety współbieżności w głębi.

Wykrywanie blokad poza kodami statusu
Wyrafinowane cele nie wysyłają uczciwych 403. Serwują 200 zawierające stronę CAPTCHA, pustą siatkę produktów lub odchudzony szablon. Solidne pająki weryfikują treść, nie tylko status — sprawdzaj element, który istnieje tylko na prawdziwej stronie, i ponownie umieszczaj w kolejce, gdy go brakuje. Jeśli strony wracają strukturalnie puste nawet przez dobre proxy, treść prawdopodobnie jest renderowana po stronie klienta; zobacz dlaczego skrapery zwracają puste strony. A gdy jedna domena pokonuje zwykłe HTTP bez względu na IP, przekaż tę domenę do Scraper API, który renderuje JavaScript i zwraca czysty HTML lub markdown — Scrapy konsumuje to jak każdą inną odpowiedź, a Ty utrzymujesz swój pipeline.
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()}
Często zadawane pytania
Czy Scrapy obsługuje proxy od razu po wyjęciu z pudełka?
Tak. HttpProxyMiddleware jest dostarczane włączone z priorytetem 750: czyta zmienne środowiskowe http_proxy / https_proxy i honoruje request.meta['proxy'] per request, w tym dane uwierzytelniające user:pass@, które przekształca w nagłówek Proxy-Authorization. Piszesz tylko kod, aby zdecydować, które proxy otrzymuje każde żądanie.
Jak ustawić proxy dla pojedynczego żądania w Scrapy?
Przekaż je w meta żądania: scrapy.Request(url, meta={'proxy': 'http://user:pass@host:port'}). Meta zawsze nadpisuje proxy na poziomie środowiska, a ustawienie wartości na None wymusza, aby to jedno żądanie poszło bezpośrednio — przydatne do mieszania ruchu z proxy i bez proxy w jednym pająku.
Jak rotować proxy w Scrapy?
Napisz middleware downloadera, które wybiera proxy per request z listy i usuwa zablokowane, lub skieruj każde żądanie do rotacyjnego punktu końcowego bramy, który przypisuje świeże wyjście IP po stronie serwera. Podejście z bramą wymaga trzy-liniowego middleware, bez kontroli zdrowia, i zamienia każdą ponowną próbę Scrapy w rotację za darmo.
Dlaczego moje proxy Scrapy zwraca 407?
Proxy odrzuciło uwierzytelnianie. Sprawdź, czy dane uwierzytelniające znajdują się w URL proxy w meta, czy specjalne znaki są zakodowane w URL, i czy Twój IP serwera jest na białej liście, jeśli Twój plan używa uwierzytelniania IP. Jeśli dane uwierzytelniające zawierają znaki spoza ASCII, ustaw HTTPPROXY_AUTH_ENCODING zgodnie z Twoim dostawcą.
Wzorzec do zapamiętania: ustaw meta['proxy'] wcześnie, pozwól wbudowanemu middleware na 750 wykonać uwierzytelnianie, dodaj 429 i 403 do swoich kodów ponownych prób, i preferuj rotację po stronie serwera nad opieką nad listą. Jeśli części Twojego crawl potrzebują JavaScriptu, rozważ koszt przeglądarek bezgłowych w porównaniu do żądań HTTP zanim sięgniesz po jedną — większość projektów Scrapy potrzebuje lepszych IP, a nie przeglądarki.