Files
152-fz-scanner/main.py
T
neo de18090304 🏗 Фаза 1-4: Архитектура, ядро, проверки, API, генератор отчетов
- scanner/engine.py — Playwright-движок сканирования
- scanner/models.py — Pydantic модели
- scanner/checks/ — 5 проверок (cookie, privacy, forms, server, external)
- api/server.py — FastAPI REST API
- reports/generator.py — PDF через WeasyPrint + Jinja2
- main.py — CLI + сервер

Проверки:
✅ cookie_banner — поиск cookie-баннера
✅ privacy_link — проверка ссылки на политику
✅ form_checkbox — проверка чекбоксов согласия
✅ server_location — геолокация сервера (RФ или нет)
✅ external_scripts — внешние скрипты аналитики
2026-06-09 21:10:21 +05:00

154 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Точка входа для 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()