Selenium proxy auth, rotacja IP i stealth — kompletny przewodnik dla inżynierów

Praktyczny przewodnik po autoryzacji proxy w Selenium: selenium-wire dla Chrome, profile Firefox, wzorzec rotującej puli IP, selenium-stealth, konteneryzacja i porównanie z Playwright.

Selenium proxy auth, rotacja IP i stealth — kompletny przewodnik dla inżynierów

Dlaczego Selenium proxy auth to problem numer jeden

Standardowe Selenium nie obsługuje proxy z loginem i hasłem. Metoda ChromeOptions.add_argument('--proxy-server=...') przyjmuje tylko adres host:port — bez poświadczeń. Gdy serwer proxy zwróci 407 Proxy Authentication Required, przeglądarka wyświetla natywny dialog, którego Selenium nie potrafi zamknąć. To fundamentalne ograniczenie Chromium DevTools Protocol.

Residential proxy — takie jak te z ProxyHat — praktycznie zawsze wymagają autoryzacji. Bez obejścia tego problemu nie zbudujesz stabilnego pipeline'u scrapingowego. Poniżej pokazuję pięć sprawdzonych wzorców, od najprostszego po produkcyjny.

Chrome + selenium-wire: autoryzacja proxy bez hacków

selenium-wire to wrapper nad WebDriver, który przechwytuje ruch przez lokalny mitmproxy i transparentnie dodaje nagłówek Proxy-Authorization. Dzięki temu standardowe proxy z poświadczeniami działa out-of-the-box.

Instalacja i podstawowa konfiguracja

pip install selenium-wire selenium

from seleniumwire import webdriver  # uwaga: import z seleniumwire, nie selenium
from selenium.webdriver.chrome.options import Options

PROXY_URL = "http://user-country-US:PASSWORD@gate.proxyhat.com:8080"

options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")

sw_options = {
    "proxy": {
        "http": PROXY_URL,
        "https": PROXY_URL,
        "no_proxy": "localhost,127.0.0.1",
    },
}

driver = webdriver.Chrome(options=options, seleniumwire_options=sw_options)
driver.get("https://httpbin.org/ip")
print(driver.page_source)  # zobaczysz IP proxy, nie swoje
driver.quit()

Kluczowa zaleta: selenium-wire dodaje Proxy-Authorization automatycznie — nie musisz wstrzykiwać JS ani obsługiwać dialogów. Proxy widzi poprawny nagłówek i przepuszcza ruch.

Ograniczenia selenium-wire

  • Dodaje ~50 ms latency na każde żądanie (przechodzenie przez lokalny mitmproxy).
  • Nie jest aktywnie rozwijany od 2023 — może lagować za najnowszymi wersjami Chrome.
  • Potencjalne problemy z certyfikatami przy stronach z pinningiem.

Jeśli selenium-wire nie działa z Twoją wersją Chrome, alternatywą jest wtyczka Chrome Extension, która przechwytuje onAuthRequired — ale to wymaga pakowania .crx i jest bardziej fragile.

Firefox: autoryzacja proxy przez profil

Firefox ma przewagę: konfiguracja proxy z poświadczeniami jest wbudowana w about:config. Nie potrzebujesz zewnętrznego wrappera.

from selenium import webdriver
from selenium.webdriver.firefox.options import Options as FirefoxOptions
from selenium.webdriver.firefox.service import Service
import tempfile, os

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

def create_firefox_profile_with_proxy():
    profile_dir = tempfile.mkdtemp()
    # Konfiguracja proxy w prefs.js
    prefs = f"""// Mozilla prefs
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");
user_pref("signon.autologin.proxy", true);
"""
    with open(os.path.join(profile_dir, "user.js"), "w") as f:
        f.write(prefs)

    # Zapisz poświadczenia w logins.json
    import json, time
    logins = [{
        "hostname": f"moz-proxy://{PROXY_HOST}:{PROXY_PORT}",
        "username": PROXY_USER,
        "password": PROXY_PASS,
        "timesUsed": 1,
        "timeCreated": int(time.time() * 1000),
        "timeLastUsed": int(time.time() * 1000),
        "timePasswordChanged": int(time.time() * 1000),
    }]
    with open(os.path.join(profile_dir, "logins.json"), "w") as f:
        json.dump({"logins": logins, "disabledHosts": [], "nextId": 1}, f)

    return profile_dir

profile_dir = create_firefox_profile_with_proxy()
options = FirefoxOptions()
options.add_argument("-headless")
options.add_argument("-profile")
options.add_argument(profile_dir)

service = Service()
driver = webdriver.Firefox(service=service, options=options)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()

Ten wzorzec jest stabilniejszy niż selenium-wire, bo nie dodaje warstwy mitmproxy. Minus: musisz zarządzać cyklem życia katalogu profilu (cleanup po zakończeniu sesji).

Selenium stealth: ukrywanie śladów automatyzacji

Nawet z residential proxy, Twoja sesja może zostać oflagowana przez detektory botów (navigator.webdriver, brak Notification.permission, podejrzane WebGL fingerprinty). Trzy główne narzędzia łagodzą ten problem.

