fix: добавление обхода для recaptcha

This commit is contained in:
protokey committed 2026-09-18 19:15:44 +04:00
1 parent b77f25d3cd
commit 4cc068a616
5 files changed
+130 -49

No files matched your search

+1
View File
@@ -0,0 +1 @@
from .known_providers import recaptcha
+35 -1
View File
@@ -6,12 +6,46 @@
- селектор со страницы (достаточно общий чтобы покрыть весь сайт, но не страницу антибот защиты)
- ключ сервиса распознавания капчи
"""
from copy import deepcopy
import asyncio
from dataclasses import dataclass
from enum import Enum
from typing import Type
from playwright.async_api import Page
from playwright.async_api import TimeoutError as PWTimeoutError
async def click_and_race(page, element, challenge_locator, timeout=30_000):
nav_task = asyncio.create_task(
page.wait_for_event(
"framenavigated",
predicate=lambda frame: frame == page.main_frame,
timeout=timeout,
)
)
challenge_task = asyncio.create_task(
challenge_locator.wait_for(state="attached", timeout=timeout)
)
await element.click()
done, pending = await asyncio.wait(
{nav_task, challenge_task}, return_when=asyncio.FIRST_COMPLETED
)
for t in pending:
t.cancel()
if nav_task in done and nav_task.exception() is None:
return "navigation"
if challenge_task in done and challenge_task.exception() is None:
return "challenge"
# оба протухли по таймауту / или упали с другой ошибкой
for t in done:
exc = t.exception()
if exc and not isinstance(exc, PWTimeoutError):
raise exc
raise PWTimeoutError("Ни навигация, ни challenge-iframe не появились вовремя")
class Stage(str, Enum):
+69 -19
View File
@@ -2,7 +2,9 @@ import logging
from playwright.async_api import Page
from ..base import Detector, ChallengeInfo, Solver, AntiBotProvider
from captcha.base import Captcha
from ..base import Detector, ChallengeInfo, Solver, AntiBotProvider, click_and_race
from ..base import Stage
from ..registry import register
from ..exceptions import IframeLoadError
@@ -14,62 +16,110 @@ log.addHandler(logging.StreamHandler())
class ReCaptchaSolver(Solver):
def __init__(self, captcha_solver) -> None:
super().__init__(captcha_solver=captcha_solver)
self.captcha_solver = self._opt["captcha_solver"]
def __init__(self, captcha_solver: Captcha, **kwargs) -> None:
super().__init__(captcha_solver=captcha_solver, **kwargs)
self.captcha_solver: Captcha = self._opt["captcha_solver"]
async def solve(self, page: Page, info: ChallengeInfo):
log.debug("Начинаю решение recaptcha антибот преграды")
if info.stage == Stage.INTERSTITIAL:
await page.wait_for_timeout(15)
return
elif info.stage == Stage.CHECKBOX:
log.debug("Нужно тыкнуть чекбокс")
checkbox_selector = "#recaptcha-anchor"
element = page.locator(checkbox_selector)
async with page.expect_response("**/recaptcha/enterprise/reload**", timeout=30_000):
await element.click()
element = page.frame_locator("iframe[title='reCAPTCHA']").locator(checkbox_selector)
interactive_marker_selector = "xpath=//div[@class='g-recaptcha-bubble-arrow'][1]"
challenge_locator = page.locator(interactive_marker_selector)
result = await click_and_race(page, element, challenge_locator)
log.debug(f"Ожидание завершено: {result}")
log.debug("Возврат к детекту")
return
else:
element = page.locator("//div[@class='g-recaptcha-bubble-arrow']/../div[1]")
log.debug("Нужно решать капчу. Закрою окошко с картинками.")
element = page.locator("xpath=//div[@class='g-recaptcha-bubble-arrow']/../div[1]")
if await element.is_visible():
await element.click()
self.captcha_solver.recaptcha(page)
# TODO: execute js
log.debug("Решаем капчу...")
assert info.detect_locator
token = await self.captcha_solver.solve(info.detect_locator, page)
log.debug("Капча решена. Возбуждаем JS")
try:
async with page.expect_navigation(wait_until='load'):
await page.evaluate("""
try {
const clients = window.___grecaptcha_cfg && window.___grecaptcha_cfg.clients;
for (const cid in clients) {
for (const key in clients[cid]) {
const obj = clients[cid][key];
for (const prop in obj) {
const cb = obj[prop] && obj[prop].callback;
if (typeof cb === 'function') { cb(token); return; }
if (typeof cb === 'string' && typeof window[cb] === 'function') {
window[cb](token); return;
}
}
}
}
} catch (e) {}""", token)
except Exception as e:
log.warning("JS navigation error", exc_info=True)
log.debug("Все готово. Проверяем еще раз.")
return
class ReCaptchaDetector(Detector):
async def detect(self, page: Page, info: ChallengeInfo):
log.debug("ReCaptchaDetector: попытка определить есть ли преграда")
info.meta = {}
checkbox_selector = "#recaptcha-anchor"
interactive_marker_selector = "#rc-imageselect"
interactive_marker_selector = "//div[@class='g-recaptcha-bubble-arrow']/..//iframe"
# попытка определить что мы вообще на странице блокировки
marker_selector = info.detect_locator or "#captcha-form"
if not await page.query_selector(marker_selector):
log.debug("Page is not a challenge form")
info.detect_locator = marker_selector
log.debug(f"ищем по detect: {marker_selector} success: {info.success_locator}")
if await page.locator(marker_selector).count() == 0:
log.debug("На странице нет маркера преграды")
if info.success_locator:
info.success = bool(await page.query_selector(info.success_locator))
info.meta["reason"] = [
"Success locator not present and marker too.",
"Marker not present but success locator is it!"
"Нет маркера что тут преграда и нет маркера что это искомая страница",
"Нет маркера что это преграда, зато есть маркер что это нужная страница"
][info.success]
else:
info.success = True
# noinspection unresolved-references
info.meta["reason"] = "Page is not a challenge form"
info.meta["reason"] = "нет маркера на это указывающего"
log.debug(f"Решение не требуется, потому что: {info.meta['reason']}")
return
log.debug("Вижу что это страница с проверкой. Определяю состояние.")
iframe = page.frame_locator("iframe[title='reCAPTCHA']")
# ожидаем когда будет загружен чекбокс
if not await page.wait_for_selector(checkbox_selector, timeout=30_000):
checkbox = iframe.locator(checkbox_selector)
try:
await checkbox.wait_for(state="attached")
except Exception:
log.debug("Checkbox not present")
# noinspection unresolved-references
info.meta["reason"] = "Checkbox not present"
raise IframeLoadError("Checkbox not present")
else:
# нужно сделать клик
log.debug("Чек бокс найден")
info.stage = Stage.CHECKBOX
if await page.wait_for_selector(interactive_marker_selector, timeout=15_000):
if await page.locator(interactive_marker_selector).count() > 0:
log.debug("Нужно решать капчу")
# нужно решение
info.stage = Stage.INTERACTIVE
else:
log.debug("Попробую просто кликнуть")
return