Как решать капчу в Patchright, не ломая stealth

Шаг с капчей в Patchright состоит из тех же трёх действий, что и везде: считать sitekey, отправить его решателю, вернуть токен в страницу. На третьем действии эта библиотека и удивляет. По умолчанию Patchright выполняет ваш JavaScript в изолированном контексте, а изолированный контекст делит со страницей её DOM, но не её глобальный объект JavaScript. Поэтому запись токена проходит, textarea действительно содержит значение, а собственный callback сайта так и не срабатывает, потому что его просто нет в той области, где выполняется ваш код. Лечится это одним дополнительным аргументом.
Что понадобится
- Python 3.10 или новее, либо Node.js, если вам ближе JavaScript-пакет. Patchright выпускается для обеих сред выполнения и в каждой из них служит прямой заменой Playwright.
- Браузер Chromium, загруженный через собственный установщик Patchright. Firefox и WebKit не пропатчены и не поддерживаются.
- URL страницы с защищённой формой. Sitekey считывается во время выполнения.
- Запущенный CapSkip в режиме Local, когда скрипт и решатель работают на одной машине, или в режиме Server, когда это не так. Оба режима описаны в разделе Настройки подключения.
# pip install patchright pip install -U capskip patchright # Pulls the browser. Real Chrome is the recommended channel, # and the maintainers say so explicitly. patchright install chrome
Что именно меняет Patchright
Patchright представляет собой Playwright с убранными очевидными признаками автоматизации. Главный патч в том, что он никогда не вызывает Runtime.enable, самый громкий сигнал, который выдаёт обычная сессия Playwright, и достигается это за счёт запуска ваших скриптов в изолированных ExecutionContexts. Кроме того, он полностью отключает Console API, добавляет флаг, скрывающий navigator.webdriver, и убирает несколько значений Playwright по умолчанию, которые выдают автоматизированную сессию: сам флаг автоматизации, переопределение блокировщика всплывающих окон, блокировку обновления компонентов и переключатели, отключавшие приложения и расширения по умолчанию.
Два из них напрямую влияют на работу с капчей, и споткнуться о них легко.
Отключённая консоль означает, что ничего из того, что вы логируете изнутри страницы, до вас не дойдёт. Функциональность консоли в Patchright не работает вовсе, поэтому привычка воткнуть строку лога в вызов evaluate и прочитать её со стороны драйвера здесь бесполезна. Вместо этого возвращайте значение из вызова. Так всё равно правильнее, и другого варианта у вас нет.
С расширениями история приятнее. Обычно Playwright запускается с отключёнными расширениями, а Patchright убирает этот переключатель, поэтому браузерное расширение, загруженное в постоянный профиль, действительно работает. Если писать весь этот код вам не хочется, браузерное расширение CapSkip обрабатывает виджет прямо на странице, а вы работаете с формой так, будто её прошёл человек.
Ещё одна возможность, о которой стоит знать: Patchright достаёт содержимое закрытых shadow root обычными локаторами и XPath. К виджетам, которые прячут свою разметку за закрытым root, можно обращаться без всякой особой обработки.
Шаг 1: запускайте так, как рекомендуют сопровождающие
Скрытность Patchright зависит от конфигурации запуска не меньше, чем от самих патчей. В документации описан постоянный контекст на настоящем канале Chrome, с видимым окном и без переопределения viewport, а также вообще без своего user agent и заголовков. Последние два пункта важны: заданный вручную user agent противоречит остальному отпечатку и сводит всю работу на нет.
# pip install patchright
from patchright.sync_api import sync_playwright
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir="C:\\profiles\\scraper",
channel="chrome",
headless=False,
no_viewport=True,
# Do not set user_agent or extra headers here.
)
page = context.new_page()
page.goto("https://example.com/page-with-recaptcha")Обратите внимание на видимое окно. Больше всего бюджета детекта уходит именно на headless, и рекомендуемая конфигурация его не использует. На Windows это значит, что учётной записи, из-под которой работает скрипт, нужна интерактивная сессия рабочего стола, и это стоит запланировать заранее, до того как вы поставите всё это на сервер.
Шаг 2: считайте sitekey со страницы
Ключ sitekey лежит в документе-хосте, а не внутри iframe виджета, и его читает обычный локатор. Локаторы идут по протоколу браузера, а не через какой-либо контекст выполнения, поэтому изолированный мир на этот шаг никак не влияет.
# Locators auto-wait, so this doubles as a wait condition
# for a widget that renders late.
holder = page.locator("div.g-recaptcha")
holder.wait_for(state="attached", timeout=15000)
sitekey = holder.get_attribute("data-sitekey")
print(sitekey) # 6LxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxНекоторые сайты вообще не кладут ключ на страницу-хост и передают его только в URL iframe виджета. В таком случае считайте его из строки запроса и проверьте до того, как тратить решение, потому что пустое значение доходит до решателя и возвращается как ERROR_GOOGLEKEY, далеко от той операции чтения, которая на самом деле и дала сбой.
# Fallback: the k= parameter on the anchor iframe.
from urllib.parse import urlparse, parse_qs
src = page.locator("iframe[src*='recaptcha/api2/anchor']").get_attribute("src")
sitekey = parse_qs(urlparse(src).query)["k"][0]Шаг 3: решите её на своей машине
Python SDK обращается к CapSkip на порту 8080 и возвращает токен обычной строкой. Один метод покрывает reCAPTCHA v2, Invisible, Enterprise и v3, а варианты передаются опциями, а не отдельными вызовами.
# pip install capskip from capskip import CapSkip solver = CapSkip(host="127.0.0.1", port=8080) # Invisible v2 takes invisible=1, Enterprise takes enterprise=1, # and v3 takes version="v3" with an action. result = solver.recaptcha(sitekey=sitekey, url=PAGE_URL) token = result["code"] # the g-recaptcha-response value
У Turnstile и GeeTest есть собственные методы, устроенные так же. Turnstile вдобавок возвращает user agent, с которым было выполнено решение, и страница проверки отклонит токен, если этот user agent не отправить вместе с ним. Отправляйте его только в запросе с формой. Не передавайте его в запуск браузера, потому что заданный вручную user agent это ровно то, чего велит избегать конфигурация stealth. Полные списки параметров приведены в документации CapSkip API.
Шаг 4: положите токен туда, где страница сможет им воспользоваться
Вот та часть, которая специфична именно для этой библиотеки. Поле ответа textarea скрыто через display:none, поэтому напечатать в него ничего нельзя, и значение приходится присваивать через JavaScript. Это присваивание работает и из изолированного контекста, потому что DOM общий. Собирайте строку через json.dumps, а не через f-строку, поскольку строковый литерал JSON одновременно является корректным строковым литералом JavaScript, вместе с кавычками и экранированием.
# A DOM write is fine from the isolated context.
import json
page.evaluate(
"document.getElementById('g-recaptcha-response').value = "
+ json.dumps(token)
)
page.click("button[type=submit]")Этого достаточно для сайтов, форма которых читает textarea при отправке. Многие сайты так не делают. Они регистрируют у виджета success callback и вообще не смотрят на textarea, поэтому токен нужно передать в функцию, объявленную страницей. У изолированного контекста Patchright свой собственный глобальный объект JavaScript, а значит, функций страницы и клиентской конфигурации reCAPTCHA в нём попросту нет. Вызов падает с reference error, и сколько ни повторяй, ничего не изменится.
Ответ Patchright на это: дополнительный аргумент. Все методы evaluate, evaluate_handle и evaluate_all принимают isolated_context, по умолчанию он равен True, а значение False выполняет скрипт в собственном main world страницы.
# The main world is where the page's own globals live.
page.evaluate(
"token => window.onRecaptchaSuccess(token)",
token,
isolated_context=False,
)Имя callback считайте из разметки страницы, а не угадывайте. Используйте main world только для вставки и ни для чего больше: работающий там код виден сайту, и именно поэтому изолированный контекст стоит по умолчанию. Оба способа отправки под капотом остаются обычной reCAPTCHA v2, и оба разобраны на странице сервиса распознавания reCAPTCHA v2.
Полный рабочий пример
Всё вышеописанное в одном скрипте. Решатель создаётся один раз, sitekey проверяется до того, как на него будет потрачено решение, а токен проверяется возвратом длины, а не логированием внутри страницы.
# pip install capskip patchright
import json
from patchright.sync_api import sync_playwright
from capskip import CapSkip
PAGE_URL = "https://example.com/page-with-recaptcha"
solver = CapSkip(host="127.0.0.1", port=8080)
with sync_playwright() as p:
context = p.chromium.launch_persistent_context(
user_data_dir="C:\\profiles\\scraper",
channel="chrome",
headless=False,
no_viewport=True,
)
page = context.new_page()
page.goto(PAGE_URL)
holder = page.locator("div.g-recaptcha")
holder.wait_for(state="attached", timeout=15000)
sitekey = holder.get_attribute("data-sitekey")
if not sitekey:
raise RuntimeError("Widget found but data-sitekey was empty.")
token = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)["code"]
page.evaluate(
"document.getElementById('g-recaptcha-response').value = "
+ json.dumps(token)
)
length = page.evaluate(
"document.getElementById('g-recaptcha-response').value.length"
)
print(length) # 0 means the injection did not land
page.click("button[type=submit]")
page.wait_for_load_state("networkidle")
context.close()Запуск решателя на другой машине
Patchright обычно оказывается на машине помощнее той, где вы писали скрипт, а рекомендуемая конфигурация с видимым окном довольно быстро подталкивает к выделенной виртуальной машине. Решателю переезжать вместе с ним не обязательно.
У CapSkip два режима подключения. Local привязывается к 127.0.0.1 и отвечает только этому устройству, и это то, что нужно, пока вы пишете скрипт на той же машине, где работает приложение. Server привязывается к вашему сетевому или публичному IP, поэтому парсящая виртуальная машина, второй рабочий компьютер или целый их пул обращаются к той же машине с Windows через API. Статический публичный IP сохраняет этот адрес неизменным. В коде не меняется ничего, кроме хоста, и в стоимости тоже ничего не меняется, потому что это всё то же оборудование, которым владеете вы.
# Same SDK, same call. Only the host moves. solver = CapSkip(host="10.0.0.12", port=8080, apiKey="YOUR_API_KEY")
Включите проверку ключа, как только решатель начнёт слушать сетевой адрес, и выдайте каждой машине собственный ключ, чтобы один можно было отозвать, не трогая остальные. Оба режима подробно разобраны в руководстве по настройке CapSkip.
Частые ошибки и что они означают
| Что вы видите | Причина | Исправить |
|---|---|---|
| В textarea токен есть, но форма не проходит | Сайт использует callback и вообще не читает textarea | Вызовите callback с isolated_context=False |
| Reference error с именем функции страницы | Глобальных объектов страницы в изолированном контексте нет | То же решение: выполните этот один вызов в main world |
| От console log на странице ничего не приходит | Patchright полностью отключает Console API | Возвращайте значение из evaluate вместо логирования |
| Firefox или WebKit ведёт себя как обычный Playwright | Пропатчены только браузеры на Chromium | Используйте канал Chromium или Chrome |
| Блокировка несмотря на патчи | Свой user agent или заголовок противоречит отпечатку | Уберите их и используйте постоянный контекст на Chrome |
| ERROR_GOOGLEKEY | До решателя дошёл пустой sitekey | Проверяйте значение до вызова recaptcha |
| NetworkException | CapSkip не запущен или указан неверный хост | Запустите приложение или направьте host на адрес сервера |
| TimeoutException | Решение заняло больше, чем recaptchaTimeout | Поднимите его выше значения по умолчанию в 300 секунд |
FAQ
Можно ли перенести мой скрипт на Playwright без изменений?
Поменяйте импорт, и большая часть заработает. Локаторы, контексты, маршруты, ожидания и навигация ведут себя как прежде. Пересмотреть придётся три вещи: любой вызов evaluate, который трогает что-то объявленное страницей, ему теперь нужен дополнительный аргумент; всё, что опиралось на вывод в консоль, его больше нет; и любую цель на Firefox или WebKit, она не пропатчена. Более широкая картина по Playwright изложена на странице сервиса распознавания капч для Playwright.
Не выдаст ли меня использование main world?
В принципе да, поскольку страница видит выполняющийся там код. На практике всё сводится к одному вызову функции длительностью в микросекунды, и выглядит он так же, как то, что делает собственный скрипт виджета, когда проверку проходит человек. Держите всё остальное в изолированном контексте, делайте вставку одним вызовом, а не несколькими, и открытая площадь останется небольшой.
Не лучше ли кликнуть по виджету Turnstile вместо вставки токена?
Иногда. Флажок Turnstile в режиме managed может пройти сам, если браузер выглядит убедительно, а Patchright именно для этого и нужен, поэтому стоит сначала попробовать клик, а решение оставить запасным вариантом. Клик по флажку reCAPTCHA лишь открывает задание с картинками, так что выигрыша тут нет. Сторона виджета разобрана на страницу решателя Cloudflare Turnstile.
Мой краулер работает на Linux. Куда ставить CapSkip?
На машину с Windows, которой вы управляете, с включённым режимом Server. Linux-машина после этого обращается к нему через API, как к любому другому внутреннему сервису, поэтому Patchright и решателю не нужна ни общая операционная система, ни даже общий сегмент сети. Направьте аргумент host на этот адрес, включите проверку ключей и выдайте краулеру собственный ключ.
Коротко
Установите Patchright, запустите постоянный контекст на канале Chrome с видимым окном и без своего user agent, считайте sitekey обычным локатором и решите капчу через CapSkip на 127.0.0.1:8080. Токен пишите прямо в textarea из изолированного контекста по умолчанию, а к isolated_context=False обращайтесь только тогда, когда сайту нужен вызов callback. Об остальном ландшафте Python, включая Selenium и Playwright, смотрите странице сервиса распознавания капч для Python.
Одну вещь стоит знать, прежде чем масштабировать обход. CapSkip представляет собой безлимитный сервис распознавания капчи который работает на оборудовании, которое у вас уже есть, поэтому повторить тысячу страниц стоит ровно столько же, сколько повторить десять.
