Selenium-Rotating-Proxies in Python: ChromeOptions, Auth und Rotation pro Sitzung
Selenium bindet einen Proxy an den Browserstart, nicht an die Anfrage – was die Rotation umständlich macht und authentifizierte Rotation auf eine andere Weise umständlich. Hier sind die drei Ansätze, die in Selenium 4 tatsächlich funktionieren, mit Python-Code für ChromeOptions, ein a
Selenium kennt kein Konzept eines rotierenden Proxys. Browsersitzung und Exit-IP sind beim Start aneinander gebunden: Man übergibt --proxy-server= an ChromeOptions, der Treiber startet, und jede Anfrage, die dieser Browser stellt, läuft über diesen Endpunkt, bis man ihn beendet.
Diese eine Design-Tatsache erklärt, warum Suchen nach selenium rotating proxy so viele widersprüchliche Ratschläge liefern. Manche Anleitungen starten den Browser einmal pro Proxy neu. Andere greifen zu einer Wrapper-Bibliothek. Beides funktioniert – aber es scheitert an unterschiedlichen Stellen, und was die meisten ersten Versuche scheitern lässt, ist die Authentifizierung, nicht die Rotation selbst.
Dieser Leitfaden behandelt die Mechanik für Chrome und Chromium unter Selenium 4 in Python: was die Proxy-Flags tatsächlich akzeptieren, drei praktikable Wege zur Rotation, wie man die Exit-IP überprüft und die Fallstricke, die die meiste Zeit kosten.
Warum sich die Rotation in Selenium von requests, httpx oder Scrapy unterscheidet
requestsundhttpxnehmen den Proxy als Argument pro Aufruf. Rotation ist eine Änderung im Dictionary.- Scrapy kann Proxys zwischen Anfragen in einer Downloader-Middleware austauschen, ohne die Sitzung neu zu starten.
- Selenium setzt den Proxy für den Browserprozess. Es gibt kein unterstütztes Proxy-Argument pro Anfrage, daher erfordert Rotation mitten in der Sitzung eine Interception-Schicht wie selenium-wire oder Arbeit mit dem DevTools-Protokoll.
Die zweite Einschränkung ist Chromes eigenes Flag. --proxy-server=socks5://host:port akzeptiert ein Scheme, einen Host und einen Port – und nichts weiter. Es gibt kein Feld für Zugangsdaten. Wenn der Proxy eine Authentifizierung verlangt, zeigt Chrome einen HTTP-Auth-Dialog an, den Selenium nicht ausfüllen kann und den der Headless-Modus überhaupt nie rendert. Manche SOCKS5-Endpunkte verweigern die Verbindung ebenfalls ohne Zugangsdaten, für die das Flag keinen Platz hat.
Es gibt also wirklich zwei Probleme, die getrennt zu lösen sind: die Exit-IP rotieren und die Zugangsdaten zum Proxy bringen.
Drei Ansätze im Vergleich
| Ansatz | Rotationsgranularität | Verarbeitet Zugangsdaten | Wo es schmerzt |
|---|---|---|---|
| Treiber pro Proxy neu starten | Pro Browsersitzung | Nein – mit IP-Allowlisting kombinieren | Jede Rotation kostet einen Browserstart |
Auth-Erweiterung via --load-extension |
Pro Browsersitzung | Ja | Erweiterungs-Gefrickel und Headless-Eigenheiten |
| selenium-wire | Pro Anfrage | Ja | Zusätzliche Abhängigkeit plus lokale Interception-Schicht |
Die meisten Browser-Automatisierungen brauchen keine Rotation pro Anfrage. Wenn dein Workflow darin besteht, eine Seite zu öffnen, auf JavaScript zu warten, zu extrahieren und weiterzuziehen, ist Rotation pro Sitzung normalerweise ausreichend und deutlich einfacher zu debuggen.
Ansatz 1: Treiber pro Proxy neu starten
Dies ist die am wenigsten magische Option und diejenige, über die man in der Produktion am einfachsten nachdenken kann.
import random
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
# Documentation ranges from RFC 5737 - replace with your own 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()
Drei Gewohnheiten unterscheiden ein Skript, das funktioniert, von einem, das über Nacht auf mysteriöse Weise stirbt:
- Immer in einem
finally-Block beenden. Verwaiste chromedriver-Prozesse halten Speicher und Dateisperren. - Gib jedem parallelen Worker sein eigenes Profilverzeichnis mit
--user-data-dir=/tmp/chrome-worker-1. Zwei Chrome-Instanzen, die ein Profil teilen, konkurrieren um die Sperre. - Verwende
--no-sandboxnur in Containern, die du kontrollierst, niemals in einer normalen Desktop-Sitzung.
Dieser Ansatz funktioniert sauber mit nicht authentifizierten Proxys, und hier kommt IP-Allowlisting ins Spiel.
Ansatz 2: Zugangsdaten zum Browser bringen
Option A: die eigene Egress-IP allowlisten
Viele Anbieter erlauben es, die IP zu autorisieren, von der aus du anfragst; dann akzeptiert der Proxy dich ohne Benutzername oder Passwort, und Ansatz 1 funktioniert unverändert. Zwei Einschränkungen: Deine Egress-IP muss stabil sein, was in CI oder bei einer Heimverbindung, die sich neu verbindet, unpraktisch ist, und eine allowlistete IP ist eine dauerhafte Autorisierung, die du dir merken musst zu widerrufen.
Option B: eine Erweiterung, die die Auth-Aufforderung beantwortet
Chrome-Erweiterungen können Proxy-Zugangsdaten über chrome.webRequest.onAuthRequired bereitstellen. Ein minimales Manifest-V3-Paar sieht so aus.
{
"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']
);
Lade sie, wenn der Treiber startet:
options.add_argument('--disable-extensions-except=/opt/proxy-auth')
options.add_argument('--load-extension=/opt/proxy-auth')
Zwei Dinge, die du wissen solltest, bevor du darauf aufbaust. Die Behandlung von Auth-Callbacks hat sich über Chrome-Versionen hinweg geändert; wenn die Callback-Form abgelehnt wird, gib das Credentials-Objekt aus dem Listener zurück, anstatt callback() aufzurufen. Und das Laden von Erweiterungen im Headless-Modus ist der Teil, der dich am ehesten überraschen wird – verwende --headless=new statt des alten Headless-Modus.
Option C: selenium-wire
Wenn du wirklich Rotation pro Anfrage oder mitten in der Sitzung brauchst, setzt selenium-wire einen kleinen lokalen Proxy vor den Browser und lässt dich den Upstream-Proxy in Python festlegen.
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()
Da eine lokale Interception-Schicht beteiligt ist, pinne selenium-wire und Selenium-Versionen zusammen und teste nach einem Upgrade von einem der beiden erneut.
Brauchst du SOCKS5 speziell für Selenium?
Normalerweise nicht, aber es wird unterstützt. Chrome akzeptiert socks5://host:port in --proxy-server, und SOCKS5 übergibt den Datenverkehr auf der Socket-Ebene, was nützlich ist, wenn dein Pool nur SOCKS unterstützt. Die Einschränkung bei den Zugangsdaten ist identisch: Das Flag hat keinen Platz für einen Benutzernamen, also bist du wieder bei Allowlisting oder einer Erweiterung.
Eine Klarstellung, die für die Verifizierung wichtig ist: Wenn man den Browser über SOCKS5 leitet, wird TCP-Datenverkehr kontrolliert. Das allein verhindert nicht, dass WebRTC einen nicht über den Proxy geleiteten UDP-Pfad öffnet. Siehe die Checkliste unten.
Eine Rotationsstrategie für Browser-Automatisierung wählen
| Strategie | Rotationsauslöser | Passt zu | Kompromisse |
|---|---|---|---|
| Sticky Session pro Aufgabe | Beim Aufgabenstart | Logins, mehrstufige Abläufe, paginierte Listen | Eine IP absorbiert die gesamte Aufgabe |
| Pro N Seiten oder pro Zeitfenster | Zähler oder Timer | Breites Crawling, Listenseiten | Rotation mitten im Ablauf kann den Zustand zerstören |
| Pro Browsersitzung | Bei jedem Treiberstart | Einfaches Scraping und Screenshot-Jobs | Startkosten dominieren die Laufzeit |
| Ein Proxy pro Worker | Beim Worker-Start | Parallele Crawls | Last pro IP muss verfolgt werden |
Da eine Browsersitzung teuer ist, ist Rotation pro Anfrage normalerweise die falsche Standardeinstellung für Selenium – du bezahlst einen Prozessneustart für jede URL. Standortkonsistenz ist für geoabhängige Ergebnisse ebenfalls wichtiger als reines IP-Volumen. Wenn du lokalisierte Seiten, Storefronts oder Preise für einen bestimmten Markt sampelst, halte die Exit-Region für die gesamte Sitzung fest, sonst sind die gesammelten Zahlen beim nächsten Durchlauf nicht reproduzierbar.
Exit-IP, DNS und WebRTC überprüfen
Die Verifizierung ist bei Headless-Läufen nicht optional, weil es kein Fenster gibt, in das man einen Blick werfen kann. Prüfe drei Dinge.
1. Die Exit-IP hat sich tatsächlich geändert. Verwende den obigen exit_ip-Helfer wieder und stelle sicher, dass der Wert von deiner eigenen Adresse abweicht. Vergleiche gelegentlich mit einem zweiten Echo-Endpunkt, damit du nicht durch eine gecachte Antwort getäuscht wirst.
2. DNS-Auflösungsverhalten. Bei einem HTTP-Proxy wird der Hostname normalerweise vom Proxy aufgelöst, was du willst. Das SOCKS5-Verhalten sollte man bestätigen, statt es anzunehmen: Öffne eine öffentliche DNS-Leak-Testseite in einem Nicht-Headless-Lauf und vergleiche den angezeigten Resolver mit dem deines ISPs.
3. WebRTC. Chrome kann einen nicht über den Proxy geleiteten UDP-Pfad verwenden, selbst wenn der Proxy konfiguriert ist. Das folgende Flag zwingt WebRTC, nicht über den Proxy geleitetes UDP zu vermeiden.
options.add_argument('--force-webrtc-ip-handling-policy=disable_non_proxied_udp')
Protokolliere schließlich für jede Sitzung den verwendeten Proxy und die beobachtete Exit-IP. Wenn eine Seite eine Challenge, die falsche Sprache oder einen unerwarteten Preis zurückgibt, ist diese Logzeile das Erste, was du sehen willst.
Fallstricke, die die meiste Zeit verschwenden
- Treiber-Leak. Jeder verwaiste chromedriver belegt Speicher. In einem Container sieht das normalerweise eher wie ein mysteriöser Absturz aus als wie ein Leck. Verwende überall
try/finally. - Geteilte Profilverzeichnisse. Parallele Instanzen, die ein Benutzerdatenverzeichnis teilen, blockieren sich gegenseitig. Ein Verzeichnis pro Worker.
- Fehler als Blocks fehlinterpretieren. Ein 403, eine leere Antwort und eine Challenge-Seite sind drei verschiedene Probleme. Protokolliere Statuscode, Seitentitel und Body-Länge, bevor du den Proxy verantwortlich machst.
- Von einer Seite auf alles schließen. Verifiziere die Exit-IP, bevor du hundert Seiten automatisierst, nicht danach.
- Die nicht-technischen Regeln vergessen. Das Rotieren einer IP ändert nicht, was die Nutzungsbedingungen einer Website, ihre robots.txt oder geltendes Datenschutzrecht erlauben – insbesondere wenn du personenbezogene Daten in einer DSGVO-Jurisdiktion sammelst. Mach langsamer, gehe bei Fehlern in den Backoff und halte die Nebenläufigkeit moderat.
Die Kurzfassung
- Selenium bindet einen Proxy an einen Browserstart, daher bedeutet Rotation Neustarts, es sei denn, du fügst eine Interception-Schicht hinzu.
- Das
--proxy-server-Flag von Chrome kann keine Zugangsdaten transportieren. Wähle IP-Allowlisting, eine Auth-Erweiterung oder selenium-wire. - Bevorzuge für Browser-Automatisierung Rotation pro Sitzung oder Sticky-Rotation gegenüber Rotation pro Anfrage.
- Verifiziere die Exit-IP innerhalb der Sitzung, deaktiviere nicht über den Proxy geleitetes WebRTC-UDP und protokolliere, welcher Proxy welche Anfrage bedient hat.
- Beende den Treiber in
finally, isoliere Profilverzeichnisse pro Worker und beweise das Setup mit einer URL, bevor du es skalierst.