- слоты браузеров: 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>
57 lines
1.6 KiB
Python
57 lines
1.6 KiB
Python
"""
|
|
|
|
dl.formRow:nth-child(3) > dd:nth-child(2) > div:nth-child(1)
|
|
<div data-xf-init="h-captcha" data-sitekey="83433494-1c9f-446b-85c3-e27b8b7375e1" data-invisible="">
|
|
|
|
iframe[data-hcaptcha-widget-id="0dm47zf0gn8t"]
|
|
|
|
textarea[id="g-recaptcha-response-0dm47zf0gn8t"]
|
|
textarea[id="h-captcha-response-0dm47zf0gn8t"]
|
|
|
|
|
|
"""
|
|
import asyncio
|
|
import logging
|
|
|
|
from .base import Captcha, Page
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SolverHCaptcha(Captcha):
|
|
ctype = "hcaptcha"
|
|
|
|
async def solve(self, selector, page: Page):
|
|
# проверка что элемент с капчей есть
|
|
div_container = page.locator(selector)
|
|
await div_container.wait_for()
|
|
|
|
# поиск данных и элементов
|
|
site_key = await div_container.get_attribute("data-sitekey")
|
|
is_invisible = bool(await div_container.get_attribute("data-invisible"))
|
|
|
|
iframe = div_container.locator("iframe:nth-child(1)")
|
|
await iframe.wait_for()
|
|
textarea_id_postfix = await iframe.get_attribute("data-hcaptcha-widget-id")
|
|
|
|
textarea_g = await page.query_selector(f"#g-recaptcha-response-{textarea_id_postfix}")
|
|
textarea_h = await page.query_selector(f"#h-captcha-response-{textarea_id_postfix}")
|
|
|
|
# решение
|
|
code = await self.hcaptcha(page.url, site_key, is_invisible)
|
|
|
|
# подстановка решения
|
|
await textarea_g.evaluate((
|
|
"(el, value) => {"
|
|
"el.value = value;"
|
|
"el.dispatchEvent(new Event('input', {bubbles: true}));"
|
|
"}"
|
|
), code)
|
|
await textarea_h.evaluate((
|
|
"(el, value) => {"
|
|
"el.value = value;"
|
|
"el.dispatchEvent(new Event('input', {bubbles: true}));"
|
|
"}"
|
|
), code)
|
|
return code
|