# src/universal_preprocessor.py """ Универсальный модуль предобработки текста. Обеспечивает стандартизацию пунктуации, замену специальных токенов и обработку сокращений для приведения текста к единому стандарту. """ import re from typing import Dict, List, Optional, Tuple from dataclasses import dataclass @dataclass class PreprocessingConfig: """Конфигурация для предобработки текста.""" replace_urls: bool = True replace_emails: bool = True replace_numbers: bool = True expand_abbreviations: bool = True normalize_punctuation: bool = True normalize_quotes: bool = True normalize_dashes: bool = True normalize_spaces: bool = True # Регулярные выражения для поиска специальных элементов RE_URL = re.compile(r'https?://\S+|www\.\S+', flags=re.I) RE_EMAIL = re.compile(r'[\w.+-]+@[\w-]+\.[\w.-]+', flags=re.I) RE_PHONE = re.compile(r'\+?[78][\s\-]?\(?\d{3}\)?[\s\-]?\d{3}[\s\-]?\d{2}[\s\-]?\d{2}') RE_NUM = re.compile(r'(? str: """Заменяет специальные элементы на унифицированные токены.""" if not text: return "" if self.config.replace_urls: text = self.patterns['url'].sub('', text) if self.config.replace_emails: text = self.patterns['email'].sub('', text) if self.config.replace_numbers: text = self.patterns['phone'].sub('', text) text = self.patterns['currency'].sub('', text) text = self.patterns['percent'].sub('', text) text = self.patterns['date'].sub('', text) text = self.patterns['number'].sub('', text) return text def expand_abbreviations(self, text: str) -> str: """Раскрывает сокращения.""" if not self.config.expand_abbreviations or not text: return text for pattern, replacement in COMMON_ABBREVIATIONS.items(): text = re.sub(pattern, replacement, text, flags=re.I) return text def normalize_punctuation(self, text: str) -> str: """Нормализует пунктуацию.""" if not text: return "" if self.config.normalize_quotes: for old, new in PUNCTUATION_MAP.items(): text = text.replace(old, new) if self.config.normalize_dashes: text = re.sub(r'[–—]', '-', text) if self.config.normalize_punctuation: # Нормализуем множественные точки text = re.sub(r'\.{3,}', '...', text) # Нормализуем множественные восклицательные знаки text = re.sub(r'!{2,}', '!!', text) # Нормализуем множественные вопросительные знаки text = re.sub(r'\?{2,}', '??', text) return text def normalize_spaces(self, text: str) -> str: """Нормализует пробелы.""" if not self.config.normalize_spaces or not text: return text # Убираем лишние пробелы text = re.sub(r'\s+', ' ', text) # Убираем пробелы перед пунктуацией text = re.sub(r'\s+([.,;:!?])', r'\1', text) # Добавляем пробел после пунктуации, если его нет text = re.sub(r'([.,;:!?])([^\s])', r'\1 \2', text) return text.strip() def preprocess(self, text: str) -> str: """ Выполняет полную предобработку текста. Args: text: Исходный текст Returns: Предобработанный текст """ if not text: return "" # Заменяем специальные токены text = self.replace_special_tokens(text) # Раскрываем сокращения text = self.expand_abbreviations(text) # Нормализуем пунктуацию text = self.normalize_punctuation(text) # Нормализуем пробелы text = self.normalize_spaces(text) return text def preprocess_corpus(self, input_path: str, output_path: str) -> int: """ Предобрабатывает корпус в формате JSONL. Args: input_path: Путь к исходному файлу output_path: Путь к выходному файлу 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'] = self.preprocess(article['text']) # Предобрабатываем заголовок if 'title' in article: article['title'] = self.preprocess(article['title']) # Записываем предобработанную статью outfile.write(json.dumps(article, ensure_ascii=False) + '\n') processed_count += 1 except json.JSONDecodeError: continue return processed_count def create_preprocessing_pipeline(config: Optional[PreprocessingConfig] = None) -> UniversalPreprocessor: """ Создает конвейер предобработки с заданной конфигурацией. Args: config: Конфигурация предобработки Returns: Настроенный предпроцессор """ return UniversalPreprocessor(config) if __name__ == "__main__": # Пример использования test_text = """ Компания ООО "Тест" (ул. Ленина, д. 1) сообщила о результатах за 2023 г. Контакты: info@test.ru, +7(495)123-45-67, сайт www.test.com Цена: 1000 руб., рост на 15% по сравнению с прошлым годом. Дата: 15.03.2024, т.е. вчера. """ # Создаем предпроцессор с настройками по умолчанию preprocessor = UniversalPreprocessor() # Предобрабатываем текст processed = preprocessor.preprocess(test_text) print("Предобработанный текст:") print(processed) # Пример с кастомной конфигурацией custom_config = PreprocessingConfig( replace_urls=True, replace_emails=True, replace_numbers=False, # Не заменяем числа expand_abbreviations=True, normalize_punctuation=True ) custom_preprocessor = UniversalPreprocessor(custom_config) custom_processed = custom_preprocessor.preprocess(test_text) print("\nС кастомной конфигурацией:") print(custom_processed)