Middleware de proxy Scrapy : guide de configuration complet (2026)

Publié le 4 juin 2026 · ≈11 min de lecture

Scrapy reste le cheval de bataille des crawls en production en 2026 — et reste le framework où la configuration de proxy déroute le plus les gens, parce qu'il existe quatre endroits différents où brancher un proxy et que trois d'entre eux sont inadaptés à la plupart des projets. Ce guide vous donne le bon : un petit middleware personnalisé avec routage par requête, sessions sticky, détection de bannissements et un comportement de retry sensé.

Si vous utilisez plutôt requests/httpx/aiohttp tout court, consultez Comment faire tourner des proxies en Python. Cet article est spécifique à Scrapy.

La version en 30 secondes

Pour une passerelle résidentielle rotative, la configuration minimale viable tient en une ligne par requête — sans middleware :

def start_requests(self):
    for url in self.urls:
        yield scrapy.Request(
            url,
            meta={"proxy": "http://USERNAME:[email protected]:913"},
        )

Le HttpProxyMiddleware intégré de Scrapy lit request.meta["proxy"] et gère l'authentification à partir de l'URL. La passerelle fait tourner l'IP de sortie à votre place. Si c'est tout ce dont vous avez besoin, arrêtez-vous ici. Le reste de ce guide concerne les cas où vous avez besoin de contrôle : sessions sticky, routage par pays, rotation consciente des bannissements et réglage de la concurrence.

Un middleware de proxy pour la production

Placez ceci dans middlewares.py. Il attribue des sessions sticky par domaine, fait tourner en cas de bannissement et étiquette chaque requête pour que vous puissiez déboguer quelle session a récupéré quoi :

import random
import string

GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME"          # move to settings.py / env in real projects
PASSWORD = "PASSWORD"

def _new_session(n=8):
    return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))

class JibaoProxyMiddleware:
    """Sticky session per domain; rotate session on ban."""

    def __init__(self):
        self.sessions = {}          # domain -> session id

    def _proxy_url(self, session_id):
        user = f"{USERNAME}-session-{session_id}"
        return f"http://{user}:{PASSWORD}@{GATEWAY}"

    def process_request(self, request, spider):
        domain = request.url.split("/")[2]
        session = self.sessions.setdefault(domain, _new_session())
        request.meta["proxy"] = self._proxy_url(session)
        request.meta["proxy_session"] = session

    def rotate(self, domain):
        """Call when a session is burned."""
        self.sessions[domain] = _new_session()

Et un middleware de téléchargement complémentaire qui détecte les bannissements et réessaie sur une session neuve :

from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message

BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")

class BanAwareRetryMiddleware(RetryMiddleware):

    def process_response(self, request, response, spider):
        banned = (
            response.status in BAN_CODES
            or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
        )
        if banned:
            domain = request.url.split("/")[2]
            proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
            for mw in proxy_mw:
                if hasattr(mw, "rotate"):
                    mw.rotate(domain)        # burn the session
            reason = response_status_message(response.status)
            return self._retry(request, reason, spider) or response
        return super().process_response(request, response, spider)

Branchez les deux dans settings.py :

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.JibaoProxyMiddleware": 350,
    "scrapy.downloadermiddlewares.retry.RetryMiddleware": None,   # replace stock retry
    "myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2

La priorité compte : le middleware de proxy doit s'exécuter avant le HttpProxyMiddleware de Scrapy (750), donc n'importe quelle valeur sous 750 fonctionne ; 350 le maintient tôt et prévisible.

Sticky vs rotatif : quel mode pour quel spider

Type de crawlModeImplémentation
Collecte de pages sans étatRotatifIdentifiant simple, la passerelle fait tourner par requête
Login + crawl derrière authentificationSticky par compte-session-{account_id}, ne jamais faire tourner en plein login
Listings à forte paginationSticky par domaine, rotation en cas de bannissementLe middleware ci-dessus
Tarifs spécifiques par géographieRotatif + épinglage de paysParamètres de style USERNAME-country-de

Traitement plus approfondi de ce compromis : Sessions de proxy sticky vs rotatives.

Réglages de concurrence qui ne vous font pas bannir

Les valeurs par défaut de Scrapy sont calibrées pour un crawling poli sur une seule IP. Derrière un pool rotatif vous pouvez pousser beaucoup plus fort — mais les limites par domaine comptent toujours parce que la cible voit le comportement agrégé :

# settings.py - sane starting point behind a residential pool
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8     # what the target experiences
DOWNLOAD_DELAY = 0.25                  # jitter applied per slot
RANDOMIZE_DOWNLOAD_DELAY = True        # 0.5x-1.5x the delay
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True

Augmentez CONCURRENT_REQUESTS_PER_DOMAIN uniquement après avoir observé votre taux de 403 au niveau actuel pendant quelques milliers de requêtes. Passer de 8 → 32 parce que « de toute façon les proxies tournent » est exactement la façon dont les gens brûlent des GB en retries.

Discipline de bande passante (les GB résidentiels, c'est de l'argent)

Échecs courants et ce qu'ils signifient vraiment

407 Proxy Authentication Required

Les identifiants ne sont pas arrivés au proxy. Mettez-les dans l'URL (http://user:pass@host:port) dans meta["proxy"] — Scrapy les parse et définit Proxy-Authorization pour vous. Configurer l'en-tête manuellement et utiliser des identifiants dans l'URL provoque des bizarreries de double authentification ; choisissez l'un ou l'autre.

TunnelError: Could not open CONNECT tunnel

Presque toujours un host/port mal saisi, ou une cible HTTPS via un endpoint qui n'autorise pas CONNECT sur ce port. Vérifiez d'abord avec curl -x en dehors de Scrapy.

Le spider fonctionne pendant 10 minutes, puis tout est en 403

Votre session sticky a fait de l'usure, ou votre cadence par domaine est trop élevée. Le middleware conscient des bannissements ci-dessus gère le premier cas ; baissez CONCURRENT_REQUESTS_PER_DOMAIN pour le second. Si c'est une cible qui vérifie JA4, la pile TLS de Scrapy elle-même peut être le signal révélateur — voyez JA3/JA4 expliqués pour comprendre pourquoi aucun proxy ne corrige cela.

Outil gratuit · sans inscription

Validez votre liste de proxies avant le crawl

Collez les endpoints dans notre Proxy Checker : il teste en lot la connectivité, la latence, le niveau d'anonymat et le type d'IP de sortie — repérez les proxies morts ou mal étiquetés avant que Scrapy ne gaspille des retries dessus.

Vérifier mes proxies →

Fatigué de materner des listes gratuites ? Une seule passerelle résidentielle les remplace toutes — obtenez 500 Mo de trafic gratuit →

Résumé

Pointez vos spiders vers un vrai pool

Sessions résidentielles rotatives et sticky sur une seule passerelle. 500 Mo de trafic gratuit pour crawler.

Démarrer l'essai gratuit

Pour tous les produits IP · un immense pool de nœuds disponibles à tout moment

Inscrivez-vous maintenant et obtenez jusqu'à 100 % de remboursement sur votre recharge

Les nouveaux utilisateurs reçoivent 500 Mo à l'inscription, plus un bonus sur la première recharge. Offre à durée limitée.