Proxies rotatifs Selenium en Python : ChromeOptions, authentification et rotation par session
Selenium lie un proxy au lancement du navigateur, pas à la requête — ce qui rend la rotation délicate et la rotation authentifiée délicate d'une autre manière. Voici les trois approches qui fonctionnent réellement dans Selenium 4, avec du code Python pour ChromeOptions, un a
Selenium n'a pas de concept de proxy rotatif. La session de navigateur et l'IP de sortie sont liées au lancement : vous passez --proxy-server= dans ChromeOptions, le driver démarre, et chaque requête effectuée par ce navigateur passe par ce endpoint jusqu'à ce que vous le fermiez.
Ce seul fait de conception explique pourquoi les recherches sur selenium rotating proxy renvoient autant de conseils contradictoires. Certains guides relancent le navigateur une fois par proxy. D'autres se tournent vers une bibliothèque wrapper. Les deux fonctionnent — mais ils échouent à des endroits différents, et ce qui fait échouer la plupart des premières tentatives, c'est l'authentification, pas la rotation elle-même.
Ce guide couvre les mécanismes tels qu'ils s'appliquent à Chrome et Chromium pilotés par Selenium 4 en Python : ce que les flags de proxy acceptent réellement, trois façons praticables de faire de la rotation, comment vérifier l'IP de sortie, et les pièges qui coûtent le plus de temps.
Pourquoi la rotation Selenium diffère de requests, httpx ou Scrapy
requestsethttpxprennent le proxy comme argument par appel. La rotation est un simple changement de dictionnaire.- Scrapy peut échanger les proxies entre les requêtes dans un middleware de téléchargement, sans redémarrer la session.
- Selenium définit le proxy au niveau du processus de navigateur. Il n'existe pas d'argument proxy pris en charge par requête, donc une rotation en cours de session nécessite une couche d'interception comme selenium-wire, ou un travail sur le DevTools Protocol.
La seconde contrainte est le flag propre à Chrome. --proxy-server=socks5://host:port accepte un schéma, un hôte et un port — et rien d'autre. Il n'y a pas de champ pour les identifiants. Si le proxy exige une authentification, Chrome affiche une boîte de dialogue d'authentification HTTP que Selenium ne peut pas remplir et que le mode headless ne rend jamais. Certains endpoints SOCKS5 refusent de la même manière la connexion sans identifiants que le flag n'a nulle part où transporter.
Il y a donc en réalité deux problèmes à résoudre séparément : faire tourner l'IP de sortie, et transmettre les identifiants au proxy.
Trois approches comparées
| Approche | Granularité de rotation | Gère les identifiants | Là où ça fait mal |
|---|---|---|---|
| Relancer le driver par proxy | Par session de navigateur | Non — à associer à une liste d'autorisation d'IP | Chaque rotation coûte un démarrage de navigateur |
Extension d'authentification via --load-extension |
Par session de navigateur | Oui | Plomberie d'extension et bizarreries du mode headless |
| selenium-wire | Par requête | Oui | Dépendance supplémentaire plus une couche d'interception locale |
La plupart des automatisations de navigateur n'ont pas besoin de rotation par requête. Si votre flux de travail consiste à ouvrir une page, attendre le JavaScript, extraire, passer à la suite, la rotation par session suffit généralement et est bien plus facile à déboguer.
Approche 1 : relancer le driver par proxy
C'est l'option la moins magique et la plus facile à appréhender en production.
import random
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
# La documentation va de RFC 5737 - remplacez par vos propres endpoints.
PROXIES = [
'http://198.51.100.10:8080',
'http://198.51.100.11:8080',
'socks5://203.0.113.7:1080',
]
def build_driver(proxy_url: str) -> webdriver.Chrome:
options = Options()
options.add_argument(f'--proxy-server={proxy_url}')
options.add_argument('--headless=new')
options.add_argument('--disable-dev-shm-usage')
return webdriver.Chrome(options=options)
def exit_ip(driver: webdriver.Chrome) -> str:
driver.get('https://api.ipify.org')
return driver.find_element(By.TAG_NAME, 'body').text.strip()
for proxy in random.sample(PROXIES, len(PROXIES)):
driver = build_driver(proxy)
try:
print(f'{proxy} -> {exit_ip(driver)}')
finally:
driver.quit()
Trois habitudes séparent un script qui fonctionne d'un script qui meurt mystérieusement du jour au lendemain :
- Toujours quitter dans un bloc
finally. Les processus chromedriver orphelins retiennent de la mémoire et des verrous de fichiers. - Donnez à chaque worker parallèle son propre répertoire de profil avec
--user-data-dir=/tmp/chrome-worker-1. Deux instances de Chrome partageant un profil se disputeront le verrou. - N'utilisez
--no-sandboxque dans des conteneurs que vous contrôlez, jamais dans une session de bureau normale.
Cette approche fonctionne proprement avec des proxies non authentifiés, et c'est là qu'intervient la liste d'autorisation d'IP.
Approche 2 : transmettre les identifiants au navigateur
Option A : mettre votre propre IP de sortie sur liste d'autorisation
De nombreux fournisseurs vous permettent d'autoriser l'IP depuis laquelle vous vous connectez ; à ce moment-là, le proxy vous accepte sans nom d'utilisateur ni mot de passe et l'approche 1 fonctionne sans modification. Deux mises en garde : votre IP de sortie doit être stable, ce qui est peu pratique en CI ou sur une connexion domestique qui se reconnecte, et une IP mise sur liste d'autorisation est une autorisation permanente que vous devez penser à révoquer.
Option B : une extension qui répond à l'invite d'authentification
Les extensions Chrome peuvent fournir des identifiants de proxy via chrome.webRequest.onAuthRequired. Une paire minimale pour Manifest V3 ressemble à ceci.
{
"manifest_version": 3,
"name": "proxy-auth",
"version": "1.0",
"permissions": ["webRequest", "webRequestAuthProvider"],
"host_permissions": ["<all_urls>"],
"background": { "service_worker": "background.js" }
}
chrome.webRequest.onAuthRequired.addListener(
(details, callback) => {
callback({ authCredentials: { username: 'USER', password: 'PASS' } });
},
{ urls: ['<all_urls>'] },
['asyncBlocking']
);
Chargez-la au démarrage du driver :
options.add_argument('--disable-extensions-except=/opt/proxy-auth')
options.add_argument('--load-extension=/opt/proxy-auth')
Deux choses à savoir avant de vous appuyer là-dessus. La gestion du callback d'authentification a changé au fil des versions de Chrome ; si la forme avec callback est refusée, renvoyez l'objet d'identifiants depuis l'écouteur au lieu d'appeler callback(). Et le chargement d'extension en mode headless est la partie la plus susceptible de vous surprendre — utilisez --headless=new plutôt que l'ancien mode headless.
Option C : selenium-wire
Si vous avez réellement besoin d'une rotation par requête ou en cours de session, selenium-wire place un petit proxy local devant le navigateur et vous permet de définir le proxy amont en Python.
from selenium.webdriver.common.by import By
from seleniumwire import webdriver
proxy = 'http://USER:[email protected]:8080'
wire_options = {
'proxy': {
'http': proxy,
'https': proxy,
'no_proxy': 'localhost,127.0.0.1',
}
}
driver = webdriver.Chrome(seleniumwire_options=wire_options)
try:
driver.get('https://api.ipify.org')
print(driver.find_element(By.TAG_NAME, 'body').text)
finally:
driver.quit()
Comme une couche d'interception locale est impliquée, épinglez les versions de selenium-wire et de Selenium ensemble et retestez après la mise à jour de l'une ou l'autre.
Avez-vous besoin de SOCKS5 spécifiquement pour Selenium ?
En général non, mais c'est pris en charge. Chrome accepte socks5://host:port dans --proxy-server, et SOCKS5 transmet le trafic au niveau de la socket, ce qui est utile lorsque votre pool est uniquement SOCKS. La limitation liée aux identifiants est identique : le flag n'a nulle part où mettre un nom d'utilisateur, vous revenez donc à la liste d'autorisation ou à une extension.
Une précision importante pour la vérification : router le navigateur via SOCKS5 contrôle le trafic TCP. Cela n'empêche pas, à soi seul, WebRTC d'ouvrir un chemin UDP non proxifié. Voir la checklist ci-dessous.
Choisir une stratégie de rotation pour l'automatisation de navigateur
| Stratégie | Déclencheur de rotation | Convient à | Compromis |
|---|---|---|---|
| Session sticky par tâche | Au démarrage de la tâche | Connexions, flux multi-étapes, listes paginées | Une IP absorbe toute la tâche |
| Toutes les N pages ou par fenêtre temporelle | Compteur ou minuteur | Crawl large, pages de listes | Une rotation en cours de flux peut casser l'état |
| Par session de navigateur | À chaque lancement de driver | Scraping simple et captures d'écran | Le coût de lancement domine le temps d'exécution |
| Un proxy par worker | Au démarrage du worker | Crawls parallèles | La charge par IP doit être suivie |
Comme une session de navigateur est coûteuse, la rotation par requête est généralement un mauvais choix par défaut pour Selenium — vous payez un redémarrage de processus pour chaque URL. La cohérence de localisation compte aussi davantage que le volume brut d'IP pour les résultats dépendants de la géolocalisation. Si vous échantillonnez des pages localisées, des vitrines ou des prix pour un marché particulier, gardez la région de sortie fixe pendant toute la session, sinon les chiffres que vous collectez ne seront pas reproductibles lors de l'exécution suivante.
Vérifier l'IP de sortie, le DNS et WebRTC
La vérification n'est pas facultative pour les exécutions headless, car il n'y a aucune fenêtre à regarder. Vérifiez trois choses.
1. L'IP de sortie a réellement changé. Réutilisez le helper exit_ip ci-dessus et assurez-vous que la valeur diffère de votre propre adresse. Comparez occasionnellement avec un second endpoint d'écho pour ne pas être trompé par une réponse mise en cache.
2. Comportement de résolution DNS. Avec un proxy HTTP, le nom d'hôte est normalement résolu par le proxy, ce qui est ce que vous voulez. Le traitement SOCKS5 mérite d'être confirmé plutôt que supposé : ouvrez une page publique de test de fuite DNS dans une exécution non headless et comparez le résolveur affiché avec celui de votre FAI.
3. WebRTC. Chrome peut utiliser un chemin UDP non proxifié même lorsque le proxy est configuré. Le flag ci-dessous force WebRTC à éviter l'UDP non proxifié.
options.add_argument('--force-webrtc-ip-handling-policy=disable_non_proxied_udp')
Enfin, journalisez le proxy utilisé et l'IP de sortie observée pour chaque session. Lorsqu'une page renvoie un challenge, la mauvaise langue ou un prix auquel vous ne vous attendiez pas, cette ligne de log est la première chose que vous voudrez consulter.
Les pièges qui font perdre le plus de temps
- Fuite de driver. Chaque chromedriver orphelin retient de la mémoire. Dans un conteneur, cela ressemble généralement à un crash mystérieux plutôt qu'à une fuite. Utilisez
try/finallypartout. - Répertoires de profil partagés. Des instances parallèles partageant un répertoire de données utilisateur se bloquent mutuellement. Un répertoire par worker.
- Interpréter des échecs comme des blocages. Un 403, une réponse vide et une page de challenge sont trois problèmes différents. Journalisez le code de statut, le titre de la page et la longueur du corps avant de blâmer le proxy.
- Extrapoler à partir d'une seule page. Vérifiez l'IP de sortie avant d'automatiser une centaine de pages, pas après.
- Oublier les règles non techniques. Faire tourner une IP ne change pas ce que permettent les conditions d'utilisation d'un site, son robots.txt ou la loi applicable sur la protection des données — en particulier si vous collectez des données personnelles dans une juridiction RGPD. Ralentissez, reculez en cas d'erreurs et gardez une concurrence modeste.
La version courte
- Selenium lie un proxy à un lancement de navigateur, donc la rotation implique des redémarrages, sauf si vous ajoutez une couche d'interception.
- Le flag
--proxy-serverde Chrome ne peut pas transporter d'identifiants. Choisissez la liste d'autorisation d'IP, une extension d'authentification ou selenium-wire. - Préférez la rotation par session ou sticky à la rotation par requête pour l'automatisation de navigateur.
- Vérifiez l'IP de sortie dans la session, désactivez l'UDP WebRTC non proxifié et journalisez quel proxy a servi quelle requête.
- Quittez le driver dans
finally, isolez les répertoires de profil par worker et validez la configuration sur une URL avant de la passer à l'échelle.