NLP_Homework_1 / src /text_cleaner.py
Kolesnikov Dmitry
feat: Готовый проект
54ccdcb
# src/text_cleaner.py
"""
Модуль для очистки и предобработки текста.
Выполняет удаление HTML-разметки, служебных символов, рекламных блоков,
стандартизацию пробельных символов и фильтрацию стоп-слов.
"""
import re
from typing import List, Optional
from bs4 import BeautifulSoup
import nltk
from nltk.corpus import stopwords
# Загружаем русские стоп-слова
try:
RU_STOP = set(stopwords.words('russian'))
except LookupError:
nltk.download('stopwords')
RU_STOP = set(stopwords.words('russian'))
# Дополнительные стоп-слова для новостных текстов
NEWS_STOP_WORDS = {
'сообщает', 'сообщил', 'сообщила', 'сообщили', 'сообщило',
'заявил', 'заявила', 'заявили', 'заявило',
'отметил', 'отметила', 'отметили', 'отметило',
'подчеркнул', 'подчеркнула', 'подчеркнули', 'подчеркнуло',
'уточнил', 'уточнила', 'уточнили', 'уточнило',
'добавил', 'добавила', 'добавили', 'добавило',
'пояснил', 'пояснила', 'пояснили', 'пояснило',
'сказал', 'сказала', 'сказали', 'сказало',
'говорит', 'говорят', 'говорил', 'говорила',
'пишет', 'пишут', 'писал', 'писала',
'читайте', 'также', 'также', 'также',
'подробнее', 'далее', 'продолжение', 'следует'
}
RU_STOP.update(NEWS_STOP_WORDS)
def remove_html(text: str) -> str:
"""Удаляет HTML-разметку из текста."""
if not text:
return ""
soup = BeautifulSoup(text, 'html.parser')
return soup.get_text(separator=' ')
def normalize_whitespace(text: str) -> str:
"""Стандартизирует пробельные символы."""
if not text:
return ""
# Заменяем все виды пробелов на обычные
text = re.sub(r'[\s\u00A0\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+', ' ', text)
return text.strip()
def remove_nontext_chars(text: str) -> str:
"""Удаляет служебные символы, оставляя кириллицу, латиницу и пунктуацию."""
if not text:
return ""
# Оставляем буквы, цифры, пробелы и основную пунктуацию
return re.sub(r'[^\w\s\-\.,;:\?!\'"«»()—–№]', ' ', text)
def remove_stopwords_tokens(tokens: List[str]) -> List[str]:
"""Удаляет стоп-слова из списка токенов."""
if not tokens:
return []
return [t for t in tokens if t.lower() not in RU_STOP and len(t.strip()) > 0]
def remove_short_tokens(tokens: List[str], min_length: int = 2) -> List[str]:
"""Удаляет слишком короткие токены."""
if not tokens:
return []
return [t for t in tokens if len(t.strip()) >= min_length]
def remove_numeric_tokens(tokens: List[str]) -> List[str]:
"""Удаляет токены, состоящие только из цифр."""
if not tokens:
return []
return [t for t in tokens if not t.isdigit()]
def clean_text(text: str,
lower: bool = True,
remove_stopwords: bool = False,
min_token_length: int = 2,
remove_numbers: bool = False) -> str:
"""
Основная функция очистки текста.
Args:
text: Исходный текст
lower: Приводить к нижнему регистру
remove_stopwords: Удалять стоп-слова
min_token_length: Минимальная длина токена
remove_numbers: Удалять числовые токены
Returns:
Очищенный текст
"""
if not text:
return ""
# Удаляем HTML
text = remove_html(text)
# Нормализуем пробелы
text = normalize_whitespace(text)
# Приводим к нижнему регистру
if lower:
text = text.lower()
# Удаляем служебные символы
text = remove_nontext_chars(text)
# Нормализуем пробелы еще раз
text = normalize_whitespace(text)
# Если нужно удалить стоп-слова или числа, токенизируем
if remove_stopwords or remove_numbers:
tokens = text.split()
if remove_stopwords:
tokens = remove_stopwords_tokens(tokens)
if remove_numbers:
tokens = remove_numeric_tokens(tokens)
if min_token_length > 1:
tokens = remove_short_tokens(tokens, min_token_length)
text = ' '.join(tokens)
return text
def clean_corpus_jsonl(input_path: str,
output_path: str,
**clean_kwargs) -> int:
"""
Очищает корпус в формате JSONL.
Args:
input_path: Путь к исходному файлу
output_path: Путь к выходному файлу
**clean_kwargs: Параметры для clean_text
Returns:
Количество обработанных статей
"""
import json
processed_count = 0
with open(input_path, 'r', encoding='utf-8') as infile, \
open(output_path, 'w', encoding='utf-8') as outfile:
for line in infile:
line = line.strip()
if not line:
continue
try:
article = json.loads(line)
# Очищаем текст статьи
if 'text' in article:
article['text'] = clean_text(article['text'], **clean_kwargs)
# Очищаем заголовок
if 'title' in article:
article['title'] = clean_text(article['title'], **clean_kwargs)
# Записываем очищенную статью
outfile.write(json.dumps(article, ensure_ascii=False) + '\n')
processed_count += 1
except json.JSONDecodeError:
continue
return processed_count
if __name__ == "__main__":
# Пример использования
test_text = """
<p>Это <strong>тестовый</strong> текст с HTML-разметкой.</p>
<br/>Он содержит множественные пробелы и
различные символы: @#$%^&*().
"""
cleaned = clean_text(test_text, lower=True, remove_stopwords=False)
print("Очищенный текст:", cleaned)