""" Точка входа для 152-ФЗ Scanner. Поддерживает запуск через CLI (один URL или CSV) и через API-сервер. """ import asyncio import csv import json import sys from pathlib import Path from scanner.engine import ScannerEngine from scanner.models import SiteScan from reports.generator import PDFReportGenerator async def scan_single(url: str, checks: list[str] | None = None) -> SiteScan: """Просканировать один URL и вывести результат.""" engine = ScannerEngine() result = await engine.scan_url(url, checks) # Выводим результаты в терминал print(f"\n{'='*60}") print(f"🔍 Сканирование: {url}") print(f"{'='*60}") print(f"📊 Балл: {result.score}/100") print(f"{'✅ Соответствует' if result.is_compliant else '❌ НЕ соответствует'} 152-ФЗ") print(f"📝 {result.summary}") print(f"\n{'─'*40}") for check in result.checks: emoji = { "pass": "✅", "fail": "❌", "skip": "⏭", "error": "⚠️", }.get(check.status.value, " ") print(f"{emoji} {check.title}: {check.message}") print(f"{'='*60}\n") return result def scan_csv(filepath: str): """Парксить CSV-файл с URL и запустить сканирование.""" csv_path = Path(filepath) if not csv_path.exists(): print(f"❌ Файл не найден: {filepath}") sys.exit(1) urls = [] with open(csv_path, "r", encoding="utf-8") as f: reader = csv.reader(f) for row in reader: if row: url = row[0].strip() if url and url.startswith(("http://", "https://")): urls.append(url) if not urls: print("❌ В CSV не найдено валидных URL") sys.exit(1) print(f"📋 Загружено {len(urls)} URL из {csv_path}") async def batch_scan(): engine = ScannerEngine() results = await engine.scan_urls_batch(urls) output = [] for r in results: output.append({ "url": r.url, "score": r.score, "is_compliant": r.is_compliant, "summary": r.summary, "checks": [c.model_dump() for c in r.checks], }) # Сохраняем JSON output_dir = Path(__file__).parent / "data" / "output" output_dir.mkdir(parents=True, exist_ok=True) json_path = output_dir / "results.json" with open(json_path, "w", encoding="utf-8") as f: json.dump(output, f, ensure_ascii=False, indent=2) print(f"\n📄 Результаты сохранены в: {json_path}") # Генерируем PDF для каждого сайта generator = PDFReportGenerator() reports_dir = output_dir / "reports" for result in output: try: pdf_path = generator.generate(result, str(reports_dir)) print(f" 📄 PDF: {pdf_path}") except Exception as e: print(f" ⚠️ Ошибка генерации PDF для {result['url']}: {e}") # Сводка compliant = sum(1 for r in output if r["is_compliant"]) non_compliant = len(output) - compliant print(f"\n📊 Сводка:") print(f" Всего сайтов: {len(output)}") print(f" ✅ Соответствуют: {compliant}") print(f" ❌ НЕ соответствуют: {non_compliant}") # Показываем топ нарушений print(f"\n🔥 Топ нарушений:") check_failures = {} for r in output: for check in r["checks"]: if check["status"] == "fail": name = check["title"] check_failures[name] = check_failures.get(name, 0) + 1 for name, count in sorted(check_failures.items(), key=lambda x: -x[1]): print(f" ❌ {name}: {count} сайтов") asyncio.run(batch_scan()) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="152-ФЗ Scanner") subparsers = parser.add_subparsers(dest="command") # Подкоманда: single single_parser = subparsers.add_parser("scan", help="Сканировать один URL") single_parser.add_argument("url", help="URL сайта для сканирования") single_parser.add_argument("--checks", nargs="+", help="Какие проверки запустить") # Подкоманда: csv csv_parser = subparsers.add_parser("csv", help="Сканировать CSV с URL") csv_parser.add_argument("filepath", help="Путь к CSV-файлу") # Подкоманда: server server_parser = subparsers.add_parser("server", help="Запустить API-сервер") server_parser.add_argument("--port", type=int, default=8000, help="Порт (по умолчанию 8000)") server_parser.add_argument("--host", default="0.0.0.0", help="Хост (по умолчанию 0.0.0.0)") args = parser.parse_args() if args.command == "scan": asyncio.run(scan_single(args.url, args.checks)) elif args.command == "csv": scan_csv(args.filepath) elif args.command == "server": import uvicorn uvicorn.run("api.server:app", host=args.host, port=args.port, reload=True) else: parser.print_help()