Middleware Scrapy Proxy: Rotazione dei Proxy Senza Ban

Scrapy ha introdotto il supporto per i proxy dalla versione 0.8, ma la documentazione non spiega mai come ruotare, riprovare e sopravvivere ai ban su larga scala. Ecco l'anatomia del middleware, il codice e le impostazioni che decidono se il tuo crawl si completa.

Scrapy ha introdotto un middleware per i proxy dalla versione 0.8, quindi 'Scrapy supporta i proxy' è stato risolto quindici anni fa. Ciò che la documentazione non riesce mai a comporre è il quadro di produzione: come il middleware proxy integrato di Scrapy elabora effettivamente le credenziali, quando impostare i proxy per richiesta rispetto a globalmente, e come ruotare e recuperare quando un obiettivo inizia a bannare le uscite a metà crawl. Questa guida percorre l'intera catena — il HttpProxyMiddleware integrato, il meta per richiesta, un middleware di rotazione personalizzato con gestione dei ban e le impostazioni che decidono se un crawl di 100k pagine si completa o muore alle 3 del mattino.

Come funziona il middleware proxy integrato di Scrapy

scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware è abilitato di default con priorità 750 in DOWNLOADER_MIDDLEWARES_BASE. Leggendo il suo codice sorgente (circa 100 righe in Scrapy 2.17) scopri tutto ciò che serve per il debug:

Conseguenza pratica: quasi mai hai bisogno di un pacchetto proxy di terze parti. Qualsiasi cosa che inserisca un URL proxy valido in request.meta['proxy'] prima della priorità 750 ottiene autenticazione e gestione delle intestazioni gratuitamente.

Proxy per richiesta con meta

L'integrazione più leggera è impostare meta direttamente nello spider — utile quando solo alcune richieste necessitano di un proxy, o diversi obiettivi necessitano di diversi paesi di uscita:

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"},
        )

Il meta per richiesta brilla quando la scelta del proxy è guidata dai dati: instrada le pagine di prodotti tedeschi tramite un nome utente mirato alla Germania, invia i download di immagini tramite uscite datacenter economiche mentre l'HTML va residenziale, o scala un URL ostinato a una sessione più stabile al secondo tentativo. Poiché il meta sopravvive a retry e redirect, la decisione presa durante la costruzione della richiesta la segue per tutto il ciclo di download. Impostare il meta su migliaia di richieste a mano non scala, però — è per questo che esistono i downloader middlewares.

La configurazione di produzione più semplice: un gateway rotante

Con proxy rotanti dietro un singolo endpoint gateway, la rotazione avviene lato server: ogni richiesta attraverso lo stesso URL esce da un IP diverso in un pool residenziale di oltre 90M che copre oltre 200 paesi. Il tuo middleware si riduce a tre righe, e non c'è lista da controllare, potare o aggiornare:

# 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,
}

La priorità 350 è importante: il tuo middleware deve essere eseguito prima di quello integrato a 750 affinché HttpProxyMiddleware possa ancora convertire le credenziali nell'intestazione di autenticazione. Questo abbinamento — rotazione del gateway più il meccanismo di retry standard di Scrapy — offre la massima affidabilità per linea di codice, perché ogni retry viaggia automaticamente attraverso un nuovo IP di uscita.

Diagramma di flusso della catena del middleware proxy di Scrapy: richiesta dello spider, middleware personalizzato imposta il proxy meta, HttpProxyMiddleware aggiunge l'autenticazione a priorità 750, downloader esce tramite gateway rotante
Il tuo middleware deve solo impostare meta['proxy']; l'integrato a priorità 750 gestisce le credenziali, e i retry rientrano nella catena su un nuovo IP.

Un middleware proxy rotante personalizzato con gestione dei ban

Se gestisci la tua lista di proxy — indirizzi ISP statici o un pool misto — il modello classico (popolarizzato dal pacchetto scrapy-proxies, che raccomandava RETRY_TIMES = 10 perché i proxy delle liste gratuite falliscono così spesso) è: scegli casualmente per richiesta, espelli sui segnali di ban, rimetti in coda la richiesta:

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

