Base de connaissances

Comment faire tourner des proxys résidentiels dans Scrapy avec un middleware personnalisé

Scrapy pose le proxy via meta, mais le piège du cache de Proxy-Authorization casse la rotation. Un middleware personnalisé qui fait tourner l'identité et réessaie sur les blocages.

Chris Collins

Chris Collins

8 août 2026 · 8 min de lecture

Scrapy est le framework vers lequel vous vous tournez quand un scrape dépasse un script : il gère l’ordonnancement, la concurrence, les réessais et les pipelines d’origine. Ajouter un proxy résidentiel est direct, mais Scrapy le fait différemment d’un client HTTP simple. Le proxy est un réglage par requête géré par un downloader middleware, et cette architecture a un piège spécifique autour de l’authentification qui casse la rotation en silence. Comprenez le modèle de middleware et tout s’emboîte.

Ceci est l’entrée Scrapy aux côtés de proxys résidentiels avec Python, qui couvre requests et httpx. Le pipeline de downloader middleware de Scrapy est une autre bête, alors il reçoit son propre traitement ici.

Tout ci-dessous utilise le gateway résidentiel de Shifter : un point de terminaison, p.shifter.io:443, avec tout le ciblage encodé dans le nom d’utilisateur. Changez l’hôte et les identifiants pour un autre fournisseur ; la forme est la même.

Le modèle du gateway en un paragraphe

Le nom d’utilisateur du proxy porte votre authentification et votre ciblage. Vous ne changez pas de point de terminaison pour changer de pays ou de session, vous changez la chaîne du nom d’utilisateur :

customer-USERNAME-country-us-sid-abc123-ttl-600

country-us cible les États-Unis, sid fixe une session sticky, ttl maintient cette IP pendant N secondes. Omettez sid/ttl et chaque nouvelle connexion tourne. Le mot de passe est constant. Dans Scrapy, ce nom d’utilisateur devient l’en-tête Proxy-Authorization, et faire tourner l’identité signifie le changer par requête.

Comment Scrapy gère les proxys

Scrapy route le proxy de chaque requête à travers le HttpProxyMiddleware intégré, qui lit request.meta['proxy']. Le montage naïf est de mettre les identifiants en ligne dans cette URL :

# Le one-liner tentant. Il marche, jusqu'à ce que vous tourniez.
request.meta['proxy'] = 'http://customer-USER-country-us:PASS@p.shifter.io:443'

Cela marche pour une identité fixe unique. Cela casse au moment où vous essayez de tourner, et la raison est le piège qui vaut la peine de connaître.

Le piège : Proxy-Authorization est mis en cache

Quand HttpProxyMiddleware voit des identifiants dans l’URL du proxy, il les encode en base64 dans un en-tête Proxy-Authorization et, crucialement, met cet en-tête en cache sur la requête. Si une requête est plus tard réessayée ou redirigée et que son meta['proxy'] change vers une identité différente, le middleware ne recalcule pas toujours l’en-tête, donc la requête part avec un Proxy-Authorization périmé pour le nom d’utilisateur précédent. Sur un gateway où le nom d’utilisateur porte votre géo et votre session, cela signifie que votre rotation ne tourne pas en silence : vous changez le nom d’utilisateur dans meta['proxy'], mais la requête s’authentifie toujours comme l’ancienne.

Le correctif est de cesser de mettre des identifiants dans l’URL du proxy entièrement. Posez l’hôte du proxy sans userinfo, et posez l’en-tête Proxy-Authorization vous-même, explicitement, sur chaque requête. C’est exactement à cela que sert un middleware personnalisé.

Un middleware de rotation personnalisé

Mettez l’hôte dans meta['proxy'] sans identifiants, et calculez l’en-tête d’auth par requête à partir de l’identité que vous voulez. Parce que le ciblage vit dans le nom d’utilisateur, choisir un pays et une session est juste construire le bon nom d’utilisateur.

middlewares.py
import os
from w3lib.http import basic_auth_header
class ShifterProxyMiddleware:
def __init__(self):
self.user = os.environ['SHIFTER_USER']
self.password = os.environ['SHIFTER_PASS']
self.endpoint = 'http://p.shifter.io:443' # hote seul, pas d'identifiants
def process_request(self, request, spider):
country = request.meta.get('country', 'us')
sid = request.meta.get('sid') # posez-le pour une session sticky, omettez pour tourner
username = f"{self.user}-country-{country}" + (f"-sid-{sid}-ttl-600" if sid else "")
request.meta['proxy'] = self.endpoint
request.headers['Proxy-Authorization'] = basic_auth_header(username, self.password)

Activez-le, et laissez-le tourner avant le middleware de proxy intégré pour que l’en-tête que vous posez soit celui qui part :

settings.py
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.ShifterProxyMiddleware': 350, # avant HttpProxyMiddleware (750)
}

Maintenant chaque requête porte son propre Proxy-Authorization fraîchement calculé, donc changer country ou sid dans le meta d’une requête change vraiment l’identité. Posez sid sur les requêtes qui appartiennent à une unité logique de travail pour qu’elles partagent une IP, et laissez-le de côté pour tourner par connexion (sticky vs rotatif couvre la distinction). Mapper le travail aux identités de cette façon est le motif de répartition de charge en forme de Scrapy.

Tournez au réessai, pas seulement sur planning

