Если вы хотя бы раз пытались передать user:pass-прокси в стандартный Selenium, вы знаете боль: ChromeOptions.add_argument('--proxy-server=http://user:pass@host:port') просто не работает. Chrome игнорирует учётные данные, Firefox молча падает, а вы остаётесь с окном авторизации и нулевым результатом. В этом руководстве — фреймворк-идиоматичные решения для каждого браузера, паттерн ротации IP на уровне пула сессий и архитектура для параллельного скрейпинга в продакшене.
Почему Selenium proxy auth — это проблема
Стандартный WebDriver API (Proxy-объект в DesiredCapabilities) поддерживает только хост и порт. Поля для логина и пароля есть в спецификации, но ни Chrome, ни Firefox их не реализуют. Причина — безопасность: браузер не должен передавать credentials в командной строке, где их видно через ps aux.
Обходные пути существуют, и их три:
- selenium-wire — промежуточный прокси-сервер на Python, который перехватывает трафик и добавляет
Proxy-Authorizationзаголовок. - Firefox-профили — записываем credentials в
prefs.js, Firefox читает их нативно. - Расширение Chrome — генерируем .crx, который перехватывает
chrome.webRequest.onAuthRequired. Хак, но работает.
Ниже — каждый подход с рабочим кодом.
Chrome + selenium-wire: авторизованный прокси без хаков
selenium-wire — это обёртка над WebDriver, которая запускает локальный прокси-сервер (mitmproxy под капотом). Весь трафик браузера идёт: Browser → selenium-wire proxy → upstream authenticated proxy → target. Вы указываете upstream-прокси с user:pass, а selenium-wire сам добавляет Proxy-Authorization при коннекте.
Установка и базовая конфигурация
pip install selenium-wire selenium
from seleniumwire import webdriver
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")
seleniumwire_options = {
"proxy": {
"http": proxy_url,
"https": proxy_url,
"no_proxy": "localhost,127.0.0.1",
},
}
driver = webdriver.Chrome(
options=options,
seleniumwire_options=seleniumwire_options,
)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()
Ключевые моменты:
- Импорт из
seleniumwire, не изselenium— это частая ошибка. - selenium-wire запускает локальный прокси на случайном порту; Chrome подключается к нему без авторизации.
- Для sticky-сессий с residential-прокси ProxyHat добавьте флаг сессии в username:
user-country-US-session-abc123:PASSWORD.
Ограничения selenium-wire
Локальный прокси добавляет ~50–100 мс задержки. При высокой параллельности (50+ браузеров) это становится заметным. Также selenium-wire не поддерживает SOCKS5 нативно — для SOCKS5 используйте Firefox-подход ниже или поднимите локальный SOCKS→HTTP-конвертер.
Firefox: авторизация через профиль
Firefox — единственный браузер, который нативно читает прокси-credentials из профиля. Мы создаём временный профиль, записываем network.proxy.* preferences и подключаем его к сессии.
import tempfile
import os
from selenium import webdriver
from selenium.webdriver.firefox.service import Service
def create_firefox_proxy_profile(proxy_host, proxy_port, proxy_user, proxy_pass):
"""Создаёт временный Firefox-профиль с авторизованным прокси."""
profile_dir = tempfile.mkdtemp(prefix="ff_proxy_")
prefs_js = os.path.join(profile_dir, "prefs.js")
with open(prefs_js, "w") as f:
f.write(f'user_pref("network.proxy.type", 1);\n')
f.write(f'user_pref("network.proxy.http", "{proxy_host}");\n')
f.write(f'user_pref("network.proxy.http_port", {proxy_port});\n')
f.write(f'user_pref("network.proxy.ssl", "{proxy_host}");\n')
f.write(f'user_pref("network.proxy.ssl_port", {proxy_port});\n')
f.write(f'user_pref("network.proxy.no_proxies_on", "localhost, 127.0.0.1");\n')
# Авторизация через signons.sqlite — используем AutoAuth
# Альтернатива: расширение для авто-авторизации
auth_ext = _build_auth_extension(proxy_host, proxy_port, proxy_user, proxy_pass)
return profile_dir, auth_ext
def _build_auth_extension(host, port, user, password):
"""Генерирует XPI-расширение для авто-авторизации прокси в Firefox."""
ext_dir = tempfile.mkdtemp(prefix="ff_auth_")
manifest = {
"manifest_version": 2,
"name": "proxy-auth",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"permissions": ["webRequest", "webRequestBlocking", "<all_urls>"],
}
import json
with open(os.path.join(ext_dir, "manifest.json"), "w") as f:
json.dump(manifest, f)
bg_script = f"""
browser.webRequest.onAuthRequired.addListener(
(details) => {{
if (details.isProxy) {{
return {{ authCredentials: {{ username: "{user}", password: "{password}" }} }};
}}
}},
{{ urls: ["<all_urls>"] }},
["blocking"]
);
"""
with open(os.path.join(ext_dir, "background.js"), "w") as f:
f.write(bg_script)
return ext_dir
# Использование
profile_dir, auth_ext = create_firefox_proxy_profile(
proxy_host="gate.proxyhat.com",
proxy_port=8080,
proxy_user="user-country-DE-city-berlin",
proxy_pass="PASSWORD",
)
options = webdriver.FirefoxOptions()
options.add_argument("-headless")
options.add_argument(f"-profile")
options.add_argument(profile_dir)
options.add_extension(auth_ext) # Для AutoAuth
driver = webdriver.Firefox(options=options)
driver.get("https://httpbin.org/ip")
print(driver.page_source)
driver.quit()
Этот подход не добавляет промежуточный прокси — трафик идёт напрямую в upstream, что снижает задержку. Но генерация профиля и расширения — больше кода. Для простоты в Python-проектах selenium-wire предпочтительнее; для контейнеризированных сценариев, где важен каждый миллисекунд, Firefox-профиль выигрывает.
Selenium stealth: снижение отпечатков автоматизации
Даже с residential-прокси ваш скрейпер может быть обнаружен через browser fingerprinting: navigator.webdriver, отсутствие плагинов, специфичные navigator.languages, Canvas/WebGL-хэши. Два основных решения:
selenium-stealth
Патчит свойства браузера через JavaScript-инъекции при запуске. Работает поверх стандартного WebDriver.
from selenium_stealth import stealth
from selenium import webdriver
options = webdriver.ChromeOptions()
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("WebDriver flag:", driver.execute_script("return navigator.webdriver"))
# → False (если stealth сработал)
selenium-driverless
Более радикальный подход: использует CDP (Chrome DevTools Protocol) напрямую, минуя WebDriver. Это устраняет navigator.webdriver и другие CDP-артефакты на корню.
import asyncio
from selenium_driverless.webdriver import Chrome
async def main():
async with Chrome(headless=True) as driver:
await driver.get("https://nowsecure.nl/")
content = await driver.page_source
print(len(content))
asyncio.run(main())
selenium-driverless лучше обходит Cloudflare и DataDome, но API менее стабилен и документация скудная. Для продакшена рекомендуем связку selenium-wire + selenium-stealth — стабильнее и предсказуемее.
Паттерн Rotating Proxy Pool
При массовом скрейпинге один IP быстро попадает в rate-limit или бан-лист. Правильная архитектура: каждая новая WebDriver-сессия получает свежий IP, а пул управляет ротацией через ProxyHat-флаги.
Реализация пула сессий
import itertools
import uuid
from seleniumwire import webdriver
from selenium.webdriver.chrome.options import Options
class ProxyPool:
"""Ротационный пул прокси для Selenium-сессий."""
COUNTRIES = ["US", "DE", "GB", "FR", "JP"]
def __init__(self, username: str, password: str):
self.username = username
self.password = password
self._country_cycle = itertools.cycle(self.COUNTRIES)
def next_proxy_url(self, sticky: bool = False) -> str:
"""Возвращает URL прокси с ротацией страны и опциональной sticky-сессией."""
country = next(self._country_cycle)
user_part = f"{self.username}-country-{country}"
if sticky:
session_id = uuid.uuid4().hex[:12]
user_part += f"-session-{session_id}"
return f"http://{user_part}:{self.password}@gate.proxyhat.com:8080"
def create_driver(self, sticky: bool = False) -> webdriver.Chrome:
"""Создаёт новую Chrome-сессию с уникальным IP."""
proxy_url = self.next_proxy_url(sticky=sticky)
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-gpu")
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
)
return driver
# Использование
pool = ProxyPool(username="myuser", password="mypass")
# Per-request ротация — каждый драйвер = новый IP
drivers = [pool.create_driver() for _ in range(5)]
for i, d in enumerate(drivers):
d.get("https://httpbin.org/ip")
print(f"Driver {i}: {d.page_source[:80]}")
d.quit()
# Sticky-сессия — IP сохраняется в рамках сессии
sticky_driver = pool.create_driver(sticky=True)
sticky_driver.get("https://httpbin.org/ip") # Тот же IP
sticky_driver.get("https://httpbin.org/headers") # Тот же IP
sticky_driver.quit()
Архитектурные преимущества этого подхода:
- Изоляция сессий — каждый WebDriver работает со своим IP; бан одного не влияет на другие.
- Гео-ротация —
itertools.cycleравномерно распределяет запросы по странам, снижая нагрузку на один регион. - Sticky-сессии — для сайтов с многошаговыми формами (логин → профиль → данные) один IP держится всю сессию.
Правило: per-request ротация — для SERP и ценового мониторинга; sticky-сессии — для авторизованного скрейпинга и мультишаговых флоу.
Selenium Grid и контейнеризация: масштабирование в продакшене
Один скрипт на ноутбуке — это MVP. Для продакшена нужен Selenium Grid: хаб маршрутизирует запросы к нодам, каждая нода запускает несколько браузеров в изолированных контейнерах.
Docker Compose для Grid + ProxyPool
# docker-compose.yml
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:
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:
replicas: 3 # 3 ноды × 4 сессии = 12 параллельных браузеров
scraper:
build: .
depends_on:
- selenium-hub
environment:
- PROXYHAT_USER=myuser
- PROXYHAT_PASS=mypass
- SELENIUM_HUB_URL=http://selenium-hub:4444/wd/hub
volumes:
- ./output:/app/output
Подключение к Grid из Python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from seleniumwire import webdriver as sw_webdriver
import os
hub_url = os.environ["SELENIUM_HUB_URL"]
proxy_user = os.environ["PROXYHAT_USER"]
proxy_pass = os.environ["PROXYHAT_PASS"]
def create_grid_driver(country: str = "US"):
"""Создаёт Remote-сессию на Selenium Grid с авторизованным прокси."""
proxy_url = f"http://{proxy_user}-country-{country}:{proxy_pass}@gate.proxyhat.com:8080"
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
sw_options = {"proxy": {"http": proxy_url, "https": proxy_url}}
driver = sw_webdriver.Remote(
command_executor=hub_url,
options=options,
seleniumwire_options=sw_options,
)
return driver
# Параллельный скрейпинг через ThreadPoolExecutor
from concurrent.futures import ThreadPoolExecutor, as_completed
URLS = [
"https://example.com/page1",
"https://example.com/page2",
"https://example.com/page3",
]
COUNTRIES = ["US", "DE", "GB"]
def scrape(url, country):
driver = create_grid_driver(country)
try:
driver.get(url)
return {"url": url, "title": driver.title, "status": "ok"}
except Exception as e:
return {"url": url, "error": str(e), "status": "fail"}
finally:
driver.quit()
with ThreadPoolExecutor(max_workers=3) as executor:
futures = [
executor.submit(scrape, url, country)
for url, country in zip(URLS, COUNTRIES)
]
for future in as_completed(futures):
print(future.result())
Советы по масштабированию
- Ресурсы: каждый Chrome-контейнер потребляет ~500 МБ RAM. Для 12 параллельных сессий выделяйте минимум 8 ГБ на ноду.
- Таймауты: установите
SE_NODE_SESSION_TIMEOUTиSE_SESSION_REQUEST_TIMEOUTравными максимальному времени скрейпинга одной страницы + 60 с буфера. - Мониторинг: Selenium Grid экспортирует метрики Prometheus на
:4444/metrics. Подключите Grafana для отслеживания очереди сессий. - Graceful shutdown: всегда вызывайте
driver.quit()вfinally-блоке. Зомби-сессии забивают Grid.
Selenium vs Playwright: когда переходить
Playwright — новый конкурент Selenium с нативной поддержкой прокси-авторизации, лучшим stealth и современным API. Но Selenium по-прежнему доминирует в legacy-проектах. Сравнение:
| Критерий | Selenium | Playwright |
|---|---|---|
| Proxy auth (user:pass) | Требует selenium-wire или расширение | Нативно: proxy: {username, password} |
| Stealth / антидетект | Требует selenium-stealth / driverless | Лучше из коробки, но не идеально |
| Параллелизм | Selenium Grid (отдельный инфра) | Встроенный browser.newContext() |
| Ожидание элементов | Явные / неявные ожидания (boilerplate) | Auto-waiting по умолчанию |
| Языки | Java, Python, JS, C#, Ruby | Python, JS, TS, Java, C# |
| Экосистема и QA-интеграции | Огромная: Appium, Katalon, TestNG, JUnit | Растущая, но моложе |
| Legacy-совместимость | Де-факто стандарт для enterprise QA | Требует миграции тестов |
| Headless Chrome | Старый headless (до Chrome 112) и новый | Только новый headless |
Когда Playwright лучше: новый проект, нет legacy-тестов, нужна нативная прокси-авторизация и auto-waiting, важна скорость написания кода.
Когда Selenium лучше: существующий QA-набор на TestNG/JUnit, интеграция с Appium для мобильного тестирования, enterprise-требования к экосистеме, команда с экспертизой в Selenium.
Если вы выбираете фреймворк для Selenium residential proxies скрейпинга с нуля — Playwright сэкономит вам недели. Если мигрируете — делайте поэтапно: сначала замените прокси-слой на Playwright Context, потом мигрируйте тесты.
Ключевые выводы
- Selenium proxy auth не работает из коробки — используйте selenium-wire (Chrome) или Firefox-профили с расширением для авторизации.
- Selenium stealth требует отдельного слоя: selenium-stealth для продакшена, selenium-driverless для максимальной незаметности (но с риском нестабильности).
- Ротация IP должна быть на уровне сессий, не запросов внутри сессии — каждый WebDriver = уникальный IP через ProxyHat-флаги гео-таргетинга.
- Sticky-сессии — обязательны для многошаговых сценариев (логин, навигация, скрейпинг); per-request ротация — для SERP и мониторинга цен.
- Selenium Grid + Docker — продакшен-стандарт для параллельного скрейпинга; не забывайте про
driver.quit()и мониторинг. - Playwright — лучший выбор для новых проектов; Selenium — для legacy и enterprise-QA.
Готовы масштабировать скрейпинг? Ознакомьтесь с тарифами ProxyHat — residential, mobile и datacenter прокси с гео-таргетингом по 190+ странам и ротацией из коробки.






