🏗 Фаза 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 — внешние скрипты аналитики
This commit is contained in:
neo committed 2026-06-09 21:10:21 +05:00
commit de18090304
20 files changed
+1665

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
"""
Проверка наличия cookie-баннера (уведомления о cookies).
Согласно 152-ФЗ, сайт обязан получать согласие пользователя
на обработку персональных данных, включая cookies.
"""
from __future__ import annotations
import re
from playwright.async_api import Page
from scanner.checks.base import BaseCheck
from scanner.models import CheckResult, RiskLevel
class CookieBannerCheck(BaseCheck):
"""Проверяет наличие cookie-баннера на сайте."""
name = "cookie_banner"
title = "Cookie-баннер"
description = "Проверяет, отображается ли на сайте уведомление о cookie"
risk_on_fail = RiskLevel.HIGH
default_weight = 15
# Паттерны для поиска cookie-баннера
COOKIE_PATTERNS = [
r"cookie",
r"куки",
r"уведомление\s*о\s*cookie",
r"согласие\s*на\s*использование\s*cookie",
r"we\s*use\s*cookies",
r"privacy\s*policy",
r"данные\s*пользователя",
r"обработка\s*персональных\s*данных",
r"personal\s*data",
r"consent",
r"согласие",
]
async def check(self, page: Page, url: str) -> CheckResult:
try:
# Получаем весь текст страницы (включая динамически загруженный)
page_text = await page.evaluate("() => document.body.innerText")
page_html = await page.evaluate("() => document.documentElement.outerHTML")
# Ищем cookie-баннер по видимым элементам
cookie_elements = await page.query_selector_all(
'[id*="cookie"], [class*="cookie"], [id*="consent"], [class*="consent"], [id*="privacy"], [class*="privacy"], [id*="policy"], [class*="policy"]'
)
found_banner = False
banner_texts = []
for el in cookie_elements:
text = await el.inner_text()
if text:
banner_texts.append(text[:200])
# Также ищем по тексту всей страницы
has_keyword = any(
re.search(pattern, page_text, re.IGNORECASE | re.UNICODE)
for pattern in self.COOKIE_PATTERNS
)
if banner_texts:
found_banner = True
return self._pass_result(
message=f"Найден cookie-баннер ({len(banner_texts)} элементов)",
details={
"banner_count": len(banner_texts),
"sample_text": banner_texts[0][:100],
},
)
if has_keyword:
return self._pass_result(
message="На странице найдены упоминания cookie/согласия",
details={"keyword_found": True},
)
return self._fail_result(
message="Cookie-баннер не найден. Сайт собирает данные без явного согласия.",
details={
"page_text_sample": page_text[:200],
"search_patterns": self.COOKIE_PATTERNS,
},
)
except Exception as e:
return self._error_result(message=f"Ошибка при проверке cookie-баннера: {str(e)}")