- 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 — внешние скрипты аналитики
90 lines
3.6 KiB
Python
90 lines
3.6 KiB
Python
"""
|
||
Проверка наличия 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)}")
|