Files
robotex/src/antibot/known_providers/cloudflare.py
T

193 lines
7.4 KiB
Python

"""
<input type="hidden" name="cf-turnstile-response" id="cf-chl-widget-kda53_response">
<div class="footer" role="contentinfo">
<div class="footer-inner">
<div class="footer-wrapper">
<div>
<div class="ray-id">Ray ID: <code>a3ea73c2a817983b</code>
</div>
</div>
<div class="footer-link-wrapper">
<span class="footer-text">Производительность и безопасность с <a rel="noopener noreferrer" href="https://www.cloudflare.com?utm_source=challenge&amp;utm_campaign=m" target="_blank" aria-label="Cloudflare, открывается в новой вкладке">Cloudflare</a>
</span>
<span class="footer-divider"></span>
<a target="_blank" rel="noopener noreferrer" href="https://www.cloudflare.com/privacypolicy/" aria-label="Конфиденциальность, открывается в новой вкладке" class="footer-text">Конфиденциальность</a>
</div>
</div>
</div>
</div>
//input[@name="cf-turnstile-response"]
//footer/footer-inner/footer-wrapper/div[@class="ray-id"]
iFrame with checkbox
<div class="main-wrapper RUWFy0" role="main">
<div class="main-content">
<div class="lpdkQ3">
<img src="/favicon.ico" class="uMaz2" alt="Значок zkillboard.com">
<h1>zkillboard.com</h1>
</div>
<h2 id="jddkS1" class="YNaX0 MmgY4">Выполнение проверки безопасности</h2>
<p id="QeINV1" class="nnhvx2 ndZkd3">Этот веб-сайт использует сервис безопасности для защиты от вредоносных ботов. Эта страница отображается, пока веб-сайт проверяет, что вы не бот.</p>
<div id="lVJB5" style="display: grid;">
<div>
<div>
<div></div>
<input type="hidden" name="cf-turnstile-response" id="cf-chl-widget-2t1qg_response">
</div>
</div>
</div>
<div id="CJPCL0" style="display: none;">
<h2 id="zfpQF7" class="ndZkd3">Проверка выполнена успешно. Ожидание ответа zkillboard.com</h2>
</div>
<div id="EaAgR8" class="Wivzc8 aLvno9" style="display: none; visibility: hidden;">
<div class="TighH9">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<noscript>
<div class="h2">
<span id="challenge-error-text">Enable JavaScript and cookies to continue</span>
</div>
</noscript>
</div>
</div>
"""
import logging
import re
import time
from random import randint
from playwright.async_api import Page
from ..base import Detector, ChallengeInfo, AntiBotProvider, Solver
from ..base import Stage
from ..registry import register
from ..humanize import wait_for_page_stability, wait_for_networkidle
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
log.addHandler(logging.StreamHandler())
class CloudflareDetector(Detector):
# степени риска, которые Cloudflare сам проставляет в конфиге своего
# челлендж-скрипта на странице (см. `cType: '...'` в html/js) —
# по нему и определяем стадию, а не по факту наличия виджета на глаз
_CTYPES = ("non-interactive", "managed", "interactive")
async def detect(self, page: Page, info: ChallengeInfo):
log.debug("CloudflareDetector run")
await wait_for_page_stability(page, True, True)
info.meta = {}
widget_selector = "div.cf-turnstile[data-sitekey], div[data-sitekey].cf-turnstile"
marker_selector = info.detect_locator or "xpath=//input[@name='cf-turnstile-response']"
info.detect_locator = marker_selector
log.debug(f"ищем {marker_selector} успех если есть {info.success_locator}")
if not await page.query_selector(marker_selector):
log.debug("на странице нет маркера преграды")
if info.success_locator:
info.success = bool(await page.query_selector(info.success_locator))
info.meta["reason"] = [
"Нет маркера блокировки и нет маркера успеха :-(",
"Нет маркера блокировки зато есть маркер успеха :-)"
][info.success]
else:
info.success = True
info.meta["reason"] = "нет маркера блокировки"
log.debug("Решение не требуется")
return
content = await page.content()
cf_type = next((t for t in self._CTYPES if f"cType: '{t}'" in content), None)
info.meta["cf_type"] = cf_type
log.debug(f"cType со страницы: {cf_type!r}")
widget = await page.query_selector(widget_selector)
if cf_type == "non-interactive":
# фулл-пейдж интерстишл без чекбокса — просто ждать, пока пропадёт
# или эскалирует в managed/interactive (тогда cf_type сменится на
# следующем detect())
info.stage = Stage.INTERSTITIAL
elif cf_type in ("managed", "interactive"):
# CF уже решил, что нужен клик по виджету внутри своего iframe
# (challenges.cloudflare.com) — сам клик этот проект не решает
info.stage = Stage.CHECKBOX
info.meta["requires_click"] = True
elif widget:
# обычный встроенный виджет на странице (без cType, просто
# <div class="cf-turnstile" data-sitekey="...">) — решаем через
# captcha_solver по токену, см. CloudflareSolver
info.stage = Stage.CHECKBOX
info.meta["container_selector"] = widget_selector
else:
info.stage = Stage.INTERSTITIAL
__CF_PATTERN__ = re.compile(r"^https://challenges\.cloudflare\.com/cdn-cgi/challenge-platform/.*")
class CloudflareSolver(Solver):
async def solve(self, page: Page, info: ChallengeInfo):
if info.stage == Stage.CHECKBOX:
# # Calculate the Captcha coordinates for any viewport
box = await page.locator("xpath=//input[@name='cf-turnstile-response']/../div").bounding_box()
captcha_x, captcha_y = box["x"] + randint(26, 28), box["y"] + randint(25, 27)
# # TODO: не кликает!
await page.mouse.click(captcha_x, captcha_y, delay=randint(100, 200), button="left")
await wait_for_networkidle(page)
if info.success_locator:
try:
await page.wait_for_selector(info.success_locator)
except:
pass
finally:
return
else:
deadline = time.time() + 10
while time.time() < deadline:
if not await page.query_selector(info.detect_locator):
return
return
elif info.stage == Stage.INTERSTITIAL:
if info.success_locator:
try:
await page.wait_for_selector(info.success_locator)
except Exception:
return
else:
return
else:
deadline = time.time() + 10
while time.time() < deadline:
if not await page.query_selector(info.detect_locator):
return
return
else:
captcha_solver = self._opt.get("captcha_solver")
if not captcha_solver:
raise RuntimeError("Нужен captcha_solver (2captcha) чтобы решить Turnstile")
await captcha_solver.solve(info.meta["container_selector"], page)
# TODO: возбудить js
@register
class ReCaptchaProvider(AntiBotProvider):
provider = 'cloudflare'
_solver_cls = CloudflareSolver
_detector_cls = CloudflareDetector