Как решить капчу в Pydoll с помощью CapSkip на асинхронном Python

Чтобы решить капчу в Pydoll, считайте sitekey со страницы, решите её клиентом AsyncCapSkip из CapSkip в том же цикле событий asyncio, запишите токен в поле g-recaptcha-response и запустите коллбэк страницы или отправьте форму. В самом Pydoll 3.0 кода для reCAPTCHA нет, поэтому токен должен прийти от решателя. CapSkip работает на вашем собственном ПК с Windows, так что у обхода, который открывает много страниц, нет платы за каждое решение. В этом руководстве весь процесс решения капчи в Pydoll собирается примерно в 80 строк, а ещё разбираются ловушки, на которых в Windows уходит время.
Что понадобится
- CapSkip, запущенный на ПК с Windows, с включённым решателем reCAPTCHA. Пошаговое руководство по настройке проведёт вас через первый запуск.
- Python 3.10 или новее. Он нужен и Pydoll 3.0.0, и пакету capskip 1.3.0. Всё ниже запускалось на Windows 11 с Python 3.14.0, а полный скрипт запускался ещё и на Python 3.11.7. Python 3.10, 3.12 и 3.13 не запускались.
- Google Chrome, установленный в стандартную папку Program Files. В руководстве использовался Chrome 155, а полный скрипт запускался ещё и с Chrome for Testing 146.
- Окно PowerShell. Команды написаны в синтаксисе PowerShell.
- Ваша собственная страница с чекбоксом reCAPTCHA v2. Пример формы представляет собой обычную форму регистрации. Поменяйте селекторы под свою страницу.
- Адрес решателя. В режиме Local CapSkip отвечает на 127.0.0.1 и только для этого устройства. В режиме Server он слушает ваш сетевой адрес или публичный IP, поэтому Pydoll на другом ПК, VPS или хостинговой платформе может обращаться к нему через API. Оба режима задаются в разделе Настройки подключения.
Замеры ниже получены на имитации CapSkip и имитации страницы регистрации на том же ПК, с настоящим Pydoll, настоящим SDK, настоящим Chrome и настоящим скриптом api.js от Google. Поведение браузера и SDK верно и для вашего собственного сайта. Время решения и правила приёма и отклонения на странице относятся только к самой имитации. Одно решение на работающем CapSkip с публичным тестовым sitekey от Google и настройками по умолчанию заняло 9.0 секунды и вернуло токен из 1764 символов. Время решения меняется от запуска к запуску. Этот токен нигде не использовался, и ни один живой сайт не проверялся.
Где заканчивается Pydoll и начинается CapSkip
Pydoll управляет Chrome по протоколу DevTools, без WebDriver посередине. Он даёт вам браузер, элементы страницы и способ выполнять JavaScript. Единственный код для капчи в Pydoll 3.0.0 относится к помощнику для чекбокса Cloudflare Turnstile. Он находит чекбокс и кликает по нему. Токен он не считывает и не выбрасывает ошибку, когда ничего не происходит. Обработки reCAPTCHA нет вообще. Чекбокс reCAPTCHA v2 решает сервис, который возвращает токен, а затем этот токен нужно положить на странице в нужное место. Именно это здесь делает CapSkip, а остальная часть руководства служит связующим кодом.
Шаг 1: создаём проект
Используйте виртуальное окружение в пустой папке. Команды запускают Python из этого окружения по его пути, так что активировать окружение не нужно, а ограничивающая политика скриптов PowerShell не мешает. Каждый скрипт ниже запускайте Python из окружения, например .\.venv\Scripts\python.exe solve.py. Старая установка Pydoll, например 2.27.0, упала бы на первом импорте скрипта ниже с ошибкой ImportError: cannot import name ‘Chrome’ from ‘pydoll’. В Pydoll 3.0 Chrome экспортируется с верхнего уровня.
# Windows PowerShell, in an empty folder python -m venv .venv .\.venv\Scripts\python.exe -m pip install pydoll-python capskip
Шаг 2: запустите Chrome и откройте страницу
Сохраните это как solve.py. Это самый маленький скрипт Pydoll, который открывает вашу страницу и печатает её заголовок. Браузер запускается внутри блока async with, поэтому по окончании блока он закрывается.
# pip install pydoll-python capskip
import asyncio
from pydoll import Chrome
from pydoll.browser.options import ChromiumOptions
PAGE = 'https://example.com/page-with-recaptcha'
async def main():
options = ChromiumOptions()
options.headless = True # set to False to watch the window
async with Chrome(options=options) as browser:
tab = await browser.start()
await tab.go_to(PAGE)
print(await tab.title())
asyncio.run(main())Pydoll ищет Chrome только в двух местах: в Program Files и Program Files (x86), в папке Google, Chrome, Application. Chrome, установленный для одного пользователя, в AppData, не находится, и скрипт останавливается с InvalidBrowserPath. Чтобы это исправить, задайте в options.binary_location полный путь к chrome.exe. Если указать несуществующий путь, вместо этого получится FailedToStartBrowser. Та же строка направляет Pydoll на Chrome for Testing, и это тоже сработало. Pydoll не сверяет версии браузера ни с чем, поэтому запустились и 146, и 155.
В Windows запуск медленнее, чем вы ожидаете. На тестовом ПК browser.start() занял 2.8 секунды с настройками по умолчанию и headless Chrome 155. Первый go_to занял ещё 2.1 секунды на странице без внешнего скрипта и около 3.1 секунды на странице регистрации, которая загружает скрипт Google. Pydoll подключается к localhost, а localhost сначала пробует адрес IPv6. Порт отладки Chrome не слушает IPv6, и Windows около 2 секунд отказывает в соединении с петлёй, поэтому каждое новое соединение с Chrome платит около 2 секунд, прежде чем сработает IPv4. Это касается самого браузера и каждой дополнительной вкладки, которую вы открываете. В Pydoll для этого нет настройки. Если заставить localhost разрешаться в 127.0.0.1 внутри процесса Python, в тесте задержка исчезает, но это патчит стандартную библиотеку, поэтому в руководстве так не делается. Учитывайте эту задержку, когда замеряете время запуска.
Блок with закрывает Chrome при обычном выходе, после исключения внутри него и после отменённой задачи, а также удаляет временную папку профиля. Если завершить процесс Python командой taskkill /F, этого не происходит. В тесте остались работать 13 процессов chrome.exe, а папка профиля осталась на диске. Ctrl+C в консоли не проверялся. Если вы прервали запуск, перед следующим запуском поищите оставшиеся процессы chrome.exe в Task Manager.
Шаг 3: проверьте, что CapSkip отвечает
Прежде чем добавлять браузер, убедитесь, что вызов решателя работает сам по себе. Сохраните это как first_solve.py. AsyncCapSkip является асинхронным клиентом из того же пакета. SDK не читает переменные окружения, поэтому хост и порт записаны прямо в клиент. Значения-заглушки в коде написаны буквально. YOUR_SITEKEY и адрес страницы должны относиться к странице, на которой действительно есть виджет reCAPTCHA v2, иначе CapSkip ответит ошибкой. Скопируйте sitekey из исходного кода страницы, это значение data-sitekey. Шаг 4 считывает его со страницы за вас. Запустите файл командой .\.venv\Scripts\python.exe first_solve.py.
# pip install capskip
import asyncio
from capskip import AsyncCapSkip
async def main():
solver = AsyncCapSkip(host='127.0.0.1', port=8080)
result = await solver.recaptcha(
sitekey='YOUR_SITEKEY',
url='https://example.com/page-with-recaptcha',
)
print(result['code'][:30])
asyncio.run(main())Шаг 4: считайте sitekey со страницы
Sitekey лежит на странице, и Pydoll может его прочитать. Обычный виджет представляет собой div с классом g-recaptcha и атрибутом data-sitekey. У виджета, который страница рисует через grecaptcha.render(), такого div нет. Он оставляет iframe, в адресе которого sitekey передаётся в параметре k. Эта функция обрабатывает оба случая одним селектором, а имя тега подсказывает, какой из них получен. Добавьте её в solve.py выше async def main().
from urllib.parse import parse_qs, urlparse
async def find_sitekey(tab):
# A normal widget carries data-sitekey. A widget drawn by
# grecaptcha.render() only leaves its iframe, with k=<sitekey>.
found = await tab.query(
'.g-recaptcha[data-sitekey], '
'iframe[src*="recaptcha/api2/anchor"]',
timeout=10,
)
if found.tag_name == 'iframe':
src = found.get_attribute('src')
return parse_qs(urlparse(src).query)['k'][0]
return found.get_attribute('data-sitekey')Здесь важны две детали Pydoll. get_attribute является обычной функцией, а не корутиной, поэтому не вызывайте её через await, а для отсутствующего атрибута она возвращает None. А query выбрасывает ElementNotFound, когда ничего не найдено. С таймаутом он ждёт и вместо этого выбрасывает WaitElementTimeout, и это правильный выбор для виджета, который появляется через мгновение после загрузки страницы. Виджет, добавленный через 3 секунды после загрузки, нашёлся примерно через 3 секунды, при таймауте в 10 секунд в find_sitekey.
Шаг 5: решайте в том же цикле событий
Pydoll работает на asyncio, и ваш вызов решения выполняется в том же цикле. Поэтому важен выбор клиента. Добавьте это тоже выше async def main() и сделайте то же самое с функцией из шага 6.
from capskip import AsyncCapSkip
solver = AsyncCapSkip(host='127.0.0.1', port=8080)
async def solve(sitekey, url):
result = await solver.recaptcha(sitekey, url)
return result['code']Синхронный клиент CapSkip тоже сработал бы, но он блокирует цикл на всё время решения, и пока он ждёт, Pydoll перестаёт отвечать Chrome. Прогон на имитации считал, как часто страница отвечала, когда у неё каждые 50 миллисекунд спрашивали время, пока шло решение на имитации длиной 3 секунды. Самая длинная пауза показывает наибольший промежуток между двумя ответами:
| Клиент | Время решения | Тики счётчика | Самая длинная пауза |
|---|---|---|---|
| Синхронный клиент CapSkip, вызванный прямо в асинхронной функции | 3.76 s | 5 | 3.78 s |
| CapSkip в asyncio.to_thread | 3.76 s | 65 | 0.06 s |
| AsyncCapSkip | 5.0 s | 68 | 0.3 s |
В первом случае сама страница продолжала работать. Замирала только сторона Python: Python не может реагировать ни на что из происходящего на странице, пока не закончится решение, поэтому события из второй вкладки обрабатываются с опозданием. AsyncCapSkip здесь занял примерно на секунду больше, но это зависит от того, куда попадёт его следующий опрос после решения, и при решениях на имитации в 2, 4 и 8 секунд он закончил раньше, чем вариант с потоком. Всегда он стоит около 0.15 секунды заблокированного цикла на каждый запрос, потому что каждый раз создаёт новый HTTP-клиент. Это всё равно лучше замороженного цикла.
Шаг 6: запишите токен в страницу
Решённая reCAPTCHA представляет собой токен. Страница ожидает его в textarea с именем g-recaptcha-response, а если у виджета есть коллбэк, страница ожидает, что коллбэк выполнится с этим токеном. Эта функция делает и то и другое. Она использует execute_script из Pydoll и возвращает True, если нашла и запустила коллбэк. Добавьте её тоже выше async def main().
import json
FILL_JS = """
(function (token) {
var seen = new Set(), fn = null;
document.querySelectorAll('textarea[name="g-recaptcha-response"]')
.forEach(function (box) { box.value = token; });
(function walk(o, depth) {
if (!o || typeof o !== 'object' || seen.has(o) || depth > 6) return;
seen.add(o);
for (var k in o) {
var v; try { v = o[k]; } catch (e) { continue; }
if (k === 'callback' && !fn) {
fn = typeof v === 'string' ? window[v] : v;
}
if (v && typeof v === 'object' && !(v instanceof Node)) {
walk(v, depth + 1);
}
}
})(window.___grecaptcha_cfg, 0);
if (typeof fn === 'function') fn(token);
return typeof fn === 'function';
})(%s)
"""
async def fill_token(tab, token):
reply = await tab.execute_script(
FILL_JS % json.dumps(token), return_by_value=True)
outcome = reply['result']
if 'exceptionDetails' in outcome:
error = outcome['exceptionDetails']['exception']
raise RuntimeError(error['description'])
return outcome['result']['value'] # True if a callback ranJavaScript обходит собственный объект конфигурации Google, чтобы найти коллбэк, потому что ключи внутри него являются короткими именами, которые Google может изменить. Атрибут data-callback хранится там как имя функции, поэтому обход ищет это имя в window. Коллбэк, переданный в grecaptcha.render(), хранится как сама функция. Оба случая проверены на настоящем api.js от Google.
Pydoll возвращает из execute_script необработанный ответ DevTools, поэтому значение лежит в reply[‘result’][‘result’][‘value’], и вызов нужно ждать через await. Объект возвращается как ссылка, если не передать return_by_value=True. Ошибка JavaScript не выбрасывается в Python. Она добавляет в ответ exceptionDetails, поэтому функция проверяет его наличие и сама выбрасывает RuntimeError.
Шаг 7: соберите всё вместе
Удалите async def main() и строку asyncio.run(main()) внизу solve.py, оставьте функции выше них и вставьте вот это на их место. Решение запускается после загрузки страницы, потому что sitekey берётся со страницы, и прямо перед использованием токена. Если коллбэк выполнился, дальше всё делает страница. Если нет, скрипт нажимает кнопку отправки формы и ждёт перехода.
async def main():
options = ChromiumOptions()
options.headless = True # set to False to watch the window
async with Chrome(options=options) as browser:
tab = await browser.start()
await tab.go_to(PAGE)
sitekey = await find_sitekey(tab)
token = await solve(sitekey, await tab.current_url())
if not await fill_token(tab, token):
button = await tab.query('form button[type="submit"]')
async with tab.expect_navigation():
await button.click()
print(await tab.title())
asyncio.run(main())На странице, где отправку делает коллбэк, нажимать нечего. Эта версия заменяет main() тем же способом. Что произойдёт дальше, зависит от страницы, поэтому ждите того, что меняет сама страница. Эта версия ждёт, пока элемент с результатом на тестовой странице сменит значение waiting на другое. Поменяйте селектор на тот элемент, который обновляет ваша страница.
async def main():
options = ChromiumOptions()
options.headless = True # set to False to watch the window
async with Chrome(options=options) as browser:
tab = await browser.start()
await tab.go_to(PAGE)
sitekey = await find_sitekey(tab)
token = await solve(sitekey, await tab.current_url())
if await fill_token(tab, token):
# The page's own callback takes over. What it does next is
# page specific: this page writes its answer into #result.
await tab.wait_for_script(
"var r = document.querySelector('#result');"
"return r && r.textContent !== 'waiting';"
)
print(await (await tab.query('#result')).text())
asyncio.run(main())Весь скрипт
Вот solve.py целиком, со всеми импортами вверху. Он заменяет блоки, которые вы добавляли по одному, поэтому сначала удалите их. На странице с обычной формой он напечатал Welcome, и страница один раз приняла токен. На странице, где отправку делает коллбэк, эта версия может напечатать старый заголовок до смены страницы, поэтому там используйте вариант с коллбэком выше.
# pip install pydoll-python capskip
import asyncio
import json
from urllib.parse import parse_qs, urlparse
from capskip import AsyncCapSkip
from pydoll import Chrome
from pydoll.browser.options import ChromiumOptions
PAGE = 'https://example.com/page-with-recaptcha'
solver = AsyncCapSkip(host='127.0.0.1', port=8080)
# Put the token in every response box, then run the page's callback.
FILL_JS = """
(function (token) {
var seen = new Set(), fn = null;
document.querySelectorAll('textarea[name="g-recaptcha-response"]')
.forEach(function (box) { box.value = token; });
(function walk(o, depth) {
if (!o || typeof o !== 'object' || seen.has(o) || depth > 6) return;
seen.add(o);
for (var k in o) {
var v; try { v = o[k]; } catch (e) { continue; }
if (k === 'callback' && !fn) {
fn = typeof v === 'string' ? window[v] : v;
}
if (v && typeof v === 'object' && !(v instanceof Node)) {
walk(v, depth + 1);
}
}
})(window.___grecaptcha_cfg, 0);
if (typeof fn === 'function') fn(token);
return typeof fn === 'function';
})(%s)
"""
async def find_sitekey(tab):
# A normal widget carries data-sitekey. A widget drawn by
# grecaptcha.render() only leaves its iframe, with k=<sitekey>.
found = await tab.query(
'.g-recaptcha[data-sitekey], '
'iframe[src*="recaptcha/api2/anchor"]',
timeout=10,
)
if found.tag_name == 'iframe':
src = found.get_attribute('src')
return parse_qs(urlparse(src).query)['k'][0]
return found.get_attribute('data-sitekey')
async def solve(sitekey, url):
result = await solver.recaptcha(sitekey, url)
return result['code']
async def fill_token(tab, token):
reply = await tab.execute_script(
FILL_JS % json.dumps(token), return_by_value=True)
outcome = reply['result']
if 'exceptionDetails' in outcome:
error = outcome['exceptionDetails']['exception']
raise RuntimeError(error['description'])
return outcome['result']['value'] # True if a callback ran
async def main():
options = ChromiumOptions()
options.headless = True # set to False to watch the window
async with Chrome(options=options) as browser:
tab = await browser.start()
await tab.go_to(PAGE)
sitekey = await find_sitekey(tab)
token = await solve(sitekey, await tab.current_url())
if not await fill_token(tab, token):
button = await tab.query('form button[type="submit"]')
async with tab.expect_navigation():
await button.click()
print(await tab.title())
asyncio.run(main())Решение в двух вкладках одновременно
Две вкладки могут решать капчу одновременно с помощью asyncio.gather. Два решения по 3 секунды, запущенные вместе, закончились вместе меньше чем за 4 секунды и с разными токенами. Ловушка в cookie. Вкладки в контексте браузера по умолчанию делят одно хранилище cookie, поэтому вторая загрузка страницы может заменить cookie сессии или CSRF, с которым первая вкладка вот-вот отправит форму. Имитация сайта ставит новый cookie сессии при каждой загрузке страницы, как это делают некоторые настоящие сайты, поэтому вторая загрузка заменила cookie первой вкладки, и одна из двух одновременных отправок была отклонена. Когда каждой вкладке дали собственный контекст браузера, это исправилось, и обе отправки приняли. Эта версия заменяет main() тем же способом и нажимает кнопку отправки, поэтому подходит для обычной формы.
async def solve_page(tab):
sitekey = await find_sitekey(tab)
token = await solve(sitekey, await tab.current_url())
await fill_token(tab, token)
button = await tab.query('form button[type="submit"]')
async with tab.expect_navigation():
await button.click()
return await tab.title()
async def main():
options = ChromiumOptions()
options.headless = True
async with Chrome(options=options) as browser:
await browser.start()
# One browser context per tab: tabs in the default context
# share cookies, and a second page load can replace the first
# tab's session or CSRF cookie.
contexts = [await browser.create_browser_context() for _ in range(2)]
tabs = [
await browser.new_tab(PAGE, browser_context_id=context)
for context in contexts
]
print(await asyncio.gather(*[solve_page(tab) for tab in tabs]))
for context in contexts:
await browser.delete_browser_context(context)
asyncio.run(main())Запуск Pydoll с CapSkip на другой машине
Pydoll и решателю не обязательно работать на одном ПК. Переключите CapSkip в режим Server в настройках подключения, и он будет слушать ваш сетевой адрес или публичный IP. Для стабильного удалённого доступа рекомендуется статический публичный IP. Тогда меняется только строка с клиентом. Решателем по-прежнему остаётся ваша собственная машина с Windows, и решения по-прежнему не тарифицируются.
# CapSkip on another Windows PC, in Server mode
solver = AsyncCapSkip(
host='192.168.1.50', # that PC's network address
port=8080,
apiKey='YOUR_API_KEY', # only if API key validation is on
)Обратите внимание на две вещи. SDK обращается к CapSkip по обычному HTTP, поэтому API-ключ передаётся открытым текстом. Держите это соединение в частной сети или VPN и включите проверку API-ключа. А ещё CapSkip открывает свой порт для всех адресов при запуске режима Server, поэтому сузить доступ можно правилом Windows Firewall, блокирующим адреса, которых вы не ожидаете. С AsyncCapSkip недоступный адрес приводит примерно через 5 секунд к NetworkException с пустым сообщением. Удалённая настройка для этого руководства не запускалась, читался только конструктор.
Таймауты и как выглядит сбой
Решайте и отправляйте быстро. Токен reCAPTCHA работает один раз и истекает, поэтому решайте после загрузки страницы и прямо перед отправкой. Строгая имитация сайта отклонила вторую отправку того же токена с кодом 403, и это правило самой имитации. Страница про срок действия токена reCAPTCHA описывает тайминги. Ниже показано, как выглядел каждый сбой с AsyncCapSkip и настройками по умолчанию на имитации:
| Случай | Что вы видите | Через |
|---|---|---|
| CapSkip не запущен | NetworkException: All connection attempts failed | 2.2 s |
| CapSkip принимает вызов и замолкает | NetworkException с пустым сообщением | 5.3 s |
| Решить капчу не получается | ApiException: ERROR_CAPTCHA_UNSOLVABLE | 0.7 s |
| Решение в 30 с при recaptchaTimeout, равном 6 | TimeoutException: timeout 6.0 exceeded | 8.7 s |
| То же решение внутри asyncio.wait_for с лимитом 4 с | Собственный TimeoutError Python (asyncio.TimeoutError в Python 3.10) | 4.0 s |
Задавайте recaptchaTimeout у клиента, а не у вызова. Аргумент timeout в вызове приводит к TypeError с сообщением got multiple values for keyword argument ‘timeout’. SDK проверяет свой лимит только между опросами, поэтому лимит в 6 секунд сработал на 8.7 секунде. Если важен жёсткий потолок, оберните вызов в asyncio.wait_for. Он отменяет только вашу сторону. CapSkip держит задание до своих собственных лимитов, и это не проверялось на настоящем приложении. У Pydoll есть свои ошибки. Навигация, которая занимает больше времени, чем разрешает go_to, вызывает PageLoadTimeout, а отказ в соединении вызывает NavigationError с net::ERR_CONNECTION_REFUSED в сообщении. В Pydoll есть и класс TimeoutException, и в CapSkip есть класс с таким же именем. Это разные классы, поэтому импортируйте модули целиком и пишите capskip.TimeoutException, когда перехватываете исключение решателя.
Частые ошибки при решении капчи в Pydoll
| Что вы видите | Причина | Исправить |
|---|---|---|
| ImportError: cannot import name ‘Chrome’ from ‘pydoll’ | Установлен Pydoll 2.x | Обновитесь до pydoll-python 3 |
| InvalidBrowserPath: No valid browser path found | Chrome установлен для одного пользователя, а не в Program Files | Задайте options.binary_location |
| FailedToStartBrowser: The browser exited with code 21 | Фиксированный user-data-dir уже используется другим браузером | Используйте другую папку профиля или не задавайте её вовсе |
| ArgumentAlreadyExistsInOptions | Один и тот же объект ChromiumOptions использован для второго браузера, или вы добавили флаг, который Pydoll добавляет сам | Создавайте новый объект options для каждого браузера |
| WaitElementTimeout при запросе sitekey | На странице пока нет виджета, или селектор ему не соответствует | Проверьте страницу в видимом окне, затем увеличьте таймаут |
| NetworkException: All connection attempts failed | CapSkip не запущен либо неверно указан порт | Запустите CapSkip и проверьте настройки подключения |
| ApiException: ERROR_CAPTCHA_UNSOLVABLE | CapSkip не смог завершить решение | Проверьте sitekey и адрес страницы, затем повторите попытку один раз. Смотрите ERROR_CAPTCHA_UNSOLVABLE |
| RuntimeError: ReferenceError, имя не определено | JavaScript в fill_token выбросил исключение внутри страницы | Запустите тот же скрипт в консоли браузера |
| Страница сообщает, что капча не пройдена, а ошибки в Python нет | Токен использовали дважды, или коллбэк не выполнился | Решайте заново прямо перед каждой отправкой |
| FailedToStartBrowser: Could not launch | options.binary_location указывает на файл, которого нет | Проверьте путь к chrome.exe |
| Каждый запуск идёт примерно на 4 секунды дольше самой работы | Каждое новое соединение с Chrome платит задержку localhost около 2 секунд | Используйте один браузер и одну вкладку: второй go_to на той же вкладке занял меньше 0.2 секунды |
FAQ
Решает ли Pydoll reCAPTCHA сам по себе?
Нет. В Pydoll 3.0.0 нет кода для reCAPTCHA. Его единственный помощник для капчи ищет чекбокс Cloudflare Turnstile и кликает по нему, а это не решение и токена не возвращает.
Работает ли это с headless Chrome?
Полный скрипт прошёл и в headless, и в headed режиме на странице-имитации, на Chrome 155 и Chrome for Testing 146. Как живой сайт относится к headless-браузеру, не проверялось, и это отдельный вопрос от токена.
Можно ли запускать Pydoll на другом ПК, а CapSkip держать дома?
Да, если этот ПК может достучаться до CapSkip. Переведите CapSkip в режим Server и задайте хост и порт в клиенте. Для стабильного удалённого доступа через интернет рекомендуется статический публичный IP. Ключ и адрес страницы передаются открытым текстом по обычному HTTP, поэтому лучше использовать VPN или частную сеть и добавить правило Windows Firewall, блокирующее другие адреса. Удалённая настройка для этого руководства не проверялась.
А как насчёт невидимой reCAPTCHA, v3 и Enterprise?
Вызов recaptcha в SDK принимает для них опции: invisible со значением 1, enterprise со значением 1 и version со значением v3. Для Pydoll страницу нужно читать иначе, например брать имя действия для v3, и ни один из этих вариантов для этого руководства не запускался. Сам вызов решателя для v3 описан в руководстве по решению reCAPTCHA v3 в Python.
Коротко
Решение капчи в Pydoll состоит из пяти частей: считать sitekey из data-sitekey или из адреса iframe, решать через AsyncCapSkip, чтобы цикл событий оставался живым, записать токен в g-recaptcha-response, запустить коллбэк страницы или отправить форму и дать каждой параллельной вкладке собственный контекст браузера. Решайте прямо перед отправкой, задавайте recaptchaTimeout у клиента и оборачивайте вызов в asyncio.wait_for как жёсткий потолок.
- Все типы капчи, которые поддерживает пакет для Python, перечислены на странице сервиса распознавания капч для Python.
- reCAPTCHA с чекбоксом в обычном скрипте на Python описана в руководстве по решению reCAPTCHA v2 в Python.
- Как работает решатель reCAPTCHA с чекбоксом, описано на странице сервиса распознавания reCAPTCHA v2.
- Похожая браузерная библиотека со своим API описана в руководстве по решению капчи в nodriver.
Обход через браузер становится медленным задолго до того, как становится дорогим, а локальный сервис распознавания капчи на вашем собственном компьютере с Windows не добавляет к решениям никакой платы.