Le RetryMiddleware de Scrapy réessaie déjà les timeouts et les 5xx, mais par défaut il réessaie avec la même identité, ce qui est inutile si la raison de l’échec était que cette identité s’est fait bloquer. Le geste à haute valeur est de faire tourner l’identité spécifiquement quand une requête échoue ou revient défiée. Dans votre middleware, détectez un blocage doux ou un 403/429 et replanifiez la requête avec une nouvelle identité :

def process_response(self, request, response, spider):
if response.status in (403, 429) or looks_blocked(response):
new = request.copy()
new.meta.pop('sid', None) # jette la session brulee -> IP fraiche
new.dont_filter = True
return new # reessaie a travers une nouvelle identite
return response

Détecter le blocage doux est sa propre discipline, un 200 peut toujours être une page de blocage, alors associez cela aux vérifications de détecter du contenu bloqué ou faux. Traiter une réponse défiée comme un signal pour tourner, au lieu de l’accepter, est ce qui garde un crawl long en vie.

Utilisez les boutons de courtoisie de Scrapy

Scrapy vous donne les contrôles de limitation de débit qu’un scraper fait main doit construire, et avec une flotte de proxys ils comptent plus, pas moins. Bornez la concurrence par domaine pour qu’une cible ne soit pas martelée, ajoutez un délai, et activez AutoThrottle pour vous adapter aux réponses du site :

settings.py
CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 8 # plafond par cible, celui qui compte
DOWNLOAD_DELAY = 0.5
AUTOTHROTTLE_ENABLED = True
RETRY_ENABLED = True
RETRY_TIMES = 3

La concurrence par domaine est le bouton qui vous empêche de transformer un pool de proxys en marteau distribué. Plus de parallélisme au-delà de la tolérance d’une cible achète des blocages, pas du débit (comment éviter de se faire bloquer et scraper de façon responsable s’appliquent tous deux), et AutoThrottle qui recule sur les réponses lentes est exactement la retenue dont un crawl sain de longue durée a besoin.

Vérifiez que vous êtes bien sur le proxy

Pointez un spider vers un endpoint qui renvoie l’IP et vérifiez l’IP de sortie avant de faire confiance à une exécution :

def start_requests(self):
yield scrapy.Request('http://ip-api.com/json',
meta={'country': 'us'},
callback=self.parse) # attendez une IP résidentielle américaine

Votre propre IP signifie que le middleware ne s’applique pas, ou est ordonné après HttpProxyMiddleware. Un mur de timeouts signifie que l’en-tête d’auth est mauvais ou manquant. Les deux sont couverts dans le guide de diagnostic des timeouts.

FAQ

Pourquoi ma rotation de proxy ne tourne-t-elle pas vraiment dans Scrapy ? Presque certainement le piège du cache de Proxy-Authorization : vous avez mis des identifiants dans l’URL du proxy, et HttpProxyMiddleware a mis l’en-tête d’auth en cache, donc quand vous changez meta['proxy'] à un réessai la requête envoie toujours les anciens identifiants. Posez l’hôte sans identifiants et calculez l’en-tête Proxy-Authorization vous-même dans un middleware, par requête.

Où vont les flags de ciblage ? Dans le nom d’utilisateur, qui devient le Proxy-Authorization. Un middleware personnalisé construit customer-USER-country-<cc>-sid-<id>-ttl-<sec> à partir du meta par requête, donc choisir géo et session est juste poser country et sid sur la requête.

Comment donner à une requête spécifique une session sticky ? Posez un sid stable dans le meta de cette requête et réutilisez-le entre les requêtes qui vont ensemble ; omettez sid pour tourner à chaque connexion. Le middleware le transforme en le bon nom d’utilisateur.

Dois-je tourner à chaque requête ou au réessai ? Les deux ont leur place. Tournez par unité logique de travail pour le trafic normal, et forcez en plus une identité fraîche quand une requête revient bloquée ou limitée, pour qu’une IP brûlée ne soit pas réessayée comme elle-même.

Ai-je encore besoin de DOWNLOAD_DELAY et AutoThrottle avec des proxys rotatifs ? Oui. La rotation répartit la charge sur les IP, mais la concurrence par domaine, le délai et AutoThrottle vous empêchent de submerger une seule cible peu importe combien d’IP vous avez. La courtoisie et la rotation résolvent des problèmes différents.

En résumé

Scrapy plus proxys résidentiels est puissant dès que vous contournez son unique piège : ne mettez pas d’identifiants dans l’URL du proxy, parce que le Proxy-Authorization en cache défait la rotation en silence. À la place, écrivez un petit downloader middleware qui pose l’hôte dans meta['proxy'] et calcule l’en-tête Proxy-Authorization par requête à partir de l’identité que vous voulez, faites tourner cette identité par unité logique de travail et à nouveau sur toute réponse bloquée ou limitée, et appuyez-vous sur la concurrence par domaine et AutoThrottle de Scrapy pour rester courtois.

Faites cela et l’ordonnanceur, les réessais et les pipelines de Scrapy travaillent avec votre couche de proxy au lieu de contre elle. Pointez le crawl vers le gateway résidentiel, et souvenez-vous que la qualité du pool décide à quelle fréquence vous réessayez tout court (réputation d’IP). La page tarifs propose les forfaits au Go pour le tester contre vos propres cibles.

Prêt à commencer ?

Essayez les proxies résidentiels de Shifter, 205M+ IPs, 195+ pays, à partir de $0.75/GB.

Commencer