selenium-stealth

Biblioteka selenium-stealth nakłada patche na WebDriver: usuwa flagę navigator.webdriver, ustawia realistyczne user-agenty, naprawia Chrome DevTools markers.

pip install selenium-stealth

from selenium import webdriver
from selenium_stealth import stealth
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")

driver = webdriver.Chrome(options=options)

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

driver.get("https://bot.sannysoft.com/")
print("Stealth check passed" if not driver.title.__contains__("Detect") else "Detected")
driver.quit()

selenium-driverless

selenium-driverless idzie krok dalej: komunikuje się z Chrome przez CDP bez użycia Chromedrivera. Eliminuje to najważniejszy marker detekcji — sam plik executables chromedriver. Wymaga jednak ręcznej obsługi CDP i jest mniej stabilny w produkcji.

Kombinacja: selenium-wire + stealth

Możesz łączyć oba podejścia. Importujesz webdriver z seleniumwire, a potem nakładasz stealth() na driver. Zwróć jednak uwagę na kolejność: najpierw utwórz driver, potem wywołaj stealth — inaczej patche nie zadziałają.

Wzorzec rotującej puli proxy: nowa sesja = nowy IP

Sticky sessions są świetne do logowania, ale do scraping na dużą skalę potrzebujesz per-session rotation: każda instancja WebDrivera dostaje unikalny IP z puli residential proxy. Oto produkcyjny wzorzec z ProxyHat.

import uuid
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options
from selenium_stealth import stealth
from concurrent.futures import ThreadPoolExecutor, as_completed

PROXY_GATE = "gate.proxyhat.com:8080"
PROXY_USER = "user-country-US"  # geo-targeting w username
PROXY_PASS = "YOUR_PASSWORD"

def create_stealth_driver(proxy_session_id: str):
    """Tworzy WebDriver z unikalną sesją proxy i patchami stealth."""
    proxy_url = (
        f"http://{PROXY_USER}-session-{proxy_session_id}"
        f":{PROXY_PASS}@{PROXY_GATE}"
    )
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--disable-blink-features=AutomationControlled")
    options.add_argument("--window-size=1920,1080")

    sw_options = {
        "proxy": {"http": proxy_url, "https": proxy_url},
    }

    driver = webdriver.Chrome(options=options, seleniumwire_options=sw_options)
    stealth(driver,
        languages=["en-US", "en"],
        vendor="Google Inc.",
        platform="Win32",
        webgl_vendor="Intel Inc.",
        renderer="Intel Iris OpenGL Engine",
        fix_hairline=True,
    )
    return driver

def scrape_url(url: str, session_id: str):
    """Pojedyncze zadanie scrapingu z dedykowanym IP."""
    driver = create_stealth_driver(session_id)
    try:
        driver.get(url)
        return {"url": url, "title": driver.title, "session": session_id}
    finally:
        driver.quit()  # ZAWSZE quit — nie close!

# --- Równoległe scrapowanie z rotacją IP ---
targets = [
    "https://httpbin.org/ip",
    "https://httpbin.org/headers",
    "https://httpbin.org/user-agent",
    "https://httpbin.org/cookies",
]

with ThreadPoolExecutor(max_workers=4) as pool:
    futures = {
        pool.submit(scrape_url, url, str(uuid.uuid4())[:8]): url
        for url in targets
    }
    for future in as_completed(futures):
        result = future.result()
        print(f"{result['url']} → session={result['session']}")

Kluczowe detale:

  • Flaga session-{id} w username mówi ProxyHat, że każda sesja ma otrzymać inny IP z residential pool. Bez tej flagi dostaniesz ten sam IP na ponowne połączenie.
  • driver.quit() nie driver.close() — quit zabija proces chromedriver i zamyka sesję proxy. Close zostawia zombie process.
  • ThreadPoolExecutor, nie asyncio — WebDriver jest blokujący. Threading jest naturalnym wyborem.

Selenium Grid + Docker: skalowanie do setek sesji

Pojedyncza maszyna szybko stanie się wąskim gardłem. Selenium Grid pozwala dystrybuować sesje WebDrivera na wiele węzłów w kontenerach Docker.

docker-compose.yml — Grid z 4 węzłami Chrome

version: '3.8'
services:
  selenium-hub:
    image: selenium/hub:4.18
    ports:
      - "4442:4442"
      - "4443:4443"
      - "4444:4444"
    environment:
      - SE_SESSION_REQUEST_TIMEOUT=300
      - SE_NODE_SESSION_TIMEOUT=300

  chrome-node-1:
    image: selenium/node-chrome:4.18
    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:
      resources:
        limits:
          memory: 2G

  chrome-node-2:
    image: selenium/node-chrome:4.18
    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:
      resources:
          limits:
            memory: 2G

  # Duplikuj chrome-node-3, chrome-node-4 według potrzeb

Połączenie z Grid + proxy

