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()niedriver.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 timeout —
driver.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.






