Selenium proxy auth и stealth: полное руководство для QA и scraping-инженеров

Практический гайд по настройке авторизованных прокси в Selenium: selenium-wire для Chrome, Firefox-профили, антидетект, ротация IP и масштабирование через Selenium Grid. С примерами кода и сравнением с Playwright.

Selenium proxy auth и stealth: полное руководство для QA и scraping-инженеров

Если вы хотя бы раз пытались передать 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+ странам и ротацией из коробки.

Готовы начать?

Доступ к более чем 50 млн резидентных IP в 148+ странах с AI-фильтрацией.

Смотреть ценыРезидентные прокси
← Вернуться в Блог