From 0e10fb671d79e2d26c25d644c0a4fd3c554d6d2b Mon Sep 17 00:00:00 2001 From: protokey Date: Wed, 14 Oct 2026 01:17:06 +0400 Subject: [PATCH] =?UTF-8?q?fix:=20reCAPTCHA=20=E2=80=94=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D0=B5=20=D0=BE=D0=BA=D0=BD=D0=B0?= =?UTF-8?q?=20=D1=81=20=D0=BA=D0=B0=D1=80=D1=82=D0=B8=D0=BD=D0=BA=D0=B0?= =?UTF-8?q?=D0=BC=D0=B8=20=D0=B8=20=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=20callba?= =?UTF-8?q?ck=20(#10)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Окно с картинками закрывается кликом в угол подложки: в её центре лежит само окно и перехватывает клик (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 --- Makefile | 3 ++- src/antibot/known_providers/recaptcha.py | 15 ++++++++++----- src/captcha/base.py | 1 + src/worker/actors.py | 5 ++++- tests/test_main.http | 8 ++------ 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index b587c1b..1bda020 100644 --- a/Makefile +++ b/Makefile @@ -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 [ ...] - установить пакет(ы) в $(VENV), обновить $(REQUIREMENTS), собрать wheel в $(WHEELS_DIR)/" diff --git a/src/antibot/known_providers/recaptcha.py b/src/antibot/known_providers/recaptcha.py index 908fe0e..c8e1519 100644 --- a/src/antibot/known_providers/recaptcha.py +++ b/src/antibot/known_providers/recaptcha.py @@ -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 diff --git a/src/captcha/base.py b/src/captcha/base.py index 3b19ed0..4704769 100644 --- a/src/captcha/base.py +++ b/src/captcha/base.py @@ -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 diff --git a/src/worker/actors.py b/src/worker/actors.py index 04f0d02..759c4b8 100644 --- a/src/worker/actors.py +++ b/src/worker/actors.py @@ -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 diff --git a/tests/test_main.http b/tests/test_main.http index d36f82b..2e7cff2 100644 --- a/tests/test_main.http +++ b/tests/test_main.http @@ -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']" } ###