Selenium Proxy Auth & Stealth: Residential Proxies richtig integrieren

Praxisguide für QA- und Scraping-Engineers: Authentifizierte Proxies in Chrome/Firefox, Fingerprint-Reduktion, rotierende IP-Pools und paralleles Scraping mit Selenium Grid – inkl. Playwright-Vergleich.

Selenium Proxy Auth & Stealth: Residential Proxies richtig integrieren

Warum Selenium proxy auth so frustrierend ist

Wer schon einmal versucht hat, einen authentifizierten Proxy in Standard-Selenium zu konfigurieren, kennt das Problem: ChromeOptions.add_argument('--proxy-server=http://user:pass@host:port') funktioniert nicht. Chrome ignoriert die Credentials im URL-Format und zeigt stattdessen einen Pop-up-Dialog, den Selenium nicht abfangen kann. Das Gleiche gilt für Firefox – die Proxy-Auth muss über einen anderen Weg injiziert werden.

Dieses Problem trifft Selenium residential proxies besonders hart, da Residential-Provider – inklusive ProxyHat – fast immer Benutzername und Passwort verlangen. Ohne funktionierende Selenium proxy auth ist der gesamte IP-Rotation-Ansatz nutzlos.

In diesem Guide zeige ich dir framework-idiomatische Lösungen: selenium-wire für Chrome, Profile-Generierung für Firefox, Stealth-Erweiterungen, ein rotierender Proxy-Pool und Container-basiertes Parallel-Scraping. Am Ende gibt es einen ehrlichen Vergleich mit Playwright.

Chrome + selenium-wire: Authentifizierte Proxies richtig konfigurieren

selenium-wire erweitert den WebDriver um einen lokalen MitM-Proxy, der die Authentifizierung übernimmt. Der Browser verbindet sich unauthentifiziert zum lokalen Proxy, und selenium-wire leitet den Traffic mit Credentials an den Upstream-Proxy weiter.

Installation und Grundkonfiguration

pip install selenium-wire selenium
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options

opts = Options()
opts.add_argument('--headless=new')
opts.add_argument('--disable-blink-features=AutomationControlled')

proxy_config = {
    'proxy': {
        'http': 'http://user-country-US:PASSWORD@gate.proxyhat.com:8080',
        'https': 'http://user-country-US:PASSWORD@gate.proxyhat.com:8080',
    },
    'verify_ssl': False,
}

driver = webdriver.Chrome(options=opts, seleniumwire_options=proxy_config)

# Prüfe die ausgehende IP
driver.get('https://httpbin.org/ip')
print(driver.find_element('tag name', 'body').text)

driver.quit()

Der entscheidende Vorteil: Kein Chrome-Extension-Hack, kein Pop-up-Dialog. selenium-wire fängt den Traffic auf Port-Ebene ab und fügt den Proxy-Authorization-Header automatisch ein. Das funktioniert zuverlässig mit Selenium residential proxies, Mobile- und Datacenter-Proxies gleichermaßen.

Geo-Targeting und Sticky Sessions

Mit ProxyHat steuerst du Geo-Targeting und Session-Verhalten über den Benutzernamen:

# Deutschland, Berlin, Sticky Session für 10 Minuten
proxy_config = {
    'proxy': {
        'http': 'http://user-country-DE-city-berlin-session-abc123:PASSWORD@gate.proxyhat.com:8080',
        'https': 'http://user-country-DE-city-berlin-session-abc123:PASSWORD@gate.proxyhat.com:8080',
    },
}

# Neue IP pro Request: einfach einen neuen Session-String generieren
import uuid
session_id = uuid.uuid4().hex[:12]
proxy_url = f'http://user-country-DE-session-{session_id}:PASSWORD@gate.proxyhat.com:8080'

Einschränkungen von selenium-wire

  • Der lokale MitM-Proxy addiert ~50-100ms Latenz pro Request.
  • SSL-Verifikation muss ggf. deaktiviert werden (verify_ssl: False), was in Produktivumgebungen Sicherheitsbedenken aufwirft.
  • Bei sehr hohem Concurrency-Level (100+ parallele Sessions) kann der lokale Proxy zum Flaschenhals werden.

Firefox: Proxy-Profile programmatisch generieren

Firefox bietet einen saubereren Weg: Proxy-Einstellungen werden direkt in prefs.js eines Firefox-Profils geschrieben. Die Authentifizierung erledigen wir über eine signierte Extension, die den Proxy-Authorization-Header setzt.

