feature!: добавлен механизм обхода антибот защиты

This commit is contained in:
protokey committed 2026-09-17 15:49:12 +04:00
1 parent f5871710ab
commit 1d2fdeae70
17 files changed
+735 -16

No files matched your search

+81
View File
@@ -0,0 +1,81 @@
"""
абстракции: ChallengeInfo, Detector, Solver
Пользователь передает нам:
- селектор со страницы (достаточно общий чтобы покрыть весь сайт, но не страницу антибот защиты)
- ключ сервиса распознавания капчи
"""
from copy import deepcopy
from dataclasses import dataclass
from enum import Enum
from typing import Type
from playwright.async_api import Page
class Stage(str, Enum):
INTERSTITIAL = "interstitial"
CHECKBOX = "checkbox"
INTERACTIVE = "interactive"
@dataclass
class ChallengeInfo:
stage: Stage
provider: str # "cloudflare_turnstile", "datadome", "hcaptcha", "recaptcha_v2", ...
success: bool = False
success_locator: str | None = None # локатор на странице за защитой
site_key: str | None = None # для стадии C, чтобы передать в 2captcha
meta: dict | None = None # что-то специфичное для провайдера
class Solver:
"""Алгоритм прохождения. Один солвер = один антибот провайдер"""
def __init__(self, **kwargs):
self._opt = kwargs
async def solve(self, page: Page, info: ChallengeInfo):
"""Возвращает True, если стадия пройдена (или больше не требуется действий)."""
raise NotImplementedError
class Detector:
"""Один детектор = одна сигнатура одного провайдера."""
async def detect(self, page: Page, info: ChallengeInfo):
raise NotImplementedError
class AntiBotProvider:
provider: str
detector: Detector
solver: Solver
_solver_cls: Type[Solver]
_detector_cls: Type[Detector]
_success_locator: str | None = None
def __init__(self):
self.detector = self._detector_cls()
async def detect(self, page: Page) -> ChallengeInfo:
info = ChallengeInfo(
stage = Stage.INTERSTITIAL,
provider = self.provider,
success_locator = self._success_locator,
)
# обновление info
await self.detector.detect(page, info)
return info
async def solve(self, page: Page, info: ChallengeInfo):
await self.solver.solve(page, info)
def configure(self, success_locator=None, **kwargs):
self.solver = self._solver_cls(**kwargs)
self._success_locator = success_locator
def clear(self):
del self.solver
del self.detector