topic_extractor.py 10.2 KB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
BroadTopicExtraction模块 - 话题提取器
基于DeepSeek直接提取关键词和生成新闻总结
"""

import sys
import json
import re
from pathlib import Path
from typing import List, Dict, Tuple
from openai import OpenAI

# 添加项目根目录到路径
project_root = Path(__file__).parent.parent
sys.path.append(str(project_root))

try:
    import config
    from config import settings
except ImportError:
    raise ImportError("无法导入settings.py配置文件")

class TopicExtractor:
    """话题提取器"""

    def __init__(self):
        """初始化话题提取器"""
        self.client = OpenAI(
            api_key=settings.MINDSPIDER_API_KEY,
            base_url=settings.MINDSPIDER_BASE_URL
        )
        self.model = settings.MINDSPIDER_MODEL_NAME
    
    def extract_keywords_and_summary(self, news_list: List[Dict], max_keywords: int = 100) -> Tuple[List[str], str]:
        """
        从新闻列表中提取关键词和生成总结
        
        Args:
            news_list: 新闻列表
            max_keywords: 最大关键词数量
            
        Returns:
            (关键词列表, 新闻分析总结)
        """
        if not news_list:
            return [], "今日暂无热点新闻"
        
        # 构建新闻摘要文本
        news_text = self._build_news_summary(news_list)
        
        # 构建提示词
        prompt = self._build_analysis_prompt(news_text, max_keywords)
        
        try:
            # 调用DeepSeek API
            response = self.client.chat.completions.create(
                model=self.model,
                messages=[
                    {"role": "system", "content": "你是一个专业的新闻分析师,擅长从热点新闻中提取关键词和撰写分析总结。"},
                    {"role": "user", "content": prompt}
                ],
                max_tokens=1500,
                temperature=0.3
            )
            
            # 解析返回结果
            result_text = response.choices[0].message.content
            keywords, summary = self._parse_analysis_result(result_text)
            
            print(f"成功提取 {len(keywords)} 个关键词并生成新闻总结")
            return keywords[:max_keywords], summary
            
        except Exception as e:
            print(f"话题提取失败: {e}")
            # 返回简单的fallback结果
            fallback_keywords = self._extract_simple_keywords(news_list)
            fallback_summary = f"今日共收集到 {len(news_list)} 条热点新闻,涵盖多个平台的热门话题。"
            return fallback_keywords[:max_keywords], fallback_summary
    
    def _build_news_summary(self, news_list: List[Dict]) -> str:
        """构建新闻摘要文本"""
        news_items = []
        
        for i, news in enumerate(news_list, 1):
            title = news.get('title', '无标题')
            source = news.get('source_platform', news.get('source', '未知'))
            
            # 清理标题中的特殊字符
            title = re.sub(r'[#@]', '', title).strip()
            
            news_items.append(f"{i}. 【{source}】{title}")
        
        return "\n".join(news_items)
    
    def _build_analysis_prompt(self, news_text: str, max_keywords: int) -> str:
        """构建分析提示词"""
        news_count = len(news_text.split('\n'))
        
        prompt = f"""
请分析以下{news_count}条今日热点新闻,完成两个任务:

新闻列表:
{news_text}

任务1:提取关键词(最多{max_keywords}个)
- 提取能代表今日热点话题的关键词
- 关键词应该适合用于社交媒体平台搜索
- 优先选择热度高、讨论量大的话题
- 避免过于宽泛或过于具体的词汇

任务2:撰写新闻分析总结(150-300字)
- 简要概括今日热点新闻的主要内容
- 指出当前社会关注的重点话题方向
- 分析这些热点反映的社会现象或趋势
- 语言简洁明了,客观中性

请严格按照以下JSON格式输出:
```json
{{
  "keywords": ["关键词1", "关键词2", "关键词3"],
  "summary": "今日新闻分析总结内容..."
}}
```

