Как решать капчи в hrequests (TLS-клиент на Python)

Решение капчи в hrequests занимает мало кода, потому что hrequests не является той частью, которая распознаёт капчу. Он отправляет запросы через Go-клиент TLS, поэтому ваше рукопожатие выглядит как настоящий Chrome или Firefox, и это проводит вас мимо проверки, которая срабатывает ещё до того, как нарисована хоть какая-то капча. Когда сайт всё же её показывает, вам нужно то, что превращает sitekey в токен. Правильно сделать стоит именно то, что происходит между ними: токен должен уйти обратно в той же сессии, которая загрузила страницу, а не в новой.
Что понадобится
- Python 3.10 или новее с установленным hrequests. Дополнение browser добавляйте только если вам действительно нужен рендеринг.
- CapSkip, запущенный на машине с Windows. Local mode, если скрипт работает на этой же машине, Server mode, если он работает где-то ещё.
- sitekey и URL страницы. На шаге 1 sitekey извлекается из страницы, а не прописывается в коде.
- Прокси, если целевому сайту важен не только ваш handshake, но и ваш IP. hrequests принимает его в виде строки URL.
# pip install capskip pip install -U hrequests capskip # Only if you need the browser. It downloads a browser build. pip install -U hrequests[all] python -m hrequests install
Две разные блокировки, два разных инструмента
Об этом стоит сказать прямо, потому что эти две вещи путают постоянно. hrequests воспроизводит TLS-отпечатки браузеров и генерирует подходящие заголовки. Он не смотрит на картинку и не создаёт токен reCAPTCHA. CapSkip создаёт токены и не трогает ваш отпечаток TLS. Блокировка ещё до того, как вам показали капчу, является проблемой рукопожатия, и руководство по отпечаткам TLS в Python и есть подробный разбор для этого случая. А показанная вам капча является темой всей остальной части этой страницы.
Одну деталь из документации hrequests стоит знать до того, как вы выберете браузер. При создании сессии принимается аргумент browser со значением firefox или chrome, и текст README расходится с его же таблицей параметров в том, какой из них вы получаете по умолчанию. Укажите значение явно, и неоднозначность исчезнет.
Шаг 1: загрузите страницу и вытащите sitekey
Используйте сессию, а не голый get, потому что именно сессия накапливает cookies и именно через неё вы будете отправлять токен обратно. В hrequests на ответе доступен быстрый парсер HTML, поэтому sitekey можно прочитать прямо из разметки, а не угадывать.
# pip install hrequests
import hrequests
SITE = "https://example.com/page-with-recaptcha"
# Name the browser. Headers are generated to match it and the OS.
session = hrequests.Session(browser="chrome", os="win")
resp = session.get(SITE)
# The parser is selectolax under the hood, so this is cheap.
widget = resp.html.find(".g-recaptcha")
sitekey = widget.attrs["data-sitekey"]
print(resp.status_code, sitekey)Если виджета нет в исходном HTML, значит его внедряет JavaScript и вам нужен рендеринг. Но прежде чем браться за него, загляните в раздел о браузере ниже, потому что большинство виджетов reCAPTCHA и Turnstile присутствуют в отдаваемой разметке, и рендеринг обойдётся вам в лишний процесс браузера просто так.
Шаг 2: решите её с помощью CapSkip
Один вызов. SDK отправляет задание и опрашивает ответ, начиная с 250 миллисекунд и постепенно увеличивая интервал, поэтому он обычно обгоняет написанный вручную цикл поверх сырого API.
# pip install capskip from capskip import CapSkip # 127.0.0.1 only if this script runs on the solver machine. solver = CapSkip(host="127.0.0.1", port=8080) result = solver.recaptcha(sitekey=sitekey, url=SITE) token = result["code"] # the g-recaptcha-response value
Любой другой тип, который поддерживает CapSkip, устроен так же. Добавьте invisible или enterprise со значением 1 либо version со значением v3 и именем action. У Turnstile и GeeTest свои методы, и именно к Turnstile стоит прочитать примечания, потому что странице с проверкой нужны два дополнительных значения и user agent, который возвращается вместе с токеном. Эти дополнительные значения описаны на страницу решателя Cloudflare Turnstile. Все параметры для каждого типа перечислены в документации CapSkip API.
Шаг 3: отправьте токен в той же сессии
Именно на этом шаге чаще всего ошибаются, и именно ради него стоит брать hrequests, а не обычный HTTP-клиент. У сессии, которая загрузила страницу, есть отпечаток TLS, сгенерированный набор заголовков и все cookies, которые выдал сайт. Отправьте токен через ту же сессию, и отправка будет выглядеть так, будто пришла от того же клиента. Отправьте его через новую сессию или, что хуже, через стандартную библиотеку, и рукопожатие сменится посреди процесса, а это уже само по себе сигнал.
# Same session object, so the fingerprint, headers and cookies
# are the ones the site already saw on the GET.
posted = session.post(
SITE,
data={
"username": "YOUR_USERNAME",
"g-recaptcha-response": token,
},
timeout=30,
)
print(posted.status_code)
session.close()Делайте это сразу. Токен reCAPTCHA действителен примерно две минуты, поэтому всё, что вклинивается между решением капчи и отправкой, тратит этот запас. Со стороны отказ выглядит как токен, отклонённый без всякой причины, и на такой случай есть отдельный подробный разбор.
Таймаут запроса в hrequests по умолчанию равен 30 секундам. Здесь это нормально, потому что он покрывает отправку формы, а не решение капчи. У решения капчи свой потолок в SDK: 120 секунд для графических капч и 300 для reCAPTCHA, Turnstile и GeeTest.
Запуск решателя на другой машине
Аргумент host выше является единственным, что меняется, когда скрипт и решатель перестают делить одну машину. У CapSkip есть два режима подключения. Local привязывается к 127.0.0.1 и обслуживает только это устройство. Server привязывается к вашему сетевому адресу или публичному IP, поэтому парсер на другой машине, на VPS или в контейнере может обратиться к той же машине с Windows через API. Оба находятся в разделе Настройки подключения, а статический публичный IP стоит завести, если вызывающая сторона находится за пределами вашей сети. Server mode меняет только то, по какому адресу слушает решатель: то же оборудование, та же машина, то же безлимитное распознавание капчи.
import os
from capskip import CapSkip
# Same script on a laptop and on a scraping box. The env var
# decides; CAPSKIP_HOST and CAPSKIP_PORT are read by the SDK too.
solver = CapSkip(
host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
port=8080,
)О прокси здесь стоит сказать отдельно, потому что hrequests и CapSkip получают их по отдельности. Прокси, который вы отдаёте hrequests, определяет, откуда идёт загрузка страницы. Прокси, который вы отдаёте решателю, определяет, откуда решается капча, и CapSkip принимает его для reCAPTCHA, Turnstile и GeeTest, но не для графических капч. Совпадение этих двух прокси важно на сайтах, которые привязывают токен к адресу, а разбор причин изложен в руководстве по ротации прокси для капчи.
Когда браузер действительно нужен и какая в нём ловушка
hrequests может передать ответ настоящему браузеру вызовом render, и привлекательность в том, что cookies переносятся в обе стороны: сессия браузера наследует cookies сессии, а закрытие страницы возвращает новые обратно. Для сценария, где нужно что-то кликнуть, это действительно полезно.
А вот та часть, которая стоит людям половины дня. Движком Firefox в hrequests служит Camoufox, запускаемый напрямую из Python-пакета Camoufox, и Camoufox выполняет скрипты страницы в изолированной области. Изолированная область может читать DOM, но не может его менять, поэтому очевидный ход, когда вы выполняете скрипт, записывающий токен в textarea ответа, не делает вообще ничего и делает это молча. hrequests предоставляет простой evaluate, который принимает скрипт и один аргумент, без всякой возможности запросить main world.
Выход в том, что hrequests передаёт дополнительные именованные аргументы прямо в Camoufox, поэтому вам доступен собственный переключатель Camoufox. Включите вычисление в main world при запуске, затем добавьте префикс к скрипту.
import hrequests
# The kwargs go through to Camoufox. Without main_world_eval the
# write below is discarded and nothing tells you.
page = hrequests.BrowserSession(headless=True, main_world_eval=True)
page.goto(SITE)
SCRIPT = (
"mw:(t) => { "
"document.getElementById('g-recaptcha-response').value = t; }"
)
# The second argument arrives as t inside the function.
page.evaluate(SCRIPT, token)
page.click("#submit")
page.close() # merges cookies back into the sessionЕсть два способа снять вопрос целиком. Используйте движок Chrome с обычной семантикой Playwright, пожертвовав ротацией отпечатков и эмуляцией человеческого курсора, которые, по словам hrequests, поддерживает только Firefox. Или поступите как на шаге 3 и не внедряйте ничего вообще: верните токен в сессию TLS и отправьте форму сами. Для формы входа или поиска это и проще, и быстрее, и именно поэтому в этом руководстве браузер стоит последним. Если вы управляете Camoufox напрямую, а не через hrequests, поведение изолированной области и прочие его последствия разобраны в руководстве по капче в Camoufox.
Решение нескольких сразу
hrequests даёт вам три способа выполнять запросы внахлёст, а CapSkip даёт один способ так же накладывать решения капч. Они сочетаются друг с другом, но это не одно и то же.
| Что вы хотите распараллелить | Какой инструмент это делает |
|---|---|
| Несколько загрузок страниц, запущенных сразу и прочитанных позже | Передайте nohup со значением true, затем прочитайте атрибут, когда он понадобится |
| Список URL за один вызов | Передайте список прямо в метод запроса |
| Много запросов с ограничением параллельности | Соберите неотправленные запросы, затем обработайте их через map с ограничением размера |
| Несколько решений капчи одновременно | Асинхронный клиент Python SDK, который является настоящей реализацией, а не псевдонимом |
Последняя строка заслуживает отдельного изучения, потому что Python является единственным SDK CapSkip, где асинхронный клиент представляет собой отдельную реализацию, а не другое имя того же класса. Схема пакетной обработки описана в руководстве по параллельному решению капчи на Python. Выбор между hrequests и массовым асинхронным клиентом для той половины работы, что отвечает за загрузку страниц, является отдельным вопросом, и руководстве по httpx и aiohttp подробно разбирает компромиссы.
Частые ошибки и что они означают
| Что вы видите | Причина | Исправить |
|---|---|---|
| NetworkException из SDK | CapSkip не запущен или недоступен с этой машины | Запустите его или переключитесь на Server mode и задайте host |
| Токен записан, но виджет остаётся нерешённым | Изолированная область Camoufox отбросила запись в DOM | Запускайте с включённым вычислением в main world и добавьте префикс к скрипту |
| MissingLibraryException при вызове render | hrequests установлен без дополнения browser | Установите дополнение, затем выполните команду установки этой библиотеки |
| Правильный токен отклоняется сайтом | Отправка ушла с другой сессии или другого клиента | Отправляйте через ту сессию, которая загрузила страницу |
| Правильный токен отклоняется после долгой паузы | Он истёк до того, как был отправлен | Решайте и отправляйте подряд, ничего не вставляя между ними |
| ERROR_GOOGLEKEY в ответе | Разобранный со страницы sitekey не совпадает с тем, что в виджете | Читайте атрибут data-sitekey, а не тег script |
| TimeoutException через 300 секунд | sitekey и URL страницы не та пара, которая стоит на этом виджете, поэтому решение капчи доходит до потолка в 300 секунд | Проверьте, что sitekey и URL страницы составляют именно ту пару, которую на самом деле использует виджет |
| Сессия браузера так и не освобождается | Страница, созданная без менеджера контекста, не была закрыта | Используйте форму with или вызывайте close в блоке finally |
FAQ
Решает ли hrequests капчи сам по себе?
Нет. Он воспроизводит TLS-отпечатки браузеров и генерирует подходящие заголовки, что снимает множество блокировок ещё до того, как вам вообще покажут капчу, и умеет эмулировать человеческие движения мыши и набор текста на отрисованной странице. Ничто из этого не распознаёт искажённую картинку и не создаёт токен reCAPTCHA. Это отдельные задачи, и для них нужен сервис распознавания капчи.
Нужно ли мне дополнение browser вообще?
Только если вам нужен рендеринг. Дополнение тянет за собой весь браузерный стек, требует потом отдельной команды установки и весит немало. Для обычного сценария, то есть загрузить страницу, прочитать sitekey, решить капчу, отправить форму, хватает обычной установки, и всё работает на одних HTTP-запросах. Добавляйте дополнение тогда, когда виджета действительно нет в отдаваемой разметке или когда форма отправляется только через настоящие клики.
Каким браузером должна представляться сессия?
Тем, который вы укажете явно. hrequests принимает firefox или chrome и генерирует соответствующие заголовки, при этом он намеренно не синхронизирует версию в заголовках с версией TLS, исходя из того, что системы обнаружения редко сопоставляют эти два признака, а дополнительный разброс выглядит как большее число разных клиентов. Для рендеринга библиотека рекомендует Firefox, потому что Chrome там не поддерживает ни ротацию отпечатков, ни эмуляцию человеческого курсора.
Может ли решатель работать не на той машине, где работает парсер?
Да, и это обычная схема, как только парсинг переезжает с ноутбука. Переведите CapSkip в Server mode, чтобы он слушал ваш сетевой адрес или публичный IP вместо адреса loopback, затем направьте на него аргумент host. Статический публичный IP рекомендуется, когда вызывающая сторона находится за пределами вашей сети. Это та же машина с Windows, выполняющая то же безлимитное распознавание капчи; меняется только адрес.
Коротко
Загружайте страницу через именованную сессию, чтобы отпечаток и cookies оставались стабильными. Читайте sitekey из ответа встроенным парсером. Решайте капчу одним вызовом SDK. Отправляйте токен обратно через ту же сессию и сразу же, а к отрисованному браузеру обращайтесь только тогда, когда виджета нет в разметке. Если вы всё же рендерите, помните, что движком Firefox служит Camoufox и что запись в DOM из изолированной области исчезает без ошибки, поэтому включайте вычисление в main world или откажитесь от внедрения совсем.
- Сам Python SDK описан на странице сервиса распознавания капч для Python.
- Капча с чекбоксом описана на странице сервиса распознавания reCAPTCHA v2.
Об одном стоит знать, прежде чем масштабировать парсер, который часто натыкается на капчи: CapSkip выполняет обход капчи на оборудовании, которое у вас уже есть, и никогда не берёт с вас плату за каждое решение, поэтому прогон, столкнувшийся с десятью капчами, и прогон, столкнувшийся с десятью тысячами, стоят ровно одинаково.
