Scrapy Proxy Middleware : Rotation des Proxies Sans Bannissement
Scrapy prend en charge les proxies depuis la version 0.8, mais la documentation ne vous explique jamais comment les faire tourner, réessayer et survivre aux bannissements à grande échelle. Voici l'anatomie du middleware, le code, et les paramètres qui décident si votre crawl se termine.
Scrapy propose un proxy middleware depuis la version 0.8, donc la question 'Scrapy prend-il en charge les proxies' a été réglée il y a quinze ans. Ce que la documentation n'assemble jamais vraiment, c'est le tableau de production : comment le middleware proxy intégré de Scrapy traite réellement les identifiants, quand définir des proxies par requête par rapport à globalement, et comment tourner et récupérer lorsqu'une cible commence à bannir les sorties en milieu de crawl. Ce guide parcourt toute la chaîne — le HttpProxyMiddleware intégré, le meta par requête, un middleware de rotation personnalisé avec gestion des bannissements, et les paramètres qui décident si un crawl de 100k pages se termine ou meurt à 3h du matin.
Comment fonctionne le middleware proxy intégré de Scrapy
scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware est activé par défaut avec une priorité de 750 dans DOWNLOADER_MIDDLEWARES_BASE. Lire son code source (environ 100 lignes dans Scrapy 2.17) vous dit tout ce dont vous avez besoin pour le débogage :
- Au démarrage, il appelle
urllib.request.getproxies(), donc les variables d'environnementhttp_proxy/https_proxyconfigurent un spider sans code.no_proxyest respecté uniquement pour les schémas http et https. - Par requête,
request.meta['proxy']l'emporte toujours sur l'environnement. Définissez-le surNonepour forcer une connexion directe pour cette requête. - Les identifiants intégrés dans l'URL du proxy (
http://user:pass@host:port) sont extraits, encodés en Base64 et envoyés en tant qu'en-têteProxy-Authorization: Basic. L'encodage par défaut est latin-1 et est configurable viaHTTPPROXY_AUTH_ENCODING. - Le middleware décode les identifiants avant de les encoder, donc encodez en URL les caractères spéciaux comme
@dans les mots de passe — ils passent correctement. - Si une réessai change le proxy, le middleware supprime l'en-tête d'authentification obsolète au lieu de le divulguer au nouveau proxy — une correction d'hygiène des identifiants dans Scrapy moderne, et une bonne raison de ne pas utiliser des versions anciennes.
Conséquence pratique : vous n'avez presque jamais besoin d'un package de proxy tiers. Tout ce qui met une URL de proxy valide dans request.meta['proxy'] avant la priorité 750 obtient l'authentification et la gestion des en-têtes gratuitement.
Proxies par requête avec méta
L'intégration la plus légère consiste à définir meta directement dans le spider — utile lorsque seules certaines requêtes nécessitent un proxy, ou que différentes cibles nécessitent différents pays de sortie :
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"},
)
La méta par requête brille lorsque le choix du proxy lui-même est basé sur les données : acheminer les pages de produits allemandes via un nom d'utilisateur ciblé pour l'Allemagne, envoyer les téléchargements d'images via des sorties de centre de données bon marché tandis que le HTML passe par résidentiel, ou escalader une URL récalcitrante vers une session plus collante à sa deuxième tentative. Parce que la méta survit aux réessais et aux redirections, la décision que vous prenez lors de la construction de la requête la suit tout au long du cycle de téléchargement. Définir la méta sur des milliers de requêtes à la main ne passe pas à l'échelle, cependant — c'est pour cela que les middlewares de téléchargeur existent.
La configuration de production la plus simple : une passerelle rotative
Avec des proxies rotatifs derrière un seul point de terminaison de passerelle, la rotation se fait côté serveur : chaque requête via la même URL sort d'une IP différente dans un pool résidentiel de plus de 90M couvrant plus de 200 pays. Votre middleware se réduit à trois lignes, et il n'y a pas de liste à vérifier, élaguer ou rafraîchir :
# 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 est importante : votre middleware doit s'exécuter avant celui intégré à 750 pour que HttpProxyMiddleware puisse toujours convertir les identifiants en en-tête d'authentification. Cette association — rotation de passerelle plus la mécanique de réessai standard de Scrapy — offre la fiabilité la plus élevée par ligne de code, car chaque réessai passe automatiquement par une nouvelle IP de sortie.

