Как решать капчу в undetected-chromedriver (Python)

undetected-chromedriver патчит Chrome, чтобы флаги автоматизации перестали утекать. Это пускает вас на страницу. Но не проводит через чекбокс reCAPTCHA, потому что виджет проверяет не отпечаток, а требует токен. Капчу в undetected-chromedriver всё равно нужно решить, а ответ вставить в DOM.
Четыре шага: запустить пропатченный драйвер, считать sitekey со страницы, решить капчу локально и положить токен туда, где его ждёт страница. Ниже всё это на Python.
Что undetected-chromedriver делает, а что нет
| Сигнал | Обрабатывается драйвером |
|---|---|
navigator.webdriver и характерные следы CDP | Да |
| Пропатченный бинарник, поэтому драйвер не определяется как драйвер | Да |
| Чекбокс reCAPTCHA v2 в форме входа | Нет |
| Невидимая задача v2 или v3, которую запускает сама страница | Нет |
| Виджет Turnstile | Нет |
Различие важно, потому что заблокированному парсеру обычно советуют “добавь stealth”. Если виджет уже на экране, никакой stealth его не уберёт. Нужен токен.
Что понадобится
- Установленный Chrome и запущенный CapSkip на
127.0.0.1:8080. Страница руководство по настройке описывает само приложение. - Python 3.10 или новее.
- Три пакета.
# the patched driver, selenium itself, and the local solver pip install undetected-chromedriver selenium capskip
Шаг 1: запустите пропатченный драйвер
Импортируйте его как uc и соберите браузер так же, как в обычном Selenium. Опции берутся из uc.ChromeOptions(), а не из Selenium, потому что библиотека патчит их по пути:
# pip install undetected-chromedriver
import undetected_chromedriver as uc
options = uc.ChromeOptions()
options.add_argument("--window-size=1280,800")
# use_subprocess keeps the patched binary alive long enough
# to shut down cleanly on Windows.
driver = uc.Chrome(options=options, use_subprocess=True)
driver.get("https://example.com/login")Не накручивайте сверху ещё один stealth-плагин. Два патчера, дерущиеся за одни и те же свойства, сами по себе служат сигналом для детекта и частой причиной того, почему рабочая связка внезапно перестаёт работать.
Шаг 2: считайте sitekey со страницы
Sitekey является публичным атрибутом элемента виджета. Читайте его из живого DOM, а не хардкодьте: многие сайты меняют ключи в зависимости от окружения:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# The widget is injected by script, so wait for it.
widget = WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "[data-sitekey]"))
)
sitekey = widget.get_attribute("data-sitekey")
page_url = driver.current_url
print(sitekey)Использовать driver.current_url а не тот URL, который вы запрашивали. Если сайт перенаправил вас на страницу с проверкой, решателю нужно сообщить именно ту страницу, на которой вы оказались.
Шаг 3: решите капчу локально
CapSkip работает на вашей машине, поэтому решение сводится к вызову localhost. Ни аккаунта, ни платы за каждое решение; за пределы машины уходят только sitekey и URL страницы:
# pip install capskip
from capskip import CapSkip
solver = CapSkip(
host="127.0.0.1",
port=8080,
recaptchaTimeout=300, # seconds, also covers Turnstile
)
result = solver.recaptcha(sitekey=sitekey, url=page_url)
token = result["code"] # the g-recaptcha-response valueВарианты задаются опциями, а не отдельными методами. Добавьте invisible=1 для невидимого виджета v2, version="v3" с action для v3 и enterprise=1 для продукта Enterprise.
Шаг 4: вставьте токен и вызовите callback
Токен должен попасть в скрытую g-recaptcha-response textarea. Одного этого достаточно для обычной отправки формы, потому что поле уходит вместе с остальной формой:
driver.execute_script(
"document.getElementById('g-recaptcha-response')"
".innerHTML = arguments[0];",
token,
)
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()С одностраничными приложениями иначе. Они вообще не читают textarea, а ждут, когда токен им передаст собственный callback виджета. Имя функции объявлено прямо на элементе, поэтому её можно вызвать напрямую:
callback = widget.get_attribute("data-callback")
if callback:
# Call the page's own handler with the token, exactly as
# the widget would have done.
driver.execute_script(f"{callback}(arguments[0]);", token)Если после вставки ничего не происходит и на элементе отсутствует data-callback, значит, страница подключает callback через JavaScript, а не через разметку. Страница решатель callback для reCAPTCHA v2 объясняет, как найти его в этом случае.
Turnstile в том же драйвере
Cloudflare Turnstile работает так же, только меняются два имени. Поле ответа называется cf-turnstile-response, и это input, а не textarea:
result = solver.turnstile(sitekey=sitekey, url=page_url)
driver.execute_script(
"document.querySelector('[name=cf-turnstile-response]')"
".value = arguments[0];",
result["code"],
)Это относится к виджету, встроенному в форму. Полностраничной промежуточной проверке нужны два дополнительных значения и тот user agent, с которым выполнялось решение. Об этом рассказывает страница Сервис распознавания Turnstile объясняет, как это сделать.
Полный рабочий пример
# pip install undetected-chromedriver selenium capskip
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from capskip import (
CapSkip, NetworkException, ApiException, TimeoutException,
)
URL = "https://example.com/login"
solver = CapSkip(host="127.0.0.1", port=8080)
driver = uc.Chrome(options=uc.ChromeOptions(), use_subprocess=True)
try:
driver.get(URL)
widget = WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "[data-sitekey]"))
)
sitekey = widget.get_attribute("data-sitekey")
result = solver.recaptcha(sitekey=sitekey, url=driver.current_url)
driver.execute_script(
"document.getElementById('g-recaptcha-response')"
".innerHTML = arguments[0];",
result["code"],
)
callback = widget.get_attribute("data-callback")
if callback:
driver.execute_script(f"{callback}(arguments[0]);", result["code"])
else:
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
except NetworkException:
print("CapSkip is not running on 127.0.0.1:8080")
except (ApiException, TimeoutException) as err:
print(f"solve failed: {err}")
finally:
driver.quit() # always, or the patched binary lingersРешайте и отправляйте за один проход. Токен reCAPTCHA принимается примерно две минуты, поэтому скрипт, который решает капчу, потом тридцать секунд занимается другими делами и только затем отправляет форму, работает нормально. А тот, что складывает токены в очередь на потом, уже нет.
Частые ошибки
| Что вы видите | Причина | Исправить |
|---|---|---|
This version of ChromeDriver only supports Chrome version N | Пропатченный драйвер неверно угадал мажорную версию Chrome | Передайте её явно: uc.Chrome(version_main=N) где N обозначает мажорную версию установленного у вас Chrome |
OSError: [WinError 6] The handle is invalid при выходе | Сборщик мусора убрал драйвер до того, как тот завершился | Вызывайте driver.quit() в блоке finally и оставьте use_subprocess=True |
NoSuchElementException при поиске [data-sitekey] | Виджет находится в iframe или ещё не отрисован | Дождитесь его, как описано выше. Для iframe вместо этого считывайте sitekey из query-строки в атрибуте src самого iframe |
ERROR_GOOGLEKEY | До решателя дошёл пустой или обрезанный sitekey | Выведите его перед решением. Смотрите исправление ERROR_GOOGLEKEY |
NetworkException | CapSkip не запущен или порт отличается | Запустите приложение и проверьте порт в его настройках |
| Токен вставлен, а форма всё равно не принимается | Страница использует callback и вообще не читает textarea | Вызывайте data-callback с токеном |
Все коды ошибок и параметры перечислены в Документация по API.
Часто задаваемые вопросы
Решает ли undetected-chromedriver капчу сам по себе?
Нет. Он прячет сигналы автоматизации, из-за которых браузер помечают. Отрисованный виджет всё равно ждёт валидный токен, поэтому вы решаете капчу и вставляете результат. Эти две задачи дополняют друг друга, а не заменяют.
Можно ли запускать его в headless-режиме?
Можно, с uc.Chrome(headless=True), который применяет собственные headless-патчи библиотеки, а не флаг Selenium. Проверок будет больше, чем в режиме с окном. Сама вставка токена работает одинаково в обоих случаях.
Нужен ли прокси для решения капчи?
Только если браузер уже ходит через прокси. Передайте тот же самый как proxy={"type": "HTTPS", "uri": "user:pass@host:port"} чтобы решение и отправка шли одним сетевым путём. Прокси применимы к reCAPTCHA, Turnstile и GeeTest и никогда не применимы к графическим капчам.
Работает ли это в обычном Selenium?
Да. Шаги со второго по четвёртый представляют собой обычные вызовы WebDriver, поэтому они работают без изменений в стандартном Selenium, в Selenium Grid и на удалённых драйверах. Только первый шаг специфичен для пропатченной библиотеки.
Сводка
Пусть undetected-chromedriver занимается отпечатком, а виджетом займитесь сами. Дождитесь [data-sitekey], решите капчу через localhost, запишите токен в g-recaptcha-response, и вызовите data-callback если страница его объявляет. Завершайте драйвер в блоке finally , чтобы пропатченный бинарник не остался висеть в памяти.
Всё остальное по Selenium собрано на странице распознавание капчи в Selenium , полный список методов собран на странице Решатель капчи для Python , а решение капчи для парсинга рассказывает, как делать это в больших объёмах. CapSkip работает как локальный сервис распознавания капчи, поэтому каждое решение выше происходит на вашей собственной машине.
