fix: reCAPTCHA — закрытие окна с картинками и вызов callback (#10)

- Окно с картинками закрывается кликом в угол подложки: в её центре лежит
  само окно и перехватывает клик (Locator.click падал по таймауту 60 с).
- form_submit выполняется в основном мире страницы (isolated_context=False):
  префикс "mw:" patchright не поддерживает, в изолированном мире
  ___grecaptcha_cfg не виден — "Recaptcha callback function not found".
- Воркер: LOG_LEVEL выставляется корневому логгеру явно — dramatiq настраивает
  логирование раньше, и basicConfig ничего не делал. make dev — LOG_LEVEL=DEBUG.
- test_main.http: запрос на zennolab с проверкой результата по #main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
protokeyandClaude Opus 5.5 committed 2026-10-14 01:17:06 +04:00
1 parent 4d90dc4a21
commit 0e10fb671d
5 files changed
+19 -13

No files matched your search

+2 -1
View File
@@ -33,7 +33,8 @@ export DEV_RABBITMQ_PORT DEV_REDIS_PORT DEV_POSTGRES_PORT RABBITMQ_PASSWORD POST
DEV_ENV := RABBITMQ_URL=amqp://robotex:$(RABBITMQ_PASSWORD)@localhost:$(DEV_RABBITMQ_PORT)/ \
REDIS_URL=redis://localhost:$(DEV_REDIS_PORT)/0 \
DATABASE_URL=postgresql+asyncpg://robotex:$(POSTGRES_PASSWORD)@localhost:$(DEV_POSTGRES_PORT)/robotex \
BROWSER_SLOTS=$(DEV_SLOTS)
BROWSER_SLOTS=$(DEV_SLOTS) \
LOG_LEVEL=DEBUG
help:
@echo "make install <pkg> [<pkg> ...] - установить пакет(ы) в $(VENV), обновить $(REQUIREMENTS), собрать wheel в $(WHEELS_DIR)/"
+10 -5
View File
@@ -1,4 +1,3 @@
import json
import logging
from patchright.async_api import Page
@@ -6,7 +5,7 @@ from patchright.async_api import Page
from captcha.base import Captcha
from ..base import Detector, ChallengeInfo, Solver, AntiBotProvider, click_and_race
from ..base import Stage
from ..base import Stage, PWTimeoutError
from ..registry import register
from ..exceptions import IframeLoadError
@@ -56,8 +55,8 @@ class ReCaptchaSolver(Solver):
self.captcha_solver: Captcha = self._opt["captcha_solver"]
async def form_submit(self, token: str) -> bool:
# noinspection UnnecessaryLabelJS
return await self.page.evaluate(f"mw:({JS_CHALLENGE_SUBMIT})({json.dumps(token)})")
# ___grecaptcha_cfg живёт в основном мире страницы, а patchright по умолчанию выполняет скрипт в изолированном
return await self.page.evaluate(JS_CHALLENGE_SUBMIT, token, isolated_context=False)
async def solve(self, page: Page, info: ChallengeInfo):
self.page = page
@@ -76,9 +75,15 @@ class ReCaptchaSolver(Solver):
return
else:
log.debug("Нужно решать капчу. Закрою окошко с картинками.")
# полупрозрачная подложка на весь экран (position: fixed), клик по ней закрывает окно. В её центре лежит
# само окно с картинками и перехватывает клик — поэтому кликаем в угол
element = page.locator("xpath=//div[@class='g-recaptcha-bubble-arrow']/../div[1]")
if await element.is_visible():
await element.click()
try:
await element.click(position={"x": 1, "y": 1}, timeout=5_000)
except PWTimeoutError:
# не закрылось — не страшно, токен уходит через callback
log.debug("Окошко с картинками закрыть не удалось")
log.debug("Решаем капчу...")
assert info.detect_locator
+1
View File
@@ -30,6 +30,7 @@ class Captcha:
return resp
async def turnstile(self, url, site_key):
# TODO: not finished!
resp = await self._run(self._solver.turnstile, site_key, url)
return resp
+4 -1
View File
@@ -10,10 +10,13 @@ import logging
import os
import socket
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO").upper(),
level=LOG_LEVEL,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
# под dramatiq логирование уже настроено им самим (уровень INFO) и basicConfig ничего не делает — уровень явно
logging.getLogger().setLevel(LOG_LEVEL)
import dramatiq # noqa: E402
from dramatiq.asyncio import get_event_loop_thread # noqa: E402
+2 -6
View File
@@ -147,18 +147,14 @@ X-API-Key: {{api_key}}
"url": "https://lessons.zennolab.com/captchas/recaptcha/v2_nosubmit.php?level=low",
"proxy": "{{proxy}}",
"actions": [
{
"type": "pass_challenge",
"value": "recaptcha"
},
{
"type": "inner_text",
"selector": "//button[contains(@class, 'actionItem_151') and @type='submit']"
"selector": "//div[@id='main']/div/h2"
}
],
"captcha": "{{captcha_key}}",
"captcha_type": "recaptcha",
"captcha_success_locator": "//code[contains(@class, 'hljs _successMessageCode')]"
"captcha_success_locator": "//div[@id='main']"
}
###