mod: Санитайзер оптимизирован и должен работать быстрее.

This commit is contained in:
2025-12-19 14:48:55 +03:00
parent cd1be6bf27
commit d4aa437558

View File

@@ -2,7 +2,7 @@
# Модуль для очистки и нормализации HTML-кода перед типографикой. # Модуль для очистки и нормализации HTML-кода перед типографикой.
import logging import logging
from bs4 import BeautifulSoup, NavigableString from bs4 import BeautifulSoup
from .config import (SANITIZE_ALL_HTML, SANITIZE_ETPGRF, SANITIZE_NONE, from .config import (SANITIZE_ALL_HTML, SANITIZE_ETPGRF, SANITIZE_NONE,
HANGING_PUNCTUATION_CLASSES, PROTECTED_HTML_TAGS) HANGING_PUNCTUATION_CLASSES, PROTECTED_HTML_TAGS)
@@ -24,7 +24,16 @@ class SanitizerProcessor:
if mode is False: if mode is False:
mode = SANITIZE_NONE mode = SANITIZE_NONE
self.mode = mode self.mode = mode
self._etp_classes_to_clean = frozenset(HANGING_PUNCTUATION_CLASSES.values())
# Оптимизация: заранее готовим CSS-селектор для поиска висячей пунктуации
if self.mode == SANITIZE_ETPGRF:
# Собираем уникальные классы
unique_classes = sorted(list(frozenset(HANGING_PUNCTUATION_CLASSES.values())))
# Формируем селектор вида: span.class1, span.class2, ...
# Это позволяет использовать нативный парсер (lxml) для поиска, что намного быстрее python-лямбд.
self._etp_selector = ", ".join(f"span.{cls}" for cls in unique_classes)
else:
self._etp_selector = None
logger.debug(f"SanitizerProcessor `__init__`. Mode: {self.mode}") logger.debug(f"SanitizerProcessor `__init__`. Mode: {self.mode}")
@@ -36,11 +45,11 @@ class SanitizerProcessor:
:return: Обработанный объект BeautifulSoup или строка (в режиме 'html'). :return: Обработанный объект BeautifulSoup или строка (в режиме 'html').
""" """
if self.mode == SANITIZE_ETPGRF: if self.mode == SANITIZE_ETPGRF:
# Находим все span'ы, у которых есть <span> с хотя бы одним из наших классов висячей пунктуации if not self._etp_selector:
spans_to_clean = soup.find_all( return soup
name='span',
class_=lambda c: c and any(etp_class in c.split() for etp_class in self._etp_classes_to_clean) # Используем CSS-селектор для быстрого поиска всех нужных элементов
) spans_to_clean = soup.select(self._etp_selector)
# "Агрессивная" очистка: просто "разворачиваем" все найденные теги, # "Агрессивная" очистка: просто "разворачиваем" все найденные теги,
# заменяя их своим содержимым. # заменяя их своим содержимым.
@@ -50,13 +59,18 @@ class SanitizerProcessor:
return soup return soup
elif self.mode == SANITIZE_ALL_HTML: elif self.mode == SANITIZE_ALL_HTML:
# Возвращаем только текст, удаляя все теги # Оптимизированный подход:
# При этом уважаем защищенные теги, не извлекая текст из них. # 1. Удаляем защищенные теги (script, style и т.д.) вместе с содержимым.
text_parts = [ # Используем select для поиска, так как это обычно быстрее.
str(node) for node in soup.descendants if PROTECTED_HTML_TAGS:
if isinstance(node, NavigableString) and node.parent.name not in PROTECTED_HTML_TAGS # Формируем селектор: script, style, pre, ...
] protected_selector = ", ".join(PROTECTED_HTML_TAGS)
return "".join(text_parts) for tag in soup.select(protected_selector):
tag.decompose() # Полное удаление тега из дерева
# 2. Извлекаем чистый текст из оставшегося дерева.
# get_text() работает на уровне C (в lxml) и намного быстрее ручного обхода.
return soup.get_text()
# Если режим не задан, ничего не делаем # Если режим не задан, ничего не делаем
return soup return soup