W środowisku Grid, selenium-wire nie zadziała bezpośrednio (węzły nie mają dostępu do mitmproxy na hoście). Zamiast tego użyj SOCKS5 proxy z flagą sesji w username — Chrome natywnie obsługuje SOCKS5 z autoryzacją w URL (od Chrome 110+).

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 1080  # SOCKS5
SESSION_ID = str(uuid.uuid4())[:8]
PROXY_USER = f"user-country-US-session-{SESSION_ID}"
PROXY_PASS = "YOUR_PASSWORD"

options = Options()
options.set_capability("browserName", "chrome")
options.add_argument(f"--proxy-server=socks5://{PROXY_USER}:{PROXY_PASS}@{PROXY_HOST}:{PROXY_PORT}")
options.add_argument("--headless=new")

driver = webdriver.Remote(
    command_executor="http://localhost:4444/wd/hub",
    options=options,
)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()

Pro tip: W środowisku Kubernetes, użyj Horizontal Pod Autoscaler na węzłach Chrome i zewnętrznego proxy rotation service, który przydziela sesje z residential pool. ProxyHat wspiera geo-targeting na poziomie miasta — możesz kierować ruch z konkretnych rynków.

Porównanie: Selenium vs Playwright do proxy scrapingu

Playwright jest coraz częściej wybierany do nowych projektów scrapingowych. Poniżej szczere porównanie z perspektywy kogoś, kto utrzymuje legacy Selenium.

Kryterium Selenium Playwright
Proxy auth (user:pass) Wymaga selenium-wire lub wtyczki Natywna obsługa w API (proxy: {username, password})
Stealth Wymaga selenium-stealth + manualne patche Lepszy baseline, mniejsza powierzchnia detekcji
IP rotation Ręczny wzorzec (jak wyżej) browser.newContext({ proxy }) — IP per context, nie per browser
Równoległość Grid + Docker, ciężki setup Wbudowany browserType.launch() + contexts, lekki
Ekosystem / QA tooling Masowy: Appium, Katalon, Robot Framework Rosnący, ale wciąż mniejszy
Legacy compatibility Tysiące istniejących testów, WebDriver BiDi Trzeba przepisać od zera
Aktywny rozwój Stabilny, wolniejszy release cycle Szybkie iteracje, Microsoft-backed

Kiedy wybrać Playwright

  • Nowy projekt scrapingowy — nie masz długu technicznego, więc natywna obsługa proxy auth i lepszy stealth są decydujące.
  • Wysoka równoległość na jednej maszynie — Playwright contexts są ~10x lżejsze niż instancje Chrome z Selenium.
  • Chcesz uniknąć zależności od selenium-wire — ten projekt nie jest aktywnie rozwijany.

Kiedy zostać przy Selenium

  • Istniejący suite testów QA — migracja setek testów nie ma ROI.
  • Integracja z narzędziami — Appium, BrowserStack, Sauce Labs mają pierwszorzędne wsparcie Selenium.
  • Zespół z wiedzą Selenium — krzywa uczenia się Playwright jest niska, ale nie zerowa.
  • WebDriver BiDi — nowy standard W3C, który unifikuje CDP i WebDriver, jest bliżej realizacji w ekosystemie Selenium.

Najlepsze praktyki — checklist

  • Zawsze używaj residential proxy do scraping — datacenter IP są oflagowane przez większość anti-botów. Sprawdź dostępne lokalizacje ProxyHat.
  • Rotuj sesje — flaga session-{id} w username gwarantuje nowy IP. Używaj UUID, nie inkrementalnych ID.
  • Ustawiaj timeoutdriver.set_page_load_timeout(30). Bez tego zawieszone sesje nigdy się nie zwolnią.
  • Killuj driver.quit() w bloku finally — zombie procesy Chrome zżerają RAM w godzinę.
  • Monitoruj success rate — jeśli spada poniżej 85%, zmień geo-target lub zwiększ opóźnienie między żądaniami.
  • Stosuj exponential backoff przy CAPTCHA — nie od razu retry, bo potwierdzisz wzorzec bota.
  • Limituj concurrency per IP — residential IP nie znoszą >5 równoległych połączeń. Skaluj liczbą IP, nie liczbą wątków per IP.

Key Takeaways

1. Selenium natywnie nie obsługuje proxy auth — selenium-wire (Chrome) lub profil Firefox (logins.json) to dwa produkcyjne rozwiązania.

2. Selenium stealth wymaga co najmniej selenium-stealth + --disable-blink-features=AutomationControlled. Sam headless nie wystarczy.

3. Rotacja IP = flaga session-{id} w username ProxyHat + driver.quit() po każdym zadaniu. Nie próbuj rotować IP bez restartu sesji.

4. Skalowanie = Selenium Grid + Docker + SOCKS5 proxy. selenium-wire nie działa w architekturze Grid.

5. Playwright jest lepszy do nowych projektów (natywna proxy auth, lżejsze contexts). Selenium wygrywa tam, gdzie masz legacy suite i integracje QA.

FAQ

Gotowy, aby zacząć?

Dostęp do ponad 50 mln rezydencjalnych IP w ponad 148 krajach z filtrowaniem AI.

Zobacz cenyProxy rezydencjalne
← Powrót do Bloga