Un middleware de proxy rotatif personnalisé avec gestion des bannissements
Si vous gérez votre propre liste de proxies — adresses ISP statiques ou un pool mixte — le schéma classique (popularisé par le package scrapy-proxies, qui recommandait RETRY_TIMES = 10 car les proxies de liste gratuite échouent si souvent) est : choisir aléatoirement par requête, évincer sur les signaux de bannissement, remettre la requête en file d'attente :
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
Notez tout ce que ce middleware doit faire qu'une passerelle fait gratuitement : suivre la santé du pool, évincer les IP brûlées, remettre les requêtes en file d'attente. Il se réduit également sous le feu — un pool de 20 IP statiques peut s'évaporer en minutes sur une cible agressive. Le livre de jeu anti-bannissement plus large (cadence, en-têtes, discipline de session) est dans notre checklist d'évitement des bannissements IP.
Paramètres Scrapy qui décident du résultat
Le middleware place le proxy ; les paramètres décident si le crawl se comporte. Ceux-ci sont les plus importants :
# 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 — ajoutez 429 et 403 pour que les bannissements souples déclenchent un réessai (et, avec rotation, une nouvelle IP) au lieu d'un élément échoué.
- RETRY_TIMES — 3-5 est correct pour un pool de qualité ; en avoir besoin de 10 est un signe que le pool lui-même est malsain.
- CONCURRENT_REQUESTS_PER_DOMAIN — le véritable bouton de politesse. La rotation répartit la charge sur les IP, mais la cible voit toujours le volume total.
- DOWNLOAD_TIMEOUT — les 180 secondes par défaut permettent à une sortie lente d'occuper un créneau de concurrence pendant trois minutes. 30 est suffisant.
- AUTOTHROTTLE — adapte la cadence à la latence observée ; se marie bien avec la rotation sur les cibles limitées en débit. Toujours noyé dans les 429 ? Notre guide de limitation de débit couvre les budgets de concurrence en profondeur.

Détection de bannissement au-delà des codes de statut
Les cibles sophistiquées n'envoient pas de 403 honnêtes. Elles servent des 200 contenant une page CAPTCHA, une grille de produits vide, ou un modèle dépouillé. Les spiders robustes vérifient le contenu, pas seulement le statut — vérifiez un élément qui n'existe que sur la vraie page, et remettez en file d'attente lorsqu'il manque. Si les pages reviennent structurellement vides même à travers de bons proxies, le contenu est probablement rendu côté client ; voir pourquoi les scrapers obtiennent des pages vides. Et lorsqu'un domaine défie le HTTP simple peu importe l'IP, confiez ce domaine à une Scraper API qui rend JavaScript et renvoie du HTML propre ou du markdown — Scrapy le consomme comme toute autre réponse, et vous gardez votre 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()}
Questions fréquemment posées
Scrapy prend-il en charge les proxies par défaut ?
Oui. HttpProxyMiddleware est livré activé à la priorité 750 : il lit les variables d'environnement http_proxy / https_proxy et respecte request.meta['proxy'] par requête, y compris les identifiants user:pass@, qu'il convertit en un en-tête Proxy-Authorization. Vous ne codez que pour décider quel proxy chaque requête obtient.
Comment définir un proxy pour une seule requête dans Scrapy ?
Passez-le dans le méta de la requête : scrapy.Request(url, meta={'proxy': 'http://user:pass@host:port'}). La méta l'emporte toujours sur les proxies au niveau de l'environnement, et définir la valeur sur None force cette requête à aller directement — utile pour mélanger le trafic avec et sans proxy dans un seul spider.
Comment faire tourner les proxies dans Scrapy ?
Soit écrivez un middleware de téléchargeur qui choisit un proxy par requête à partir d'une liste et évince ceux bannis, soit pointez chaque requête vers un point de terminaison de passerelle rotative qui assigne une nouvelle IP de sortie côté serveur. L'approche de la passerelle nécessite un middleware de trois lignes, pas de vérifications de santé, et transforme chaque réessai Scrapy en rotation gratuite.
Pourquoi mon proxy Scrapy renvoie-t-il 407 ?
Le proxy a rejeté l'authentification. Vérifiez que les identifiants sont dans l'URL du proxy dans la méta, que les caractères spéciaux sont encodés en URL, et que votre IP de serveur est sur liste blanche si votre plan utilise l'authentification IP. Si les identifiants contiennent des caractères non-ASCII, définissez HTTPPROXY_AUTH_ENCODING pour correspondre à votre fournisseur.
Le schéma à retenir : définissez meta['proxy'] tôt, laissez le middleware intégré à 750 faire l'authentification, ajoutez 429 et 403 à vos codes de réessai, et préférez la rotation côté serveur à la gestion de liste. Si certaines parties de votre crawl nécessitent JavaScript, pesez le coût des navigateurs sans tête par rapport aux requêtes HTTP avant d'en utiliser un — la plupart des projets Scrapy ont besoin de meilleures IP, pas d'un navigateur.