fix: reCAPTCHA — окно с картинками определяется по положению (#10)

- Маркер интерактивного челленджа: iframe bframe, контейнер которого не стоит
  за экраном (top: -10000px); click_and_race ждёт видимости, а не появления в DOM.
- Маркер наличия капчи берётся из исходного HTML (api.js / enterprise.js,
  .g-recaptcha, iframe чекбокса), проверка мгновенная, без ожидания iframe.
- SolverReCaptcha ждёт detect_locator в DOM, а не видимости (script невидим).
- test_main.http: секреты вынесены в http-client.private.env.json, запрос
  на стенд zennolab, исправлены XPath с @class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
protokeyandClaude Opus 5.5 committed 2026-10-14 00:30:29 +04:00
1 parent fdee12e2d8
commit 4d90dc4a21
7 files changed
+67 -19

No files matched your search

+3 -1
View File
@@ -23,8 +23,10 @@ async def click_and_race(page, element, challenge_locator, timeout=30_000):
timeout=timeout,
)
)
# challenge_locator должен совпадать только с открытым окном челленджа: элементы челленджа могут быть в DOM
# заранее (reCAPTCHA держит их за экраном), поэтому ждём видимости, а не появления
challenge_task = asyncio.create_task(
challenge_locator.wait_for(state="attached", timeout=timeout)
challenge_locator.first.wait_for(state="visible", timeout=timeout)
)
await element.click()
+18 -7
View File
@@ -12,6 +12,20 @@ from ..exceptions import IframeLoadError
log = logging.getLogger(__name__)
# iframe окна с картинками (bframe) вставляется в DOM вместе с чекбоксом, но его контейнер стоит за экраном:
# style="visibility: hidden; ... top: -10000px". Когда выпадает челлендж, контейнер переезжает на экран и
# становится видимым — локатор совпадает только с открытым окном
INTERACTIVE_MARKER_SELECTOR = (
"xpath=//iframe[contains(@src, '/bframe')][not(ancestor::div[contains(@style, 'top: -10000px')])]"
)
# признак reCAPTCHA, который есть уже в исходном HTML, до загрузки iframe с чекбоксом (он грузится долго):
# скрипт api.js / enterprise.js, контейнер .g-recaptcha (у render=explicit его может не быть) или сам iframe.
# #recaptcha-token не подходит — он внутри iframe чекбокса, с основной страницы его не видно
DETECT_MARKER_SELECTOR = (
"script[src*='/recaptcha/api.js'], script[src*='/recaptcha/enterprise.js'], "
".g-recaptcha, iframe[title='reCAPTCHA']"
)
JS_CHALLENGE_SUBMIT = """(token) => {
const clients = window.___grecaptcha_cfg && window.___grecaptcha_cfg.clients;
@@ -55,9 +69,7 @@ class ReCaptchaSolver(Solver):
log.debug("Нужно тыкнуть чекбокс")
checkbox_selector = "#recaptcha-anchor"
element = page.frame_locator("iframe[title='reCAPTCHA']").locator(checkbox_selector)
# TODO: маркер всегда присутствует
interactive_marker_selector = "//div[@class='g-recaptcha-bubble-arrow']/..//iframe"
challenge_locator = page.locator(interactive_marker_selector)
challenge_locator = page.locator(INTERACTIVE_MARKER_SELECTOR)
result = await click_and_race(page, element, challenge_locator)
log.debug(f"Ожидание завершено: {result}")
log.debug("Возврат к детекту")
@@ -87,10 +99,9 @@ class ReCaptchaDetector(Detector):
log.debug("ReCaptchaDetector: попытка определить есть ли преграда")
info.meta = {}
checkbox_selector = "#recaptcha-anchor"
interactive_marker_selector = "//div[@class='g-recaptcha-bubble-arrow']/..//iframe"
# попытка определить что мы вообще на странице блокировки
marker_selector = info.detect_locator or "#captcha-form"
marker_selector = info.detect_locator or DETECT_MARKER_SELECTOR
info.detect_locator = marker_selector
log.debug(f"ищем по detect: {marker_selector} success: {info.success_locator}")
@@ -111,7 +122,7 @@ class ReCaptchaDetector(Detector):
log.debug("Вижу что это страница с проверкой. Определяю состояние.")
iframe = page.frame_locator("iframe[title='reCAPTCHA']")
# ожидаем когда будет загружен чекбокс
# маркер есть — значит капча будет, ждём пока догрузится iframe с чекбоксом
info.stage = Stage.INTERSTITIAL
checkbox = iframe.locator(checkbox_selector)
@@ -126,7 +137,7 @@ class ReCaptchaDetector(Detector):
log.debug("Чек бокс найден")
info.stage = Stage.CHECKBOX
if await page.locator(interactive_marker_selector).count() > 0:
if await page.locator(INTERACTIVE_MARKER_SELECTOR).first.is_visible():
log.debug("Нужно решать капчу")
# нужно решение
info.stage = Stage.INTERACTIVE
+2 -2
View File
@@ -25,8 +25,8 @@ class SolverReCaptcha(Captcha):
:param page:
:return:
"""
div_container = page.locator(selector)
await div_container.wait_for()
# selector может указывать на невидимый элемент (script) и совпадать с несколькими
await page.locator(selector).first.wait_for(state="attached")
site_key_input_element = page.locator("iframe[title='reCAPTCHA']")
src_value = await site_key_input_element.get_attribute("src")