chore: добавлена возможность передавать локатор для выбора элемента сигнализирующего о странице антибот защиты

This commit is contained in:
protokey committed 2026-09-18 16:21:07 +04:00
1 parent 6124c604d9
commit ce68d2727c
7 files changed
+36 -11

No files matched your search

+5 -1
View File
@@ -26,6 +26,7 @@ class ChallengeInfo:
provider: str # "cloudflare_turnstile", "datadome", "hcaptcha", "recaptcha_v2", ... provider: str # "cloudflare_turnstile", "datadome", "hcaptcha", "recaptcha_v2", ...
success: bool = False success: bool = False
success_locator: str | None = None # локатор на странице за защитой success_locator: str | None = None # локатор на странице за защитой
detect_locator: str | None = None # локатор на странице за защитой
site_key: str | None = None # для стадии C, чтобы передать в 2captcha site_key: str | None = None # для стадии C, чтобы передать в 2captcha
meta: dict | None = None # что-то специфичное для провайдера meta: dict | None = None # что-то специфичное для провайдера
@@ -55,6 +56,7 @@ class AntiBotProvider:
_solver_cls: Type[Solver] _solver_cls: Type[Solver]
_detector_cls: Type[Detector] _detector_cls: Type[Detector]
_success_locator: str | None = None _success_locator: str | None = None
_detect_locator: str | None = None
def __init__(self): def __init__(self):
self.detector = self._detector_cls() self.detector = self._detector_cls()
@@ -64,6 +66,7 @@ class AntiBotProvider:
stage = Stage.INTERSTITIAL, stage = Stage.INTERSTITIAL,
provider = self.provider, provider = self.provider,
success_locator = self._success_locator, success_locator = self._success_locator,
detect_locator = self._detect_locator,
) )
# обновление info # обновление info
await self.detector.detect(page, info) await self.detector.detect(page, info)
@@ -72,9 +75,10 @@ class AntiBotProvider:
async def solve(self, page: Page, info: ChallengeInfo): async def solve(self, page: Page, info: ChallengeInfo):
await self.solver.solve(page, info) await self.solver.solve(page, info)
def configure(self, success_locator=None, **kwargs): def configure(self, success_locator=None, detect_locator=None, **kwargs):
self.solver = self._solver_cls(**kwargs) self.solver = self._solver_cls(**kwargs)
self._success_locator = success_locator self._success_locator = success_locator
self._detect_locator = detect_locator
def clear(self): def clear(self):
del self.solver del self.solver
+1 -1
View File
@@ -42,7 +42,7 @@ class ReCaptchaDetector(Detector):
interactive_marker_selector = "body > div:nth-child(2) > div:nth-child(4) > iframe:nth-child(1)" interactive_marker_selector = "body > div:nth-child(2) > div:nth-child(4) > iframe:nth-child(1)"
# попытка определить что мы вообще на странице блокировки # попытка определить что мы вообще на странице блокировки
marker_selector = "#captcha-form" marker_selector = info.detect_locator or "#captcha-form"
if not await page.query_selector(marker_selector): if not await page.query_selector(marker_selector):
log.debug("Page is not a challenge form") log.debug("Page is not a challenge form")
if info.success_locator: if info.success_locator:
+10 -2
View File
@@ -25,7 +25,11 @@ class ChallengeOutcome:
async def pass_challenges( async def pass_challenges(
antibot_name, page, *, captcha=None, success_locator: str | None = None, timeout_ms=120_000) -> ChallengeOutcome: antibot_name, page, *,
captcha=None,
success_locator: str | None = None,
detect_locator: str | None = None,
timeout_ms=120_000) -> ChallengeOutcome:
# установка дедлайна # установка дедлайна
deadline = time.monotonic() + timeout_ms / 1000 deadline = time.monotonic() + timeout_ms / 1000
@@ -36,7 +40,11 @@ async def pass_challenges(
else: else:
captcha_solver = None captcha_solver = None
provider = await select_one( provider = await select_one(
page, antibot_name, captcha_solver=captcha_solver, success_locator=success_locator) page, antibot_name,
captcha_solver=captcha_solver,
success_locator=success_locator,
detect_locator=detect_locator,
)
except ProviderNotDetected: except ProviderNotDetected:
return ChallengeOutcome(False, None, None, "Provider not detected") return ChallengeOutcome(False, None, None, "Provider not detected")
+1
View File
@@ -1,2 +1,3 @@
from .hcaptcha import SolverHCaptcha from .hcaptcha import SolverHCaptcha
from .recaptcha import SolverReCaptcha
from .base import load_captcha from .base import load_captcha
+15 -6
View File
@@ -65,28 +65,37 @@ async def wait_for(page: Page, act: ActionRequest, **kwargs) -> str | None:
@register @register
async def pass_challenge(page: Page, act: ActionRequest, *, antibot_name, success_locator = None, captcha_key= None, **kwargs) -> str | None: async def pass_challenge(
assert act.selector page: Page, act: ActionRequest, *,
antibot_name, success_locator=None, captcha_key=None, detect_locator=None,
**kwargs
) -> str | None:
await pass_challenges( await pass_challenges(
antibot_name, page, antibot_name, page,
captcha=captcha_key, success_locator=act.selector captcha=captcha_key,
success_locator=act.selector or success_locator,
detect_locator=detect_locator
) )
async def execute( async def execute(
page: Page, actions: list[ActionRequest], page: Page, actions: list[ActionRequest],
captcha_key: str | None = None, antibot_name: str | None = None, captcha_key: str | None = None, antibot_name: str | None = None,
success_locator: str | None = None) -> list[dict]: success_locator: str | None = None,
detect_locator: str | None = None) -> list[dict]:
"""Выполнение очереди автоматизации""" """Выполнение очереди автоматизации"""
actions_result = [] actions_result = []
ctx = { ctx = {
"captcha_key": captcha_key, "captcha_key": captcha_key,
"antibot_name": antibot_name, "antibot_name": antibot_name,
"success_locator": success_locator "success_locator": success_locator,
"detect_locator": detect_locator,
} }
await pass_challenges( await pass_challenges(
antibot_name, page, antibot_name, page,
captcha=captcha, success_locator=success_locator captcha=captcha_key,
success_locator=success_locator,
detect_locator=detect_locator,
) )
for act in actions: for act in actions:
new_line = { new_line = {
+1
View File
@@ -32,5 +32,6 @@ class SolveRequest(BaseModel):
# TODO: screenshot: bool = False # TODO: screenshot: bool = False
captcha: str | None = None captcha: str | None = None
captcha_detect_locator: str | None = None
captcha_success_locator: str | None = None captcha_success_locator: str | None = None
captcha_type: str | None = None captcha_type: str | None = None
+3 -1
View File
@@ -130,7 +130,9 @@ async def solve(
actions_result = await actions.execute( actions_result = await actions.execute(
page, payload.actions, payload.captcha, page, payload.actions, payload.captcha,
antibot_name=payload.captcha_type, success_locator=payload.captcha_success_locator antibot_name=payload.captcha_type,
success_locator=payload.captcha_success_locator,
detect_locator=payload.captcha_detect_locator,
) )
status = not "error" in actions_result[-1] status = not "error" in actions_result[-1]