Files
wiki/skills/telegram-collector/scripts/topic_extractor.py
2026-04-12 21:55:33 +03:00

255 lines
10 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Утилита для автоматического извлечения потенциальных тем и ключевых слов
из собранных сырых данных Telegram
"""
import os
import sys
import json
import glob
import re
from collections import Counter
import nltk
from pathlib import Path
import logging
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler("topic_extractor.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Директории
INPUT_DIR = os.getenv('OUTPUT_DIR', 'data')
RAW_DIR = f"{INPUT_DIR}/raw"
STOPWORDS_FILE = "stopwords.txt"
def download_nltk_data():
"""Скачивает необходимые данные для NLTK"""
try:
nltk.data.find('tokenizers/punkt')
except LookupError:
nltk.download('punkt')
try:
nltk.data.find('corpora/stopwords')
except LookupError:
nltk.download('stopwords')
def load_stopwords():
"""Загружает стоп-слова из файла или использует встроенные"""
try:
if os.path.exists(STOPWORDS_FILE):
with open(STOPWORDS_FILE, 'r', encoding='utf-8') as f:
return set(line.strip().lower() for line in f if line.strip())
else:
# Если файл не существует, используем стандартные стоп-слова NLTK
from nltk.corpus import stopwords
return set(stopwords.words('russian') + stopwords.words('english'))
except Exception as e:
logger.error(f"Ошибка при загрузке стоп-слов: {e}")
return set()
def preprocess_text(text):
"""Предобработка текста: удаление спецсимволов, приведение к нижнему регистру"""
# Удаляем ссылки
text = re.sub(r'https?://\S+', '', text)
# Удаляем упоминания
text = re.sub(r'@\w+', '', text)
# Удаляем хештеги
text = re.sub(r'#\w+', '', text)
# Удаляем спецсимволы и цифры
text = re.sub(r'[^\w\s]', ' ', text)
text = re.sub(r'\d+', ' ', text)
# Приводим к нижнему регистру
text = text.lower()
# Удаляем лишние пробелы
text = re.sub(r'\s+', ' ', text).strip()
return text
def extract_keywords(texts, stopwords, min_length=4, max_length=20, top_n=30):
"""Извлекает ключевые слова из текстов"""
# Объединяем все тексты в один
all_text = ' '.join(texts)
# Токенизируем
tokens = nltk.word_tokenize(all_text)
# Фильтруем стоп-слова и короткие слова
filtered_tokens = [token for token in tokens
if token.lower() not in stopwords
and min_length <= len(token) <= max_length]
# Считаем частоту слов
word_counts = Counter(filtered_tokens)
# Возвращаем top_n самых частых слов
return [word for word, _ in word_counts.most_common(top_n)]
def extract_bigrams(texts, stopwords, min_length=4, top_n=20):
"""Извлекает биграммы из текстов"""
all_tokens = []
for text in texts:
tokens = nltk.word_tokenize(text)
# Фильтруем стоп-слова и короткие слова
filtered_tokens = [token.lower() for token in tokens
if token.lower() not in stopwords
and len(token) >= min_length]
all_tokens.extend(filtered_tokens)
# Создаем биграммы
bigrams = list(nltk.bigrams(all_tokens))
# Считаем частоту биграмм
bigram_counts = Counter(bigrams)
# Возвращаем top_n самых частых биграмм в виде строк
return [f"{w1} {w2}" for (w1, w2), _ in bigram_counts.most_common(top_n)]
def extract_topics_from_raw_data(min_messages=10):
"""Извлекает потенциальные темы из собранных сырых данных"""
# Ищем все .json файлы в директории с сырыми данными
raw_files = glob.glob(f"{RAW_DIR}/*.json")
if not raw_files:
logger.warning("Нет файлов для анализа")
return None
# Загружаем стоп-слова
stopwords = load_stopwords()
# Собираем все тексты
all_texts = []
for file in raw_files:
try:
with open(file, 'r', encoding='utf-8') as f:
data = json.load(f)
for msg in data["messages"]:
if msg["text"]:
preprocessed = preprocess_text(msg["text"])
if preprocessed: # Проверяем, что текст не пустой после предобработки
all_texts.append(preprocessed)
except Exception as e:
logger.error(f"Ошибка при чтении файла {file}: {e}")
if len(all_texts) < min_messages:
logger.warning(f"Недостаточно сообщений для анализа: {len(all_texts)} < {min_messages}")
return None
# Извлекаем ключевые слова
keywords = extract_keywords(all_texts, stopwords)
# Извлекаем биграммы
bigrams = extract_bigrams(all_texts, stopwords)
# Формируем предложения тем на основе биграмм и популярных слов
suggested_topics = []
for bigram in bigrams[:10]: # Берем 10 лучших биграмм
if bigram not in suggested_topics:
suggested_topics.append(bigram)
# Добавляем популярные ключевые слова, которые еще не в списке
for keyword in keywords[:15]: # Берем 15 лучших ключевых слов
if keyword not in suggested_topics:
suggested_topics.append(keyword)
# Группируем слова по общей основе (очень упрощенный подход)
grouped_topics = {}
for topic in suggested_topics:
# Используем первые 5 символов как приблизительную основу слова
if len(topic) >= 5:
root = topic[:5].lower()
if root not in grouped_topics:
grouped_topics[root] = []
grouped_topics[root].append(topic)
# Формируем финальный список тем и ключевых слов
final_topics = {}
for root, words in grouped_topics.items():
# Выбираем самое длинное слово как название темы
topic_name = max(words, key=len)
# Остальные слова используем как ключевые слова
topic_keywords = [word for word in words if word != topic_name]
# Добавляем дополнительные ключевые слова из общего списка
for keyword in keywords:
if keyword.startswith(root[:3]) and keyword not in topic_keywords and keyword != topic_name:
topic_keywords.append(keyword)
final_topics[topic_name] = topic_keywords[:10] # Ограничиваем 10 ключевыми словами
return final_topics
def save_suggested_topics(topics):
"""Сохраняет предложенные темы в файл"""
if not topics:
logger.warning("Нет тем для сохранения")
return False
try:
suggested_file = "suggested_topics.json"
with open(suggested_file, 'w', encoding='utf-8') as f:
json.dump(topics, f, indent=2, ensure_ascii=False)
logger.info(f"Предложенные темы сохранены в {suggested_file}")
# Также создаем более читаемую версию в Markdown
md_file = "suggested_topics.md"
with open(md_file, 'w', encoding='utf-8') as f:
f.write("# Предложенные темы и ключевые слова\n\n")
for topic, keywords in topics.items():
f.write(f"## {topic.capitalize()}\n\n")
f.write("Ключевые слова:\n")
for keyword in keywords:
f.write(f"- {keyword}\n")
f.write("\n")
return True
except Exception as e:
logger.error(f"Ошибка при сохранении предложенных тем: {e}")
return False
def main():
"""Основная функция для запуска экстрактора тем"""
try:
download_nltk_data()
logger.info("Извлечение тем из собранных данных...")
topics = extract_topics_from_raw_data()
if topics:
save_suggested_topics(topics)
# Выводим результаты в консоль
print("\nПредложенные темы и ключевые слова:")
for topic, keywords in topics.items():
print(f"\n{topic.capitalize()}:")
print("Ключевые слова:")
for keyword in keywords:
print(f"- {keyword}")
else:
print("Не удалось извлечь темы из данных. Возможно, недостаточно сообщений.")
except Exception as e:
logger.error(f"Ошибка: {e}")
sys.exit(1)
if __name__ == "__main__":
main()