fix: слоты, безопасность, единый формат ответа и валидация
- слоты браузеров: asyncio.Semaphore, размер из BROWSER_SLOTS, при нехватке — 503 + Retry-After; устранена утечка счётчика при отказе (#12, #49) - session_id принимается только как UUID, путь профиля проверяется (#5) - секреты клиента в логах маскируются, полный дамп — только на DEBUG (#6) - ответ: status всегда "ok"/"err", поле code (no_slots / request_failed / challenge_failed / action_failed), response_model (#7) - непройденный антибот → challenge_failed, действия не выполняются (#8) - таймауты провайдеров в мс вынесены в константы; detect переживает навигацию посреди проверки; пауза между неудачными solve вместо холостого цикла (#9) - обязательные поля действий и формат прокси проверяются схемой → 422 до запуска браузера (#13) - прокси необязателен (#15) - user_agent сохраняется в профиле сессии и реально передаётся в браузер (#16) - duration_ms у действий и у запроса (#18), опция screenshot (#19) - общий конфиг логирования (LOG_LEVEL), print → логгер, CloudflareProvider, удалён cached_persistent, GET /health (#22) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f58306cbb4
commit
437ea27be7
14 files changed
+360
-201
No files matched your search
@@ -72,8 +72,6 @@ from ..registry import register
|
||||
from ..humanize import wait_for_page_stability, wait_for_networkidle, click_human
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(logging.DEBUG)
|
||||
log.addHandler(logging.StreamHandler())
|
||||
|
||||
|
||||
class CloudflareDetector(Detector):
|
||||
@@ -185,9 +183,12 @@ __CF_PATTERN__ = re.compile(r"^https://challenges\.cloudflare\.com/cdn-cgi/chall
|
||||
|
||||
|
||||
class CloudflareSolver(Solver):
|
||||
# опрос исчезновения маркера челленджа после клика / на interstitial
|
||||
POLL_INTERVAL_MS = 250
|
||||
WAIT_CLEARED_S = 10
|
||||
|
||||
async def solve(self, page: Page, info: ChallengeInfo):
|
||||
print("start solving")
|
||||
log.debug("start solving")
|
||||
iframe = page.frame(url=__CF_PATTERN__)
|
||||
if iframe:
|
||||
await page.wait_for_timeout(randint(500, 1000))
|
||||
@@ -212,11 +213,11 @@ class CloudflareSolver(Solver):
|
||||
finally:
|
||||
return
|
||||
else:
|
||||
deadline = time.time() + 10
|
||||
deadline = time.time() + self.WAIT_CLEARED_S
|
||||
while time.time() < deadline:
|
||||
if not await page.query_selector(info.detect_locator):
|
||||
return
|
||||
await page.wait_for_timeout(1)
|
||||
await page.wait_for_timeout(self.POLL_INTERVAL_MS)
|
||||
return
|
||||
elif info.stage == Stage.INTERSTITIAL:
|
||||
if info.success_locator:
|
||||
@@ -227,11 +228,11 @@ class CloudflareSolver(Solver):
|
||||
else:
|
||||
return
|
||||
else:
|
||||
deadline = time.time() + 10
|
||||
deadline = time.time() + self.WAIT_CLEARED_S
|
||||
while time.time() < deadline:
|
||||
if not await page.query_selector(info.detect_locator):
|
||||
return
|
||||
await page.wait_for_timeout(1)
|
||||
await page.wait_for_timeout(self.POLL_INTERVAL_MS)
|
||||
return
|
||||
else:
|
||||
|
||||
@@ -257,7 +258,7 @@ class CloudflareSolver(Solver):
|
||||
|
||||
|
||||
@register
|
||||
class ReCaptchaProvider(AntiBotProvider):
|
||||
class CloudflareProvider(AntiBotProvider):
|
||||
provider = 'cloudflare'
|
||||
_solver_cls = CloudflareSolver
|
||||
_detector_cls = CloudflareDetector
|
||||
Reference in new issue
Block a user