# 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'environnement `http_proxy` / `https_proxy` configurent un spider sans code. `no_proxy` est respecté uniquement pour les schémas http et https.
- Par requête, `request.meta['proxy']` l'emporte toujours sur l'environnement. Définissez-le sur `None` pour 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ête `Proxy-Authorization: Basic`. L'encodage par défaut est latin-1 et est configurable via `HTTPPROXY_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 :

```python
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@residentialpremium.quantumproxies.io:8080"},
        )
```

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](https://quantumproxies.io/rotating-proxies) 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 :

```python
# middlewares.py
class RotatingGatewayMiddleware:
    PROXY = "http://USER:PASS@residentialpremium.quantumproxies.io:8080"

    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.

![Une requête Scrapy traverse la chaîne de downloader middlewares : le spider la produit, votre middleware en priorité 350 définit meta'proxy' sur la passerelle, le HttpProxyMiddleware intégré en 750 transforme les identifiants en en-tête Proxy-Authorization, et le downloader sort par la passerelle rotative ; les nouvelles tentatives obtiennent une nouvelle IP.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335142/blog/generated/qp-b9-002-diag1.png)
*Votre middleware n'a besoin que de définir meta['proxy']; celui intégré à la priorité 750 gère les identifiants, et les réessais réintègrent la chaîne sur une nouvelle IP.*

## 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 :

```python
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](https://quantumproxies.io/blog/avoid-ip-ban-web-scraping).

## 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 :

```python
# 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](https://quantumproxies.io/blog/fix-429-too-many-requests-scraping) couvre les budgets de concurrence en profondeur.

![Deux configurations de rotation Scrapy côte à côte : une liste de proxies faite maison qui rétrécit à mesure que les IP bannies sont évincées et les requêtes remises en file, et une passerelle rotative, un seul endpoint qui fait tourner les IP côté serveur. La carte : une liste implique contrôles de santé et éviction, 20 IP statiques peuvent s'évaporer en quelques minutes, une passerelle met chaque nouvelle tentative sur une nouvelle IP.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335142/blog/generated/qp-b9-002-diag2.png)
*Une liste DIY signifie des vérifications de santé, une logique d'éviction et des IP obsolètes. Une passerelle est un point de terminaison qui tourne côté serveur — les réessais atterrissent par défaut sur des IP fraîches.*

## 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](https://quantumproxies.io/blog/scraper-returns-empty-page-javascript). Et lorsqu'un domaine défie le HTTP simple peu importe l'IP, confiez ce domaine à une [Scraper API](https://quantumproxies.io/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.

```python
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](https://quantumproxies.io/blog/headless-browser-vs-http-requests-cost) avant d'en utiliser un — la plupart des projets Scrapy ont besoin de meilleures IP, pas d'un navigateur.

[Intégrez des proxies rotatifs dans votre projet Scrapy](https://quantumproxies.io/rotating-proxies)
