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