# Middleware proxy Scrapy pour proxies résidentiels rotatifs

> Middleware proxy rotatif Scrapy : IPs résidentielles rotatives et fixées par requête, réglages AutoThrottle et retry, scrapy-playwright. Code vérifié syntaxiquement.

- URL: https://crawlrelay.com/fr/docs/scrapy/
- Dernière mise à jour: 2026-10-13
- Publisher: CrawlRelay (https://crawlrelay.com)

Un middleware de téléchargement de 30 lignes qui choisit le pays et la session par requête, plus les réglages pour garder un crawl respectueux : robots.txt, concurrence par domaine, AutoThrottle et retries.

**Réponse rapide — Comment utiliser des proxies rotatifs avec Scrapy ?**

Réglez `request.meta["proxy"]` à `http://USERNAME:PASSWORD@gw.crawlrelay.com:7777` dans un petit middleware de téléchargement qui tourne avant le `HttpProxyMiddleware` de Scrapy ; Scrapy envoie alors les identifiants pour vous. Avec des proxies résidentiels **CrawlRelay**, le pays et la session sticky vont dans le nom d’utilisateur, donc un seul middleware suffit. Le trafic coûte 0,50 $US/GB à partir de 50 GB. Payable en crypto, sans KYC.

## Configuration Scrapy en un clin d'œil

| Attribut | Valeur |
| --- | --- |
| Où placer le proxy | `request.meta["proxy"]`, défini par votre middleware |
| Ordre des middlewares | Votre middleware sous 750, la priorité du `HttpProxyMiddleware` intégré |
| Identifiants | Dans l’URL proxy ; Scrapy les transforme en un en-tête `Proxy-Authorization` |
| SOCKS5 | Pas avec le gestionnaire HTTP/1.1 par défaut ; Scrapy 2.17+ supporte SOCKS via HttpxDownloadHandler optionnel. Le port HTTP 7777 est le plus simple |
| Rotation | Nouvelle sortie par nouvelle connexion ; un ID de session unique par requête donne normalement une IP différente |
| Respect des usages | `ROBOTSTXT_OBEY`, `CONCURRENT_REQUESTS_PER_DOMAIN`, `AUTOTHROTTLE_ENABLED` |
| Retries | 429 et 5xx sont dans le `RETRY_HTTP_CODES` par défaut ; Retry-After n’est pas lu par défaut |

## Comment écrire le middleware proxy ?

Placez la classe dans `middlewares.py`. Elle construit le nom d’utilisateur à partir des réglages et de trois clés `meta` optionnelles : `proxy_country`, `proxy_session` et `proxy_ttl`. Une requête sans `proxy_session` tourne ; une requête avec `proxy_session="new"` obtient une nouvelle session, donc normalement une IP de sortie différente ; une requête avec un ID de session fixé partage une sortie avec les autres requêtes du même flux. Le middleware recalcule son proxy à chaque passage, retries inclus, et laisse intact un proxy défini par un autre code. Si `CRAWLRELAY_USER` manque, le crawl s’arrête au lancement au lieu de tourner depuis votre propre IP.

*middlewares.py*

```python
import uuid
from urllib.parse import quote


class CrawlRelayProxyMiddleware:
    """Sets request.meta["proxy"]; Scrapy's HttpProxyMiddleware then sends the credentials."""

    def __init__(self, user, password, country):
        self.user, self.password, self.country = user, password, country

    @classmethod
    def from_crawler(cls, crawler):
        s = crawler.settings
        if not s.get("CRAWLRELAY_USER"):
            # Fail loudly: NotConfigured would only disable the middleware and crawl from your own IP
            raise ValueError("CRAWLRELAY_USER is not set: refusing to crawl without the proxy")
        return cls(s.get("CRAWLRELAY_USER"), s.get("CRAWLRELAY_PASSWORD", ""), s.get("CRAWLRELAY_COUNTRY", "us"))

    def process_request(self, request, spider=None):
        if "proxy" in request.meta and not request.meta.get("_crawlrelay"):
            return None  # a proxy set by other code: leave it alone
        parts = [self.user, "country", request.meta.get("proxy_country", self.country)]
        session = request.meta.get("proxy_session")
        if session == "new":  # a fresh session for this request (and for each of its retries)
            session = uuid.uuid4().hex[:12]
        if session:
            parts += ["session", session, "ttl", str(request.meta.get("proxy_ttl", 10))]
        username = "-".join(parts)
        request.meta["proxy"] = "http://%s:%s@gw.crawlrelay.com:7777" % (
            quote(username, safe=""), quote(self.password, safe=""))
        request.meta["_crawlrelay"] = True
        return None
```

## Quels réglages Scrapy garantissent un crawl proxifié rapide et respectueux ?

Les proxies répartissent vos requêtes sur plusieurs IPs, mais la cible voit quand même votre taux total. Ces réglages maintiennent chaque domaine à un rythme soutenable et laissent AutoThrottle ralentir seul quand les réponses ralentissent. Gardez `ROBOTSTXT_OBEY = True`, déjà réglé par les nouveaux projets Scrapy.

*settings.py (extrait)*

```python
import os

BOT_NAME = "myproject"
ROBOTSTXT_OBEY = True
USER_AGENT = "ExampleCrawler/1.0 (+https://example.com/crawler-info)"

CRAWLRELAY_USER = os.environ.get("CRAWLRELAY_USER")  # e.g. the username from your dashboard
CRAWLRELAY_PASSWORD = os.environ.get("CRAWLRELAY_PASSWORD")
CRAWLRELAY_COUNTRY = "us"

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.CrawlRelayProxyMiddleware": 350,  # runs before HttpProxyMiddleware (750)
}

CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 4
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1.0
AUTOTHROTTLE_MAX_DELAY = 30.0
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0

RETRY_ENABLED = True
RETRY_TIMES = 3
RETRY_HTTP_CODES = [429, 500, 502, 503, 504, 522, 524, 408]
DOWNLOAD_TIMEOUT = 45
```

## Comment garder une IP fixe pour la pagination et la faire tourner pour les pages d'articles ?

Passez un `proxy_session` fixe dans les requêtes d'un flux qui dépend de la page précédente, et aucun dans les pages indépendantes. L’araignée ci-dessous fonctionne sur books.toscrape.com, un site sandbox publié pour s'entraîner au scraping.

*spiders/catalog.py*

```python
import scrapy


class CatalogSpider(scrapy.Spider):
    name = "catalog"
    start_urls = ["https://books.toscrape.com/"]

    def parse(self, response):
        for href in response.css("article.product_pod h3 a::attr(href)").getall():
            # Product pages are independent: rotating exits
            yield response.follow(href, callback=self.parse_item)
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            # The listing flow keeps one sticky exit for 30 minutes
            yield response.follow(next_page, callback=self.parse,
                                  meta={"proxy_session": "listing1", "proxy_ttl": 30})

    def parse_item(self, response):
        yield {
            "title": response.css("div.product_main h1::text").get(),
            "price": response.css("p.price_color::text").get(),
            "url": response.url,
        }
```

## Est-ce que Scrapy réutilise les connexions, et cela empêche-t-il la rotation ?

Oui, Scrapy garde les connexions ouvertes et les réutilise par proxy et hôte cible, donc les requêtes consécutives sur une connexion partagent une même sortie. Pour la plupart des crawls, cela fonctionne : Scrapy ouvre jusqu’à `CONCURRENT_REQUESTS_PER_DOMAIN` connexions par site, et chaque nouvelle connexion obtient généralement une IP résidentielle différente.

Quand une requête doit sortir via une nouvelle IP, par exemple une tentative après un [502](https://crawlrelay.com/fr/docs/errors/502-bad-gateway/) dû à une sortie défaillante, renvoyez-la avec `response.request.replace(meta={**response.meta, "proxy_session": "new"}, dont_filter=True)`. Le middleware crée un nouveau nom d'utilisateur de session, un nom d'utilisateur différent signifie un pool de connexions différent, donc Scrapy ouvre une nouvelle connexion et la passerelle attribue normalement une sortie différente.

## Comment respecter Retry-After sur les réponses 429 avec Scrapy ?

Le système intégré de `RetryMiddleware` de Scrapy retente les 429 mais n’attend pas le délai indiqué par `Retry-After`. AutoThrottle ralentit le domaine progressivement, ce qui est généralement suffisant. Pour les API qui envoient de longues valeurs `Retry-After`, ajoutez un petit middleware qui lit l’en-tête et planifie la nouvelle tentative avec délai, ou réduisez `CONCURRENT_REQUESTS_PER_DOMAIN` pour ce domaine. La page [429](https://crawlrelay.com/fr/docs/errors/429-too-many-requests/) explique ce que signifie cet en-tête.

## Comment utiliser un proxy avec scrapy-playwright ?

Pour les pages nécessitant JavaScript, scrapy-playwright transmet les options de lancement de Playwright via un réglage. Donnez les identifiants comme champs séparés : Chromium ignore les identifiants placés dans l’URL du serveur. Utilisez un contexte par session stable si vous rendez des flux déconnectés qui s’étendent sur plusieurs pages.

*settings.py pour scrapy-playwright*

```python
import os

DOWNLOAD_HANDLERS = {
    "http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
    "https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

PLAYWRIGHT_LAUNCH_OPTIONS = {
    "proxy": {
        "server": "http://gw.crawlrelay.com:7777",
        "username": os.environ.get("CRAWLRELAY_USER", "USERNAME") + "-country-us",
        "password": os.environ.get("CRAWLRELAY_PASSWORD", "PASSWORD"),
    },
}
```

## Scrapy et proxies : questions

### Ai-je besoin de scrapy-rotating-proxies ou d’une liste de proxies ?

Non. Ces paquets font tourner une liste d’adresses proxy individuelles et bannissent celles qui échouent. Avec une passerelle résidentielle, la rotation se fait derrière une seule adresse : chaque nouvelle connexion obtient généralement une IP résidentielle différente. Un seul middleware qui définit `request.meta["proxy"]` suffit.

### Pourquoi Scrapy affiche-t-il TunnelError avec mon proxy ?

`TunnelError: Could not open CONNECT tunnel` signifie que le proxy a refusé ou échoué la requête CONNECT pour un site HTTPS. Le statut dans le message indique pourquoi : 407 correspond aux identifiants, 502 ou 504 au problème de sortie ou de cible. Consultez les pages [407](https://crawlrelay.com/fr/docs/errors/407-proxy-authentication-required/) et [502](https://crawlrelay.com/fr/docs/errors/502-bad-gateway/).

### Combien de requêtes simultanées par domaine devrais-je utiliser ?

Commencez avec 2 à 4 par domaine et laissez AutoThrottle ajuster. Augmentez ce nombre seulement si la part de réponses 429, 403 et 5xx reste stable, et surveillez les temps de réponse du site. Les grands sites tolèrent plus que les petits ; un petit site peut nécessiter un délai entre les requêtes et une seule connexion.

### Dois-je désactiver ROBOTSTXT_OBEY pour obtenir plus de données ?

Non. robots.txt est la manière dont un site indique quels chemins les clients automatisés peuvent récupérer, et la norme RFC 9309 a standardisé ce format en 2022. L’ignorer augmente vos risques légaux et éthiques et viole notre [Acceptable Use Policy](https://crawlrelay.com/fr/acceptable-use/) lorsqu’il est utilisé pour accéder à du contenu que le site a exclu.

### Combien de trafic utilise un crawl Scrapy ?

Scrapy télécharge uniquement le HTML, donc les pages coûtent généralement quelques dizaines de kilo-octets chacune, compressées. Vérifiez la valeur `downloader/response_bytes` dans les statistiques que Scrapy affiche en fin de crawl et divisez-la par le nombre de réponses. Le [guide sur la bande passante](https://crawlrelay.com/fr/guides/bandwidth-calculator/) transforme cela en un coût.

**Intégrez des IP résidentielles dans votre projet Scrapy** 5,94 $US pour 10 GB pour tester, 0,50 $US/GB depuis 50 GB. [Voir les tarifs](https://crawlrelay.com/fr/pricing/) · [Guide de concurrence](https://crawlrelay.com/fr/docs/concurrency/)

---

CrawlRelay: CrawlRelay est un service de proxy résidentiel rotatif et proxy mobile 4G/5G pour développeurs, adapté aux scrapers et pipelines de données IA : trafic prépayé sans expiration à partir de 0,50 $US/GB, paiement en crypto, sans téléchargement d’ID. Sans KYC — pas d’ID, pas de selfie, pas de documents. Payer en crypto.