Nota tutto ciò che questo middleware deve fare che un gateway fa gratuitamente: traccia la salute del pool, espelli gli IP bruciati, rimetti in coda le richieste. Si riduce anche sotto pressione — un pool di 20 IP statici può evaporare in minuti su un obiettivo aggressivo. Il più ampio manuale anti-ban (ritmo, intestazioni, disciplina delle sessioni) è nel nostro checklist per evitare il ban IP.

Impostazioni di Scrapy che decidono l'esito

Il middleware posiziona il proxy; le impostazioni decidono se il crawl si comporta. Queste contano di più:

# 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
Confronto tra un middleware proxy list fai-da-te di Scrapy e un gateway residenziale rotante per la rotazione dei proxy
Una lista fai-da-te significa controlli di salute, logica di espulsione e IP obsoleti. Un gateway è un endpoint che ruota lato server — i retry atterrano su IP freschi per impostazione predefinita.

Rilevamento dei ban oltre i codici di stato

Gli obiettivi sofisticati non inviano 403 onesti. Servono 200 contenenti una pagina CAPTCHA, una griglia di prodotti vuota o un modello spogliato. Gli spider robusti verificano il contenuto, non solo lo stato — controlla un elemento che esiste solo sulla pagina reale e rimetti in coda quando manca. Se le pagine tornano strutturalmente vuote anche tramite buoni proxy, il contenuto è probabilmente reso lato client; vedi perché gli scraper ottengono pagine vuote. E quando un dominio sconfigge il semplice HTTP indipendentemente dall'IP, consegna quel dominio a un Scraper API che rende JavaScript e restituisce HTML pulito o markdown — Scrapy lo consuma come qualsiasi altra risposta, e mantieni la tua 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()}

Domande frequenti

Scrapy supporta i proxy di default?

Sì. HttpProxyMiddleware è abilitato a priorità 750: legge le variabili d'ambiente http_proxy / https_proxy e rispetta request.meta['proxy'] per richiesta, incluse le credenziali user:pass@, che converte in un'intestazione Proxy-Authorization. Devi solo scrivere codice per decidere quale proxy riceve ogni richiesta.

Come imposto un proxy per una singola richiesta in Scrapy?

Passalo nel meta della richiesta: scrapy.Request(url, meta={'proxy': 'http://user:pass@host:port'}). Il meta prevale sempre sui proxy a livello di ambiente, e impostare il valore su None forza quella singola richiesta ad andare diretta — utile per mescolare traffico con e senza proxy in uno spider.

Come ruoto i proxy in Scrapy?

Scrivi un middleware downloader che seleziona un proxy per richiesta da una lista ed espelle quelli bannati, oppure punta ogni richiesta a un endpoint gateway rotante che assegna un nuovo IP di uscita lato server. L'approccio del gateway necessita di un middleware di tre righe, nessun controllo di salute, e trasforma ogni retry di Scrapy in una rotazione gratuita.

Perché il mio proxy Scrapy restituisce 407?

Il proxy ha rifiutato l'autenticazione. Controlla che le credenziali siano all'interno dell'URL del proxy nel meta, che i caratteri speciali siano codificati URL e che il tuo IP server sia nella whitelist se il tuo piano utilizza l'autenticazione IP. Se le credenziali contengono caratteri non ASCII, imposta HTTPPROXY_AUTH_ENCODING per corrispondere al tuo provider.

Il modello da ricordare: imposta meta['proxy'] presto, lascia che il middleware integrato a 750 gestisca l'autenticazione, aggiungi 429 e 403 ai tuoi codici di retry, e preferisci la rotazione lato server rispetto alla gestione delle liste. Se parti del tuo crawl necessitano di JavaScript, valuta il costo dei browser headless rispetto alle richieste HTTP prima di sceglierne uno — la maggior parte dei progetti Scrapy ha bisogno di IP migliori, non di un browser.

Collega proxy rotanti al tuo progetto Scrapy