请直接输出JSON格式的结果,不要包含其他文字说明。
"""
        return prompt
    
    def _parse_analysis_result(self, result_text: str) -> Tuple[List[str], str]:
        """解析分析结果"""
        try:
            # 尝试提取JSON部分
            json_match = re.search(r'```json\s*(.*?)\s*```', result_text, re.DOTALL)
            if json_match:
                json_text = json_match.group(1)
            else:
                # 如果没有代码块,尝试直接解析
                json_text = result_text.strip()
            
            # 解析JSON
            data = json.loads(json_text)
            
            keywords = data.get('keywords', [])
            summary = data.get('summary', '')
            
            # 验证和清理关键词
            clean_keywords = []
            for keyword in keywords:
                keyword = str(keyword).strip()
                if keyword and len(keyword) > 1 and keyword not in clean_keywords:
                    clean_keywords.append(keyword)
            
            # 验证总结
            if not summary or len(summary.strip()) < 10:
                summary = "今日热点新闻涵盖多个领域,反映了当前社会的多元化关注点。"
            
            return clean_keywords, summary.strip()
            
        except json.JSONDecodeError as e:
            print(f"解析JSON失败: {e}")
            print(f"原始返回: {result_text}")
            
            # 尝试手动解析
            return self._manual_parse_result(result_text)
        
        except Exception as e:
            print(f"处理分析结果失败: {e}")
            return [], "分析结果处理失败,请稍后重试。"
    
    def _manual_parse_result(self, text: str) -> Tuple[List[str], str]:
        """手动解析结果(当JSON解析失败时的后备方案)"""
        print("尝试手动解析结果...")
        
        keywords = []
        summary = ""
        
        lines = text.split('\n')
        
        for line in lines:
            line = line.strip()
            if not line:
                continue
            
            # 寻找关键词
            if '关键词' in line or 'keywords' in line.lower():
                # 提取关键词
                keyword_match = re.findall(r'[""](.*?)["""]', line)
                if keyword_match:
                    keywords.extend(keyword_match)
                else:
                    # 尝试其他分隔符
                    parts = re.split(r'[,,、]', line)
                    for part in parts:
                        clean_part = re.sub(r'[关键词::keywords\[\]"]', '', part).strip()
                        if clean_part and len(clean_part) > 1:
                            keywords.append(clean_part)
            
            # 寻找总结
            elif '总结' in line or '分析' in line or 'summary' in line.lower():
                if ':' in line or ':' in line:
                    summary = line.split(':')[-1].split(':')[-1].strip()
            
            # 如果这一行看起来像总结内容
            elif len(line) > 50 and ('今日' in line or '热点' in line or '新闻' in line):
                if not summary:
                    summary = line
        
        # 清理关键词
        clean_keywords = []
        for keyword in keywords:
            keyword = keyword.strip()
            if keyword and len(keyword) > 1 and keyword not in clean_keywords:
                clean_keywords.append(keyword)
        
        # 如果没有找到总结,生成一个简单的
        if not summary:
            summary = "今日热点新闻内容丰富,涵盖了社会各个层面的关注点。"
        
        return clean_keywords[:max_keywords], summary
    
    def _extract_simple_keywords(self, news_list: List[Dict]) -> List[str]:
        """简单关键词提取(fallback方案)"""
        keywords = []
        
        for news in news_list:
            title = news.get('title', '')
            
            # 简单的关键词提取
            # 移除常见的无意义词汇
            title_clean = re.sub(r'[#@【】\[\]()()]', ' ', title)
            words = title_clean.split()
            
            for word in words:
                word = word.strip()
                if (len(word) > 1 and 
                    word not in ['的', '了', '在', '和', '与', '或', '但', '是', '有', '被', '将', '已', '正在'] and
                    word not in keywords):
                    keywords.append(word)
        
        return keywords[:10]
    
    def get_search_keywords(self, keywords: List[str], limit: int = 10) -> List[str]:
        """
        获取用于搜索的关键词
        
        Args:
            keywords: 关键词列表
            limit: 限制数量
            
        Returns:
            适合搜索的关键词列表
        """
        # 过滤和优化关键词
        search_keywords = []
        
        for keyword in keywords:
            keyword = str(keyword).strip()
            
            # 过滤条件
            if (len(keyword) > 1 and 
                len(keyword) < 20 and  # 不能太长
                keyword not in search_keywords and
                not keyword.isdigit() and  # 不是纯数字
                not re.match(r'^[a-zA-Z]+$', keyword)):  # 不是纯英文(除非是专有名词)
                
                search_keywords.append(keyword)
        
        return search_keywords[:limit]

if __name__ == "__main__":
    # 测试话题提取器
    extractor = TopicExtractor()
    
    # 模拟新闻数据
    test_news = [
        {"title": "AI技术发展迅速", "source_platform": "科技新闻"},
        {"title": "股市行情分析", "source_platform": "财经新闻"},
        {"title": "明星最新动态", "source_platform": "娱乐新闻"}
    ]
    
    keywords, summary = extractor.extract_keywords_and_summary(test_news)
    
    print(f"提取的关键词: {keywords}")
    print(f"新闻总结: {summary}")
    
    search_keywords = extractor.get_search_keywords(keywords)
    print(f"搜索关键词: {search_keywords}")