""" Движок сканирования 152-ФЗ. Использует Playwright для загрузки сайтов и выполнения проверок. """ from __future__ import annotations import asyncio import uuid from datetime import datetime from typing import Any from playwright.async_api import async_playwright, Page from scanner.checks import ALL_CHECKS from scanner.models import CheckResult, CheckStatus, RiskLevel, SiteScan class ScannerEngine: """Управляет процессом сканирования сайтов.""" def __init__(self, timeout: int = 15000, headless: bool = True): self.timeout = timeout self.headless = headless async def scan_url(self, url: str, checks: list[str] | None = None) -> SiteScan: """ Просканировать один URL. Args: url: URL для сканирования checks: список имен проверок (None = все) Returns: SiteScan с результатами """ scan_id = str(uuid.uuid4())[:8] result = SiteScan(url=url, scanned_at=datetime.now()) async with async_playwright() as p: browser = await p.chromium.launch( headless=self.headless, args=[ "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu", ], ) try: context = await browser.new_context( viewport={"width": 1920, "height": 1080}, user_agent=( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ), ) page = await context.new_page() # Устанавливаем stealth (отключаем webdriver flag) await page.add_init_script(""" Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); """) # Загружаем страницу try: await page.goto(url, timeout=self.timeout, wait_until="networkidle") except Exception as e: result.checks.append( CheckResult( name="page_load", title="Загрузка страницы", status=CheckStatus.ERROR, risk_level=RiskLevel.CRITICAL, message=f"Не удалось загрузить страницу: {str(e)}", ) ) return result # Подождем, пока JavaScript отрендерит динамический контент await page.wait_for_timeout(3000) # Запускаем все проверки check_names = checks if checks else list(ALL_CHECKS.keys()) tasks = [] for check_name in check_names: check_cls = ALL_CHECKS.get(check_name) if check_cls: check_instance = check_cls() tasks.append(check_instance.check(page, url)) if tasks: check_results = await asyncio.gather(*tasks, return_exceptions=True) for cr in check_results: if isinstance(cr, CheckResult): result.checks.append(cr) elif isinstance(cr, Exception): result.checks.append( CheckResult( name="unknown", title="Неизвестная ошибка", status=CheckStatus.ERROR, risk_level=RiskLevel.CRITICAL, message=f"Необработанная ошибка: {str(cr)}", ) ) finally: await browser.close() # Вычисляем итоговые метрики result._calculate_score() return result async def scan_urls_batch(self, urls: list[str], checks: list[str] | None = None) -> list[SiteScan]: """Параллельно сканировать несколько URL.""" tasks = [self.scan_url(url, checks) for url in urls] return await asyncio.gather(*tasks) # Глобальный экземпляр для переиспользования scanner_engine = ScannerEngine()