Как доработать undetected-chromedriver: Socks5, многопроцессность и обработка капч

Как доработать undetected-chromedriver: SOCKS5, многопроцессность и модуль капчи
Автоматизация браузера на базе Selenium часто упирается в ограничения стандартных инструментов. Даже если сценарий корректно запускается локально, при большом количестве параллельных задач возникают проблемы с прокси, конфликтами профилей, обнаружением автоматизации и обработкой капч. Поэтому на практике нередко требуется не просто настроить готовую библиотеку, а адаптировать её под конкретную инфраструктуру.
В качестве основы можно использовать `undetected-chromedriver` - модифицированный драйвер для запуска Chromium-браузеров с меньшим количеством очевидных признаков автоматизации. Однако у исходного проекта есть ограничения: работа с SOCKS5 не всегда удобна, параллельный запуск требует самостоятельного управления профилями, а обработку капч приходится выносить в отдельный слой.
Зачем понадобился собственный форк
Основная причина создания форка - необходимость объединить несколько функций в одном управляемом решении:
- подключение индивидуального SOCKS5-прокси к каждому браузеру;
- запуск нескольких независимых экземпляров Chrome;
- автоматическое создание временных профилей;
- корректное завершение дочерних процессов;
- единый интерфейс для обработки капч;
- возможность менять параметры запуска без правок в прикладном коде.
Отдельно важно учитывать, что браузерный профиль нельзя бездумно использовать одновременно в нескольких процессах. Chrome блокирует каталог профиля, а при аварийном завершении могут остаться lock-файлы и дочерние процессы. Поэтому многопроцессная архитектура должна заранее решать вопросы изоляции и очистки ресурсов.
Подключение SOCKS5
У Chromium нет универсального и одинаково удобного механизма для передачи логина и пароля SOCKS5 через аргумент командной строки. Обычный параметр вроде `--proxy-server=socks5://user:password@host:port` может работать нестабильно или привести к тому, что учетные данные будут обработаны некорректно.
Для прокси без авторизации достаточно передать браузеру адрес сервера:
```python
options.add_argument("--proxy-server=socks5://127.0.0.1:9050")
```
Если прокси требует авторизацию, практичнее использовать расширение Chrome, которое перехватывает запрос авторизации и передает браузеру нужные учетные данные. Расширение можно собрать прямо во время запуска: сформировать `manifest.json`, добавить обработчик `onAuthRequired`, упаковать файлы во временную директорию и подключить каталог через `--load-extension`.
При таком подходе у каждого экземпляра браузера может быть собственное расширение с отдельными параметрами прокси. Это особенно удобно при параллельной обработке задач, когда разные процессы должны работать через разные IP-адреса.
Изоляция профилей
Каждому процессу следует назначать отдельный каталог профиля. Обычно для этого используют временные директории:
```python
import tempfile
profile_dir = tempfile.mkdtemp(prefix="chrome-profile-")
options.add_argument(f"--user-data-dir={profile_dir}")
```
Изоляция решает сразу несколько проблем:
1. процессы не блокируют один и тот же профиль;
2. cookies и local storage не смешиваются между задачами;
3. настройки одного браузера не влияют на другой;
4. проще удалять данные после завершения работы;
5. снижается вероятность повреждения профиля.
Если нужно сохранять сессию между запусками, вместо временного каталога можно использовать заранее подготовленные директории. Но в таком случае необходимо строго соблюдать правило: один профиль - один активный процесс Chrome.
Многопроцессный запуск
Для независимых браузерных сессий лучше использовать `multiprocessing`, а не потоки. Selenium взаимодействует с внешним процессом браузера, а сложные сценарии с ожиданиями, сетевыми задержками и капчами плохо масштабируются в рамках одного потока.
Упрощенная схема выглядит так:
```python
from multiprocessing import Process
def worker(proxy):
driver = create_driver(proxy)
try:
driver.get("https://example.com")
# Основная логика задачи
finally:
driver.quit()
processes = []
for proxy in proxies:
process = Process(target=worker, args=(proxy,))
process.start()
processes.append(process)
for process in processes:
process.join()
```
Каждый рабочий процесс самостоятельно создает драйвер, профиль и подключение к прокси. Главный процесс отвечает за распределение задач и ожидание завершения дочерних процессов.
На практике полезно добавить ограничение количества одновременно работающих браузеров. Chrome потребляет значительный объем оперативной памяти, поэтому запуск десятков экземпляров может привести к нехватке ресурсов. Для контроля применяют пул процессов или семафор.
Корректное завершение браузера
Вызова `driver.quit()` бывает недостаточно, особенно если браузер завершился аварийно или процесс был остановлен извне. После завершения стоит проверить, не остались ли процессы Chrome и chromedriver, относящиеся к конкретной задаче.
Также желательно:
- хранить PID дочерних процессов;
- обрабатывать `KeyboardInterrupt`;
- удалять временный профиль после завершения;
- устанавливать тайм-аут на выполнение задания;
- не оставлять фоновые процессы после исключения.
Для длительных задач удобно использовать контекстный менеджер, который гарантирует очистку ресурсов даже при ошибке:
```python
from contextlib import contextmanager
@contextmanager
def managed_driver(proxy):
driver = create_driver(proxy)
try:
yield driver
finally:
driver.quit()
```
Модуль обработки капч
Капча - отдельная задача, которую не стоит жестко встраивать в код навигации. Лучше выделить самостоятельный модуль с единым интерфейсом. Он может отвечать за:
- обнаружение капчи на странице;
- определение ее типа;
- отправку задания во внешний сервис;
- ожидание результата;
- передачу токена в браузер;
- повторную проверку состояния страницы;
- обработку тайм-аутов и ошибок.
Удобная архитектура строится вокруг абстрактного класса:
```python
class CaptchaSolver:
def solve(self, driver):
raise NotImplementedError
```
Для разных типов проверок создаются собственные реализации. Основной сценарий при этом не знает деталей конкретного сервиса и вызывает только метод `solve()`.
Важно предусмотреть ограничение числа попыток. Если капча не решилась с первого раза, бесконечные повторы могут привести к блокировке сессии. Лучше использовать небольшой лимит, увеличивать интервалы между попытками и завершать задачу при повторяющихся ошибках.
Ожидания и устойчивость сценариев
Надежность автоматизации сильно зависит от правильного ожидания элементов. Фиксированные задержки через `time.sleep()` делают сценарий медленным и нестабильным: на быстром соединении программа простаивает, а при высокой нагрузке времени может не хватить.
Предпочтительнее применять явные ожидания:
```python
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(driver, 20).until(
EC.presence_of_element_located(("css selector", "input"))
)
```
Кроме наличия элемента, следует проверять его видимость, доступность для клика и фактическое изменение состояния страницы. После отправки формы полезно ожидать исчезновения индикатора загрузки или появления результата, а не просто выдерживать фиксированную паузу.
Логирование и диагностика
При параллельном запуске без логирования определить причину сбоя практически невозможно. Каждому процессу стоит присвоить собственный идентификатор и записывать:
- время старта и завершения;
- используемый прокси без раскрытия пароля;
- адрес текущей страницы;
- этап сценария;
- текст исключения;
- количество попыток решения капчи;
- код завершения процесса.
Пароли и токены нельзя помещать в обычные логи. При необходимости следует маскировать чувствительные значения, а снимки экрана сохранять только в защищенное хранилище с ограниченным сроком жизни.
Что еще важно учесть
Перед запуском большого числа экземпляров необходимо проверить совместимость версий Chromium и драйвера. Обновление браузера может изменить поведение аргументов командной строки, расширений или механизмов защиты, поэтому версии лучше фиксировать в окружении.
Также рекомендуется заранее определить правила повторной обработки задач. Если процесс завершился из-за сетевой ошибки, задачу можно вернуть в очередь. Если же обнаружена блокировка или повторяющаяся капча, автоматический повтор может только усугубить ситуацию.
Для производственной эксплуатации полезно добавить очередь заданий, ограничение скорости запросов и централизованный мониторинг. Такие меры снижают нагрузку на сайты и помогают не превращать автоматизацию в неконтролируемый поток запросов.
Итоговая архитектура
В результате получается несколько независимых уровней:
1. фабрика драйверов создает Chrome с нужным профилем и прокси;
2. рабочий процесс выполняет одну изолированную задачу;
3. модуль капчи занимается только проверками и их обработкой;
4. менеджер процессов ограничивает параллелизм;
5. система логирования собирает диагностические данные;
6. блок очистки удаляет временные файлы и завершает дочерние процессы.
Такой подход делает форк `undetected-chromedriver` не просто измененной библиотекой, а полноценным инструментом для управляемой браузерной автоматизации. При этом устойчивость системы определяется не одной настройкой "антидетекта", а совокупностью факторов: корректной изоляцией профилей, аккуратной работой с прокси, контролем процессов, понятной обработкой ошибок и разумным ограничением нагрузки.