Profil-Generierung und Auth-Extension

import tempfile
import os
import zipfile
from selenium import webdriver
from selenium.webdriver.firefox.options import Options as FirefoxOptions

def create_proxy_auth_extension(proxy_host, proxy_port, proxy_user, proxy_pass):
    """Erstellt eine signierte XPI-Extension für Firefox Proxy-Auth."""
    manifest_json = '{"manifest_version": 2, "name": "proxy-auth", "version": "1.0", "background": {"scripts": ["background.js"]}, "permissions": ["webRequest", "webRequestBlocking", "<all_urls>"]}'

    background_js = '''
    var authHeader = "Basic " + btoa("%s:%s");
    browser.webRequest.onBeforeSendHeaders.addListener(
        function(details) {
            details.requestHeaders.push({name: "Proxy-Authorization", value: authHeader});
            return {requestHeaders: details.requestHeaders};
        },
        {urls: ["<all_urls>"]},
        ["blocking", "requestHeaders"]
    );
    ''' % (proxy_user, proxy_pass)

    pluginfile = tempfile.mktemp(suffix='.xpi')
    with zipfile.ZipFile(pluginfile, 'w') as zp:
        zp.writestr('manifest.json', manifest_json)
        zp.writestr('background.js', background_js)
    return pluginfile

def create_firefox_proxy_profile(proxy_host, proxy_port, proxy_user, proxy_pass):
    """Erstellt ein Firefox-Profil mit Proxy-Einstellungen."""
    profile_dir = tempfile.mkdtemp()
    prefs_path = os.path.join(profile_dir, 'user.js')

    prefs = f'''
    user_pref("network.proxy.type", 1);
    user_pref("network.proxy.http", "{proxy_host}");
    user_pref("network.proxy.http_port", {proxy_port});
    user_pref("network.proxy.ssl", "{proxy_host}");
    user_pref("network.proxy.ssl_port", {proxy_port});
    user_pref("network.proxy.no_proxies_on", "localhost, 127.0.0.1");
    '''

    with open(prefs_path, 'w') as f:
        f.write(prefs)

    return profile_dir

# Nutzung
PROXY_HOST = 'gate.proxyhat.com'
PROXY_PORT = 8080
PROXY_USER = 'user-country-DE-city-berlin'
PROXY_PASS = 'PASSWORD'

profile_dir = create_firefox_proxy_profile(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)
extension_path = create_proxy_auth_extension(PROXY_HOST, PROXY_PORT, PROXY_USER, PROXY_PASS)

opts = FirefoxOptions()
opts.add_argument('--headless')
opts.add_argument('-profile')
opts.add_argument(profile_dir)

driver = webdriver.Firefox(options=opts)
driver.install_addon(extension_path, temporary=True)

driver.get('https://httpbin.org/ip')
print(driver.find_element('tag name', 'body').text)
driver.quit()

Dieser Ansatz ist framework-idiomatisch: Die Proxy-Konfiguration lebt im Profil, die Auth in einer Extension. Beides wird programmatisch erzeugt und kann pro Session variiert werden – ideal für rotierende Proxy-Pools.

Selenium stealth: Automation-Fingerprints reduzieren

Selbst mit dem richtigen Proxy erkennen viele Seiten, dass du Selenium nutzt. Die Indizien sind vielfältig: navigator.webdriver ist true, Chrome spezifische Automation-Flags sind gesetzt, window.chrome fehlt, und die Standard-Auflösung von Headless-Browsern ist 800x600.

selenium-stealth: Der Quick-Fix

undetected-chromedriver und selenium-stealth patchen die offensichtlichsten Fingerprints:

from selenium import webdriver
from selenium_stealth import stealth

opts = webdriver.ChromeOptions()
opts.add_argument('--headless=new')
opts.add_experimental_option('excludeSwitches', ['enable-automation'])
opts.add_experimental_option('useAutomationExtension', False)

driver = webdriver.Chrome(options=opts)

stealth(driver,
    languages=['de-DE', 'de', 'en-US', 'en'],
    vendor='Google Inc.',
    platform='Win32',
    webgl_vendor='Intel Inc.',
    renderer='Intel Iris OpenGL Engine',
    fix_hairline=True,
)

# navigator.webdriver ist jetzt false
print(driver.execute_script('return navigator.webdriver'))  # None
driver.get('https://bot.sannysoft.com/')
driver.save_screenshot('stealth_check.png')
driver.quit()

