#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Stage 6 独立运行脚本 从 Stage 5 结果开始,进行深度解构分析 支持指定 feature 和数量限制 """ import sys import os # 将项目根目录添加到Python路径 project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, project_root) import json import logging import argparse import webbrowser from pathlib import Path from src.analyzers.post_deconstruction_analyzer import PostDeconstructionAnalyzer from src.analyzers.similarity_analyzer import SimilarityAnalyzer import src.visualizers.deconstruction_visualizer as deconstruction_visualizer # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S', handlers=[ logging.FileHandler('deconstruction_standalone.log', encoding='utf-8'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) def main(): """主函数""" parser = argparse.ArgumentParser( description='深度解构分析(独立运行,支持流水线执行)', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=''' 基础用法示例: # 只处理"墨镜"特征的前10个高分帖子 python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10 # 处理"墨镜"和"耳环"两个特征,每个最多5个 python3 scripts/run_deconstruction.py --feature "墨镜" "耳环" --max-notes 5 # 按数据原始顺序处理前50个(不排序) python3 scripts/run_deconstruction.py --sort-by none --max-notes 50 # 处理所有特征,按时间排序,前20个 python3 scripts/run_deconstruction.py --sort-by time --max-notes 20 # 只处理"墨镜",按互动量排序,跳过前3个 python3 scripts/run_deconstruction.py --feature "墨镜" --sort-by engagement --skip 3 # 降低分数阈值,处理更多帖子 python3 scripts/run_deconstruction.py --feature "墨镜" --min-score 6.0 --max-notes 30 流水线执行示例(推荐): # 完整流水线: 深度解构 → 相似度分析 → 可视化 → 自动打开浏览器 python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10 --run-similarity --visualize # 深度解构 → 相似度分析(不生成可视化) python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10 --run-similarity # 深度解构 → 可视化(跳过相似度分析) python3 scripts/run_deconstruction.py --feature "墨镜" --max-notes 10 --visualize # 完整流水线,不自动打开浏览器 python3 scripts/run_deconstruction.py --feature "墨镜" --run-similarity --visualize --no-open # 自定义相似度分析权重 python3 scripts/run_deconstruction.py --feature "墨镜" --run-similarity --visualize \\ --similarity-weight-embedding 0.7 --similarity-weight-semantic 0.3 # 过滤低相似度特征 python3 scripts/run_deconstruction.py --feature "墨镜" --run-similarity --visualize \\ --similarity-min-similarity 0.3 配置文件示例: # 使用配置文件(支持所有参数) python3 scripts/run_deconstruction.py --config pipeline_config.json # 配置文件示例内容(pipeline_config.json): { "feature": ["墨镜"], "max_notes": 10, "timeout": 600, "run_similarity": true, "visualize": true, "similarity_weight_embedding": 0.5, "similarity_weight_semantic": 0.5 } ''' ) # 输入输出配置 parser.add_argument( '--input', default='output_v2/evaluated_results.json', help='评估结果文件路径(默认: output_v2/evaluated_results.json)' ) parser.add_argument( '--output', default='output_v2/deep_analysis_results.json', help='深度分析输出文件路径(默认: output_v2/deep_analysis_results.json)' ) # Feature 过滤(新增) parser.add_argument( '--feature', nargs='+', default=None, help='指定要处理的原始特征名称(可指定多个),如: --feature "墨镜" "耳环"。不指定则处理所有特征' ) # 过滤参数 parser.add_argument( '--min-score', type=float, default=0.8, help='最低分数阈值,只处理 >= 此分数的帖子(默认: 0.8)' ) parser.add_argument( '--skip', type=int, default=0, help='跳过前 N 个帖子(默认: 0)' ) parser.add_argument( '--max-notes', type=int, default=None, help='最多处理多少个帖子(默认: None 不限制)' ) parser.add_argument( '--sort-by', choices=['none', 'score', 'time', 'engagement'], default='score', help='排序方式: none(不排序,保持数据原始顺序), score(评分), time(时间), engagement(互动量)(默认: score)' ) # API 配置 parser.add_argument( '--api-url', default='http://192.168.245.150:7000/what/analysis/single', help='解构 API 地址(默认: http://192.168.245.150:7000/what/analysis/single)' ) parser.add_argument( '--timeout', type=int, default=800, help='API 超时时间(秒)(默认: 600,即10分钟)' ) parser.add_argument( '--max-retries', type=int, default=3, help='API 最大重试次数(默认: 3)' ) # 并发配置 parser.add_argument( '--max-workers', type=int, default=5, help='并发处理数(默认: 5)' ) # 从配置文件加载 parser.add_argument( '--config', default=None, help='从 JSON 配置文件加载参数' ) # 流水线控制参数 parser.add_argument( '--run-similarity', action='store_true', help='深度解构完成后自动运行相似度分析' ) parser.add_argument( '--visualize', action='store_true', help='生成可视化结果' ) parser.add_argument( '--open-browser', action='store_true', default=True, help='自动在浏览器中打开可视化结果(默认: True)' ) parser.add_argument( '--no-open', action='store_true', help='禁用自动打开浏览器' ) # Stage 7 输出配置 parser.add_argument( '--similarity-output', default='output_v2/similarity_analysis_results.json', help='相似度分析输出文件路径(默认: output_v2/similarity_analysis_results.json)' ) # Stage 7 相似度配置 parser.add_argument( '--similarity-weight-embedding', type=float, default=0.5, help='相似度分析向量模型权重(默认: 0.5)' ) parser.add_argument( '--similarity-weight-semantic', type=float, default=0.5, help='相似度分析 LLM 模型权重(默认: 0.5)' ) parser.add_argument( '--similarity-min-similarity', type=float, default=0.0, help='相似度分析最小相似度阈值(默认: 0.0)' ) parser.add_argument( '--similarity-max-workers', type=int, default=5, help='相似度分析最大并发数(默认: 5)' ) # 可视化输出配置 parser.add_argument( '--viz-output', default=None, help='可视化输出目录(默认: visualization/)' ) args = parser.parse_args() # 如果提供了配置文件,加载配置 if args.config: logger.info(f"从配置文件加载参数: {args.config}") with open(args.config, 'r', encoding='utf-8') as f: config = json.load(f) # 配置文件中的参数会覆盖命令行参数 for key, value in config.items(): setattr(args, key.replace('-', '_'), value) # 检查输入文件是否存在 if not os.path.exists(args.input): logger.error(f"输入文件不存在: {args.input}") return # 加载 Stage 5 结果 logger.info(f"加载评估结果: {args.input}") with open(args.input, 'r', encoding='utf-8') as f: evaluation_results = json.load(f) # 打印配置 logger.info("=" * 60) logger.info("运行配置:") logger.info(f" 输入文件: {args.input}") logger.info(f" 输出文件: {args.output}") if args.feature: logger.info(f" 指定特征: {', '.join(args.feature)}") else: logger.info(f" 指定特征: 全部") logger.info(f" API 地址: {args.api_url}") logger.info(f" 最低分数阈值: {args.min_score}") logger.info(f" 跳过前 N 个: {args.skip}") logger.info(f" 最多处理数: {args.max_notes if args.max_notes else '不限制'}") logger.info(f" 排序方式: {args.sort_by}") logger.info(f" 并发数: {args.max_workers}") logger.info(f" API 超时: {args.timeout}秒") logger.info(f" 最大重试: {args.max_retries}次") logger.info("=" * 60) # 创建分析器 analyzer = PostDeconstructionAnalyzer( api_url=args.api_url, max_workers=args.max_workers, max_notes=args.max_notes, min_score=args.min_score, skip_count=args.skip, sort_by=args.sort_by, timeout=args.timeout, max_retries=args.max_retries, output_dir=os.path.dirname(args.output) or 'output_v2', target_features=args.feature # 传递 feature 过滤参数 ) # 运行分析 try: deep_results = analyzer.run( evaluation_results=evaluation_results, output_path=args.output ) # 打印结果摘要 logger.info("\n" + "=" * 60) logger.info("深度解构分析完成!") logger.info(f" 总匹配帖子数: {deep_results['metadata']['total_matched_notes']}") logger.info(f" 实际处理数: {deep_results['metadata']['processed_notes']}") logger.info(f" 成功: {deep_results['metadata']['success_count']}") logger.info(f" 失败: {deep_results['metadata']['failed_count']}") logger.info(f" 总耗时: {deep_results['metadata']['processing_time_seconds']}秒") logger.info(f" 结果已保存: {args.output}") logger.info("=" * 60) # Stage 7: 相似度分析 similarity_results = None if args.run_similarity: logger.info("\n" + "=" * 60) logger.info("开始执行相似度分析...") logger.info("=" * 60) try: # 创建 Stage 7 分析器 similarity_analyzer = SimilarityAnalyzer( weight_embedding=args.similarity_weight_embedding, weight_semantic=args.similarity_weight_semantic, max_workers=args.similarity_max_workers, min_similarity=args.similarity_min_similarity, target_features=args.feature ) # 运行 Stage 7 分析 similarity_results = similarity_analyzer.run( deconstruction_results=deep_results, output_path=args.similarity_output ) # 打印 Stage 7 结果摘要 logger.info("\n" + "=" * 60) logger.info("相似度分析完成!") metadata = similarity_results['metadata'] overall_stats = metadata['overall_statistics'] logger.info(f" 处理帖子数: {overall_stats['total_notes']}") logger.info(f" 提取特征总数: {overall_stats['total_features_extracted']}") logger.info(f" 平均特征数/帖子: {overall_stats['avg_features_per_note']:.2f}") logger.info(f" 平均最高相似度: {overall_stats['avg_max_similarity']:.3f}") logger.info(f" 包含高相似度特征的帖子: {overall_stats['notes_with_high_similarity']}") logger.info(f" 总耗时: {metadata['processing_time_seconds']:.2f}秒") logger.info(f" 结果已保存: {args.similarity_output}") logger.info("=" * 60) # 打印 Top 5 高相似度特征示例 if similarity_results['results']: logger.info("\nTop 5 高相似度特征示例:") all_features = [] for result in similarity_results['results']: for feat in result['deconstructed_features'][:5]: all_features.append({ 'note_id': result['note_id'], 'feature_name': feat['feature_name'], 'dimension': feat['dimension'], 'similarity': feat['similarity_score'] }) # 按相似度排序,取 Top 5 all_features.sort(key=lambda x: x['similarity'], reverse=True) for i, feat in enumerate(all_features[:5], 1): logger.info(f" {i}. [{feat['note_id'][:12]}...] " f"{feat['feature_name']} ({feat['dimension']}) " f"- 相似度: {feat['similarity']:.3f}") except Exception as e: logger.error(f"相似度分析失败: {e}", exc_info=True) logger.warning("继续执行后续步骤...") # 可视化生成 viz_path = None if args.visualize: logger.info("\n" + "=" * 60) logger.info("开始生成可视化结果...") logger.info("=" * 60) try: # 准备可视化所需的数据文件路径 viz_args = [ '--evaluation-results', args.input, '--deep-analysis-results', args.output ] # 如果有 Stage 7 结果,添加到参数中 if similarity_results and args.similarity_output: viz_args.extend(['--similarity-results', args.similarity_output]) # 如果指定了可视化输出目录 if args.viz_output: viz_args.extend(['--output-dir', args.viz_output]) # 调用可视化模块 import sys original_argv = sys.argv try: sys.argv = ['deconstruction_visualizer.py'] + viz_args viz_path = deconstruction_visualizer.main() finally: sys.argv = original_argv if viz_path: logger.info("\n" + "=" * 60) logger.info("可视化生成完成!") logger.info(f" 可视化文件: {viz_path}") logger.info("=" * 60) # 自动打开浏览器 if args.open_browser and not args.no_open: logger.info("\n正在打开浏览器...") try: # 使用 Path.as_uri() 来正确处理包含中文和特殊字符的路径 file_url = Path(viz_path).resolve().as_uri() webbrowser.open(file_url) logger.info("浏览器已打开") except Exception as e: logger.warning(f"无法自动打开浏览器: {e}") logger.info(f"请手动打开: {os.path.abspath(viz_path)}") else: logger.warning("可视化生成返回了空路径") except Exception as e: logger.error(f"可视化生成失败: {e}", exc_info=True) logger.warning("跳过可视化步骤") # 流水线执行完成 logger.info("\n" + "=" * 60) logger.info("流水线执行完成!") logger.info("=" * 60) except Exception as e: logger.error(f"执行失败: {e}", exc_info=True) raise if __name__ == '__main__': main()