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

undetected-chromedriver captcha - How to Handle CAPTCHA in 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
NetworkExceptionCapSkip не запущен или порт отличаетсяЗапустите приложение и проверьте порт в его настройках
Токен вставлен, а форма всё равно не принимаетсяСтраница использует 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 работает как локальный сервис распознавания капчи, поэтому каждое решение выше происходит на вашей собственной машине.