selenium-driverless: Der radikalere Ansatz

selenium-driverless kommuniziert direkt über die Chrome DevTools Protocol (CDP), ohne einen WebDriver-Prozess. Das eliminiert die größte Erkennungsquelle komplett:

from selenium_driverless import webdriver as driverless_webdriver
from selenium_driverless.types import Options

opts = Options()
opts.add_argument('--headless=new')

# Proxy über CDP-Flags setzen (kein selenium-wire nötig!)
opts.add_argument(f'--proxy-server=http://gate.proxyhat.com:8080')

async def main():
    driver = await driverless_webdriver.Chrome(options=opts)

    # Proxy-Auth über CDP Fetch domain
    await driver.execute_cdp('Fetch.enable',
        handleAuthRequests=True,
        patterns=[{'urlPattern': '*'}]
    )
    await driver.execute_cdp('Fetch.continueWithAuth',
        requestId='<request_id>',
        authChallengeResponse={
            'response': 'ProvideCredentials',
            'username': 'user-country-US',
            'password': 'PASSWORD'
        }
    )

    await driver.get('https://httpbin.org/ip')
    print(await driver.page_source)
    await driver.quit()

import asyncio
asyncio.run(main())

Praxistipp: Kombiniere Selenium residential proxies mit Stealth-Maßnahmen. Ein Residential-IP allein reicht nicht – moderne Anti-Bot-Systeme wie Cloudflare und Datadome prüfen sowohl die IP-Reputation als auch Browser-Fingerprints.

Rotierender Proxy-Pool: Jede Session bekommt eine frische IP

Der produktive Einsatz erfordert ein Muster, bei dem jede neue WebDriver-Instanz automatisch einen frischen Proxy zugewiesen bekommt. Das ist der Kern von Selenium proxy auth bei Scale.

Pool-Manager als Middleware

Anstatt Proxy-URLs hart zu coden, implementieren wir einen Proxy-Pool-Manager, der als zentrale Middleware fungiert:

import itertools
import uuid
from dataclasses import dataclass
from typing import Generator

@dataclass
class ProxyConfig:
    host: str
    port: int
    username_template: str
    password: str

    def get_url(self, country: str = 'US', city: str = None, session: str = None) -> str:
        """Generiert eine Proxy-URL mit Geo-Targeting und Session-Kontrolle."""
        user = self.username_template.format(
            country=country,
            city=city or '',
            session=session or uuid.uuid4().hex[:12]
        )
        return f'http://{user}:{self.password}@{self.host}:{self.port}'


class ProxyPool:
    """Rotierender Proxy-Pool für Selenium WebDriver-Sessions."""

    def __init__(self, config: ProxyConfig, countries: list[str] = None):
        self.config = config
        self.countries = countries or ['US', 'DE', 'GB', 'FR']
        self._cycle = itertools.cycle(self.countries)

    def next_proxy(self, country: str = None, city: str = None, sticky: bool = False) -> dict:
        """Liefert die nächste Proxy-Konfiguration für selenium-wire."""
        target_country = country or next(self._cycle)
        session_id = uuid.uuid4().hex[:12] if not sticky else 'sticky'
        proxy_url = self.config.get_url(
            country=target_country,
            city=city,
            session=session_id
        )
        return {
            'proxy': {
                'http': proxy_url,
                'https': proxy_url,
            },
            'country': target_country,
            'session': session_id,
        }


# Nutzung mit ProxyHat
pool = ProxyPool(
    ProxyConfig(
        host='gate.proxyhat.com',
        port=8080,
        username_template='user-country-{country}-city-{city}-session-{session}',
        password='PASSWORD'
    ),
    countries=['US', 'DE', 'GB', 'JP']
)

# Jeder Aufruf liefert eine andere IP
for _ in range(5):
    cfg = pool.next_proxy()
    print(f"Country: {cfg['country']}, Session: {cfg['session']}")
    # -> Übergib cfg['proxy'] an seleniumwire_options

Dieses Pattern trennt die Proxy-Logik von der WebDriver-Logik. Der Pool kann erweitert werden um Retry-Logik, IP-Blacklisting und Rate-Limiting – alles als saubere Middleware, nicht als Hack im Test-Code.

WebDriver-Factory mit integriertem Proxy-Pool

Die Factory kapselt die Browser-Erstellung und injiziert automatisch den nächsten Proxy:

