Scrapy è uno dei framework Python più potenti per il web scraping, ma senza una corretta configurazione del proxy, i tuoi scraper verranno bloccati dopo pochi minuti di lavoro. In questa guida mostrerò tutti i modi per integrare i proxy in Scrapy: dalla configurazione più semplice ai metodi avanzati di rotazione degli indirizzi IP con gestione automatica degli errori.
Il materiale si basa su esperienze reali di scraping di grandi piattaforme di e-commerce e siti protetti. Otterrai esempi di codice pronti all'uso nei tuoi progetti.
Perché Scrapy senza proxy riceve blocchi
I siti moderni utilizzano una protezione multilivello contro lo scraping. Anche se hai configurato l'User-Agent e i ritardi tra le richieste, il tuo indirizzo IP rivela l'automazione per diversi motivi:
- Frequenza delle richieste: un IP effettua 100+ richieste al minuto — chiaro segno di un bot
- Modelli di comportamento: navigazione sequenziale delle pagine senza transizioni casuali
- Assenza di JavaScript: Scrapy non esegue JS, il che è facilmente rilevabile
- Geolocalizzazione: accesso da un data center invece che da una rete domestica
Il risultato è un ban per IP che dura ore o giorni. Le piattaforme di marketplace (Amazon, Wildberries, Ozon), i social network e i siti con Cloudflare utilizzano una protezione particolarmente aggressiva. I proxy risolvono questo problema distribuendo le richieste tra molti indirizzi IP.
Importante: Anche con i proxy è necessario rispettare i limiti di frequenza. Velocità consigliata: 1-3 richieste al secondo per un singolo IP. Per scraping ad alta velocità, utilizza un pool di 50+ proxy con rotazione.
Configurazione di base del proxy in Scrapy
Il modo più semplice è specificare il proxy direttamente nelle impostazioni del ragno. Questo metodo è adatto per testare o per scraping di piccole quantità di dati con un singolo server proxy.
Metodo 1: Tramite meta in Request
import scrapy
class MySpider(scrapy.Spider):
name = 'example'
start_urls = ['https://example.com']
def start_requests(self):
proxy = 'http://username:password@proxy.example.com:8080'
for url in self.start_urls:
yield scrapy.Request(
url=url,
callback=self.parse,
meta={'proxy': proxy}
)
def parse(self, response):
# La tua logica di scraping
self.log(f'Scraped {response.url} via {response.meta["proxy"]}')
Il formato del proxy dipende dal protocollo e dal metodo di autenticazione:
http://proxy.example.com:8080— senza autenticazionehttp://user:pass@proxy.example.com:8080— con login/passwordsocks5://user:pass@proxy.example.com:1080— proxy SOCKS5
Metodo 2: Configurazione globale in settings.py
# settings.py
# Proxy HTTP per tutte le richieste
HTTPPROXY_ENABLED = True
HTTPPROXY_AUTH_ENCODING = 'utf-8'
# Configurazione tramite variabili d'ambiente
HTTP_PROXY = 'http://username:password@proxy.example.com:8080'
HTTPS_PROXY = 'http://username:password@proxy.example.com:8080'
Questo metodo è comodo per test rapidi, ma non è adatto per la produzione: non c'è rotazione IP, se il proxy fallisce, l'intero scraper si ferma, non è possibile utilizzare proxy diversi per siti diversi.
Creazione di un Proxy Middleware personalizzato
Per lo scraping in produzione è necessario un middleware personalizzato che gestisca il pool di proxy, gestisca gli errori e ruoti automaticamente gli IP. Ecco una implementazione di base:
# middlewares.py
import random
from scrapy import signals
from scrapy.exceptions import NotConfigured
class RandomProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
@classmethod
def from_crawler(cls, crawler):
# Carichiamo la lista dei proxy dalle impostazioni
proxy_list = crawler.settings.getlist('PROXY_LIST')
if not proxy_list:
raise NotConfigured('PROXY_LIST non configurato')
return cls(proxy_list)
def process_request(self, request, spider):
# Selezioniamo un proxy casuale dal pool
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.info(f'Utilizzando proxy: {proxy}')
def process_exception(self, request, exception, spider):
# In caso di errore proviamo un altro proxy
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.warning(
f'Errore proxy, passando a: {proxy}'
)
return request
Ora configuriamo l'uso del middleware in settings.py:
# settings.py
# Lista dei proxy (può essere caricata da un file o API)
PROXY_LIST = [
'http://user1:pass1@proxy1.example.com:8080',
'http://user2:pass2@proxy2.example.com:8080',
'http://user3:pass3@proxy3.example.com:8080',
# ... aggiungi 50+ proxy per una rotazione efficace
]
# Colleghiamo il middleware
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.RandomProxyMiddleware': 350,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}
# Tentativi ripetuti in caso di errori
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
Rotazione dei proxy: tre metodi funzionanti
La selezione casuale dei proxy (come nell'esempio sopra) è il metodo più semplice, ma non il più efficace. Consideriamo tre strategie di rotazione per diversi scenari.
Metodo 1: Round-robin (rotazione sequenziale)
I proxy vengono selezionati in modo circolare. Adatto per una distribuzione uniforme del carico:
class RoundRobinProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.current_index = 0
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Prendiamo il prossimo proxy in circolo
proxy = self.proxy_list[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxy_list)
request.meta['proxy'] = proxy
Metodo 2: Rotazione intelligente con blacklist
Monitoriamo i proxy problematici ed escludiamo temporaneamente dalla rotazione:
import time
from collections import defaultdict
class SmartProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.proxy_errors = defaultdict(int)
self.blacklist = set()
self.blacklist_timeout = 300 # 5 minuti
self.blacklist_time = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def get_working_proxies(self):
# Rimuoviamo dalla blacklist i proxy il cui timeout è scaduto
current_time = time.time()
expired = [
proxy for proxy, ban_time in self.blacklist_time.items()
if current_time - ban_time > self.blacklist_timeout
]
for proxy in expired:
self.blacklist.discard(proxy)
self.proxy_errors[proxy] = 0
# Restituiamo i proxy funzionanti
return [p for p in self.proxy_list if p not in self.blacklist]
def process_request(self, request, spider):
working_proxies = self.get_working_proxies()
if not working_proxies:
spider.logger.error('Tutti i proxy sono in blacklist!')
return
proxy = random.choice(working_proxies)
request.meta['proxy'] = proxy
def process_response(self, request, response, spider):
# Se riceviamo un blocco — aggiungiamo alla blacklist
if response.status in [403, 429, 503]:
proxy = request.meta.get('proxy')
self.proxy_errors[proxy] += 1
if self.proxy_errors[proxy] >= 3:
self.blacklist.add(proxy)
self.blacklist_time[proxy] = time.time()
spider.logger.warning(
f'Proxy {proxy} in blacklist per {self.blacklist_timeout}s'
)
return response
Metodo 3: Rotazione tramite API del fornitore
Molti fornitori di proxy (inclusi proxy residenziali) offrono un endpoint rotante — un URL che cambia automaticamente l'IP ad ogni richiesta:
# settings.py
# Unico endpoint con rotazione automatica
ROTATING_PROXY = 'http://username:password@rotating.proxy.com:8080'
# Middleware semplice
class RotatingProxyMiddleware:
def __init__(self, proxy):
self.proxy = proxy
@classmethod
def from_crawler(cls, crawler):
proxy = crawler.settings.get('ROTATING_PROXY')
return cls(proxy)
def process_request(self, request, spider):
# Un URL, ma ogni richiesta avviene con un nuovo IP
request.meta['proxy'] = self.proxy
Questo è il metodo più comodo per la produzione: non è necessario gestire un pool di proxy, il fornitore si occupa della qualità degli IP e sostituisce quelli problematici. Funziona particolarmente bene con proxy residenziali, dove il pool di IP può raggiungere milioni di indirizzi.
Autenticazione: login/password vs IP whitelist
I fornitori di proxy offrono due metodi di autenticazione. La scelta influisce sulla velocità di connessione e sulla facilità di configurazione.
Autenticazione User:Pass
Login e password vengono trasmessi nell'URL del proxy. Scrapy li converte automaticamente nell'intestazione HTTP Proxy-Authorization:
proxy = 'http://username:password@proxy.example.com:8080'
request.meta['proxy'] = proxy
# Scrapy aggiungerà automaticamente l'intestazione:
# Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Pro: funziona da qualsiasi IP, facile cambiare proxy nel codice.
Contro: piccolo overhead su ogni richiesta (~50-100ms), credenziali in chiaro nel codice.
Autenticazione IP Whitelist
Aggiungi l'IP del tuo server nella whitelist del fornitore, non è necessaria l'autenticazione:
proxy = 'http://proxy.example.com:8080' # senza login/password
request.meta['proxy'] = proxy
Pro: più veloce di 50-100ms, più sicuro (niente credenziali nel codice).
Contro: funziona solo da IP specifici, è necessario aggiornare la whitelist quando si cambia server.
Raccomandazione per la produzione:
Utilizza IP whitelist per lo scraping da server dedicati (AWS, Google Cloud, Hetzner). Per sviluppo e test da macchina locale — autenticazione user:pass.
Gestione degli errori e cambio automatico dell'IP
Anche con proxy di qualità ci saranno errori: timeout, rifiuti di connessione, blocchi. Una corretta gestione degli errori è fondamentale per il funzionamento stabile dello scraper.
Gestione degli stati HTTP
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Codici per cui è necessario cambiare proxy e ripetere
ban_codes = [403, 407, 429, 503]
if response.status in ban_codes:
proxy = request.meta.get('proxy')
spider.logger.warning(
f'Ricevuto {response.status} da {proxy}, riprovando...'
)
# Segniamo per retry con un nuovo proxy
request.meta['dont_retry'] = False
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Gestione delle eccezioni di rete
from twisted.internet.error import TimeoutError, ConnectionRefusedError
from scrapy.exceptions import IgnoreRequest
class ProxyMiddleware:
def process_exception(self, request, exception, spider):
# Errori di connessione al proxy
proxy_errors = (
TimeoutError,
ConnectionRefusedError,
ConnectionLost,
)
if isinstance(exception, proxy_errors):
proxy = request.meta.get('proxy')
spider.logger.error(
f'Connessione proxy {proxy} fallita: {exception}'
)
# Cambiamo proxy e riproviamo
request.meta['proxy'] = self.get_new_proxy()
return request
# Per altre eccezioni utilizziamo la gestione standard
return None
Rilevamento dei blocchi tramite contenuto
Alcuni siti restituiscono HTTP 200, ma mostrano un captcha o una pagina di blocco:
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Segnali di blocco nel contenuto
ban_indicators = [
'captcha',
'access denied',
'blocked',
'unusual traffic',
'robot check',
]
body_text = response.text.lower()
if any(indicator in body_text for indicator in ban_indicators):
spider.logger.warning(
f'Spotted ban page from {request.meta.get("proxy")}'
)
# Cambiamo proxy e ripetiamo
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Quale tipo di proxy scegliere per Scrapy
La scelta del tipo di proxy dipende dal sito target, dal budget e dalla velocità di scraping richiesta. Ecco un confronto delle principali opzioni:
| Tipo di proxy | Velocità | Costo | Quando utilizzare |
|---|---|---|---|
| Proxy di data center | Alta (50-200ms) | Basso ($1-3/IP) | Siti semplici senza protezione, API, strumenti interni |
| Proxy residenziali | Media (300-800ms) | Media ($5-15/GB) | E-commerce, social media, siti con Cloudflare, geotargeting |
| Proxy mobili | Bassa (500-1500ms) | Alta ($50-150/IP) | App mobili, Instagram, TikTok, massima protezione |
Raccomandazioni per la scelta
Per lo scraping di marketplace (Amazon, Wildberries, Ozon, AliExpress) — solo proxy residenziali. Questi siti bloccano aggressivamente i data center. È necessaria la rotazione e il geotargeting (ad esempio, IP russi per Wildberries).
Per lo scraping di siti di notizie, blog, forum — sono adatti i proxy di data center. La protezione è minima, la velocità e il costo del traffico sono importanti.
Per lo scraping di siti con Cloudflare — i proxy residenziali sono obbligatori. I data center vengono rilevati quasi istantaneamente da Cloudflare. Aggiungi a Scrapy la libreria cloudscraper per bypassare le sfide JS.
Per lo scraping di Google Search, strumenti SEO — proxy residenziali con geotargeting. Google mostra risultati diversi per diversi paesi e città.
Consiglio: Inizia con un pool di 10 proxy residenziali per testare. Se ricevi blocchi — aumenta il pool a 50-100 IP. Per scraping ad alta velocità (1000+ richieste/minuto) utilizza un endpoint rotante con un pool di 10.000+ IP.
Tecniche avanzate: sessioni e IP sticky
Durante lo scraping di alcuni siti è necessario mantenere un IP durante l'intera sessione (autenticazione, carrello, moduli multi-passaggio). Ecco come implementare sessioni sticky in Scrapy.
IP sticky per un dominio
from urllib.parse import urlparse
class StickyProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
# Mappa: dominio -> proxy
self.domain_proxy_map = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Estraiamo il dominio dall'URL
domain = urlparse(request.url).netloc
# Se per questo dominio c'è già un proxy — lo utilizziamo
if domain in self.domain_proxy_map:
proxy = self.domain_proxy_map[domain]
else:
# Altrimenti scegliamo uno nuovo e lo memorizziamo
proxy = random.choice(self.proxy_list)
self.domain_proxy_map[domain] = proxy
spider.logger.info(f'Assegnato {proxy} a {domain}')
request.meta['proxy'] = proxy
IP sticky con timeout di sessione
Un'opzione più avanzata: il proxy è legato al dominio per un certo periodo (ad esempio, 10 minuti), poi cambia:
import time
from urllib.parse import urlparse
class SessionProxyMiddleware:
def __init__(self, proxy_list, session_timeout=600):
self.proxy_list = proxy_list
self.session_timeout = session_timeout # 10 minuti
# Mappa: dominio -> (proxy, tempo di creazione)
self.sessions = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
timeout = crawler.settings.getint('PROXY_SESSION_TIMEOUT', 600)
return cls(proxy_list, timeout)
def get_proxy_for_domain(self, domain):
current_time = time.time()
# Controlliamo se c'è una sessione attiva
if domain in self.sessions:
proxy, created_at = self.sessions[domain]
# Se la sessione non è scaduta — utilizziamo lo stesso proxy
if current_time - created_at < self.session_timeout:
return proxy
# Creiamo una nuova sessione con un nuovo proxy
new_proxy = random.choice(self.proxy_list)
self.sessions[domain] = (new_proxy, current_time)
return new_proxy
def process_request(self, request, spider):
domain = urlparse(request.url).netloc
proxy = self.get_proxy_for_domain(domain)
request.meta['proxy'] = proxy
Integrazione con Cookie Middleware
Per sessioni complete è necessario sincronizzare proxy e cookies. Scrapy memorizza i cookies separatamente per ogni dominio, ma quando si cambia proxy è necessario pulire i cookies:
# settings.py
# Attiviamo il cookie middleware
COOKIES_ENABLED = True
COOKIES_DEBUG = False
# Middleware per sincronizzare proxy e cookies
class ProxyCookieMiddleware:
def process_request(self, request, spider):
# Otteniamo il proxy attuale
current_proxy = request.meta.get('proxy')
# Se il proxy è cambiato — puliamo i cookies
previous_proxy = request.meta.get('previous_proxy')
if previous_proxy and previous_proxy != current_proxy:
# Puliamo i cookies per questo dominio
jar = spider.crawler.engine.downloader.middleware.middlewares[0].jars
domain = urlparse(request.url).netloc
if domain in jar:
jar[domain].clear()
spider.logger.info(f'Cookies puliti per {domain}')
request.meta['previous_proxy'] = current_proxy
Conclusione
Una corretta configurazione del proxy in Scrapy è la base per uno scraping stabile senza blocchi. Abbiamo esaminato tutti gli aspetti chiave: dall'integrazione di base a tecniche avanzate di rotazione e gestione delle sessioni.
Principali conclusioni:
- Per la produzione utilizza un middleware personalizzato con rotazione intelligente e blacklist degli IP problematici
- Gestisci tutti i tipi di errori: stati HTTP, eccezioni di rete, blocchi tramite contenuto
- Scegli il tipo di proxy in base al compito: data center per siti semplici, residenziali per siti protetti
- Per siti con autenticazione utilizza sessioni sticky legando il proxy al dominio
- Inizia con un pool di 10-50 proxy, scala in base all'aumento del carico
Se intendi fare scraping di siti protetti (marketplace, social media, siti con Cloudflare), ti consiglio di utilizzare proxy residenziali — offrono la massima anonimato e il minimo rischio di blocchi. Per scraping ad alta velocità, scegli fornitori con endpoint rotante e un pool di 10.000 indirizzi IP.
Tutti gli esempi di codice di questo articolo sono stati testati su Scrapy 2.x e sono pronti per l'uso in produzione. Adattali alle tue esigenze e scala man mano che il progetto cresce.