from seleniumwire import webdriver as sw_webdriver
from selenium.webdriver.chrome.options import Options

class WebDriverFactory:
    """Erstellt WebDriver-Instanzen mit rotierenden Proxies und Stealth."""

    def __init__(self, proxy_pool: ProxyPool, headless: bool = True):
        self.pool = proxy_pool
        self.headless = headless

    def create_chrome(self, country: str = None, city: str = None) -> sw_webdriver.Chrome:
        proxy_cfg = self.pool.next_proxy(country=country, city=city)

        opts = Options()
        if self.headless:
            opts.add_argument('--headless=new')
        opts.add_argument('--disable-blink-features=AutomationControlled')
        opts.add_argument('--window-size=1920,1080')
        opts.add_experimental_option('excludeSwitches', ['enable-automation'])
        opts.add_experimental_option('useAutomationExtension', False)

        driver = sw_webdriver.Chrome(
            options=opts,
            seleniumwire_options=proxy_cfg['proxy']
        )

        # Stealth-Patches anwenden
        driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
            'source': '''
                Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
                Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
                window.chrome = {runtime: {}};
            '''
        })

        return driver, proxy_cfg


# Nutzung
factory = WebDriverFactory(pool)
driver, proxy_info = factory.create_chrome(country='DE', city='berlin')
driver.get('https://httpbin.org/ip')
print(f"IP via {proxy_info['country']}: {driver.find_element('tag name', 'body').text}")
driver.quit()

Selenium Grid und Containerisierung: Paralleles Scraping at Scale

Einzelne WebDriver-Sessions skalieren nicht. Für ernsthaftes Scraping brauchst du parallele Ausführung. Selenium Grid bietet das Framework, Docker die Isolation.

Docker Compose für Selenium Grid v4

# docker-compose.yml
version: '3.8'
services:
  selenium-hub:
    image: selenium/hub:4.18.0
    ports:
      - '4442-4444:4442-4444'
    environment:
      - SE_SESSION_REQUEST_TIMEOUT=300
      - SE_NODE_SESSION_TIMEOUT=300

  chrome-node:
    image: selenium/node-chrome:4.18.0
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_MAX_SESSIONS=4
      - SE_NODE_OVERRIDE_MAX_SESSIONS=true
    deploy:
      replicas: 4
    shm_size: '2gb'

Parallel-Scraper mit Grid und Proxy-Pool

from concurrent.futures import ThreadPoolExecutor, as_completed
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

GRID_URL = 'http://localhost:4444/wd/hub'


def scrape_with_proxy(proxy_url: str, target_url: str) -> dict:
    """Startet eine Remote-Session über Selenium Grid mit Proxy."""
    opts = Options()
    opts.add_argument('--headless=new')
    opts.add_argument(f'--proxy-server={proxy_url.split("@")[1]}')  # Nur host:port
    # Auth muss über selenium-wire oder CDP gelöst werden

    driver = webdriver.Remote(
        command_executor=GRID_URL,
        options=opts
    )
    try:
        driver.get(target_url)
        return {'status': 'ok', 'title': driver.title, 'url': target_url}
    except Exception as e:
        return {'status': 'error', 'url': target_url, 'error': str(e)}
    finally:
        driver.quit()


# 20 URLs parallel über 4 Chrome-Nodes (je 4 Sessions = 16 parallel)
urls = [f'https://example.com/page/{i}' for i in range(20)]
pool = ProxyPool(
    ProxyConfig(
        host='gate.proxyhat.com',
        port=8080,
        username_template='user-country-US-session-{session}',
        password='PASSWORD'
    )
)

with ThreadPoolExecutor(max_workers=16) as executor:
    futures = {}
    for url in urls:
        cfg = pool.next_proxy()
        # Beachte: Für Grid brauchst du selenium-wire auf Node-Ebene
        # oder CDP-basierte Auth – siehe Abschnitt selenium-driverless
        futures[executor.submit(scrape_with_proxy, cfg['proxy']['http'], url)] = url

    for future in as_completed(futures):
        result = future.result()
        print(result)

Hinweis zu Grid + Auth: Selenium Grid unterstützt standardmäßig keine Proxy-Authentifizierung. Du musst entweder selenium-wire auf den Node-Images installieren (Custom-Dockerfile), die CDP-basierte Auth via selenium-driverless nutzen, oder eine lokale Proxy-Forwarder-Lösung wie tinyproxy + basic-auth auf dem Node einsetzen.

Skalierungs-Strategien

  • Horizontal skalieren: Mehrere Chrome-Nodes via replicas in Docker Compose oder Kubernetes HPA.
  • Session-Pooling: WebDriver-Sessions wiederverwenden statt für jeden Request neu zu erstellen – reduziert Overhead von ~3-5s pro Session-Start.
  • Headless Chrome Flags: --disable-gpu, --disable-dev-shm-usage, --no-sandbox für Container-Optimierung.
  • shm_size: Immer auf mindestens 2gb setzen – Chrome crasht sonst bei mehreren Tabs.

Playwright vs. Selenium: Wann wechseln?

Playwright hat sich als ernsthafte Alternative etabliert. Die Entscheidung hängt von deinem spezifischen Kontext ab:

KriteriumSeleniumPlaywright
Proxy-Auth (User:Pass)Nativ nicht unterstützt – selenium-wire oder Extension nötigNative Unterstützung via proxy: {username, password}
Stealth / Anti-DetectionManuell: selenium-stealth, undetected-chromedriver, CDP-HacksBesserer Standard-Stealth, aber auch nicht unsichtbar
API-DesignVeraltet: explizite Waits, String-basierte LocatorsModern: Auto-Waiting, typed Locators, context-basiert
Browser-SupportChrome, Firefox, Edge, Safari (via WebDriver)Chromium, Firefox, WebKit (eigene Builds)
Ecosystem / CommunityMassiv: 20+ Jahre, unzählige Tutorials, CI-IntegrationenWachsend, aber kleiner – weniger Legacy-Ressourcen
Parallel ExecutionSelenium Grid nötig (komplexe Infrastruktur)Eingebaut: browser.contexts() – keine externe Infrastruktur
Legacy-KompatibilitätExistierende Test-Suites, Selenese, BDD-FrameworksNeue Projekte bevorzugt – Migration aufwändig
Mobile TestingAppium-Integration verfügbarDevice-Emulation eingebaut, keine echte App-Unterstützung

Wann Playwright die bessere Wahl ist

  • Neue Projekte ohne Legacy-Code: Die modernere API spart erheblich Entwicklungszeit.
  • Proxy-Auth ist zentral: Playwrights native Proxy-Auth-Unterstützung eliminiert den selenium-wire-Umweg.
  • Parallele Ausführung ohne Grid: Playwright startet Browser-Contexte in Millisekunden – kein Grid-Setup nötig.

Wann du bei Selenium bleiben solltest

  • Bestehende Test-Suites: Eine Migration von 500+ Tests lohnt sich selten nur für bessere Proxy-Auth.
  • CI/CD-Integration: Viele CI-Systeme haben native Selenium-Plugins.
  • Breite Browser-Abdeckung: Wenn du echten Safari via WebDriver testen musst, bleibt Selenium die einzige Option.

Praxistipp: Du musst dich nicht entscheiden. Viele Teams nutzen Playwright für neue Scraping-Projekte und behalten Selenium für bestehende QA-Suites. Die Proxy-Infrastruktur (ProxyHat) funktioniert mit beiden Frameworks identisch.

Key Takeaways

  • Selenium proxy auth erfordert immer einen Workaround: selenium-wire für Chrome, Firefox-Profile + Auth-Extension für Firefox, oder CDP-basierte Auth via selenium-driverless.
  • Selenium stealth ist ein Mehrschichten-Problem: IP-Reputation (Residential-Proxies), Browser-Fingerprints (selenium-stealth / undetected-chromedriver), und Verhaltens-Signale (Mouse-Movements, Timing) müssen alle adressiert werden.
  • Ein rotierender Proxy-Pool als Middleware-Pattern trennt Proxy-Logik sauber von WebDriver-Code und ermöglicht Geo-Targeting, Sticky Sessions und Retry-Logik.
  • Selenium Grid + Docker ermöglicht paralleles Scraping, erfordert aber Custom-Images für Proxy-Auth.
  • Playwright ist für neue Projekte oft die bessere Wahl – aber Selenium bleibt relevant für Legacy-Suites und breite Browser-Kompatibilität.

FAQ

Bereit loszulegen?

Zugang zu über 50 Mio. Residential-IPs in über 148 Ländern mit KI-gesteuerter Filterung.

Preise ansehenResidential Proxies
← Zurück zum Blog