| """批量分析多个角色的工具脚本""" |
|
|
| import sys |
| import os |
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
| from core import TextProcessor, CharacterExtractor, CharacterAnalyzer |
| from utils import CacheManager |
| import json |
| from tqdm import tqdm |
|
|
| def batch_analyze(novel_path: str, output_dir: str = "character_profiles", |
| max_characters: int = 10): |
| """批量分析小说中的所有主要角色 |
| |
| Args: |
| novel_path: 小说文件路径 |
| output_dir: 输出目录 |
| max_characters: 最多分析的角色数 |
| """ |
| |
| print("="*70) |
| print("📚 批量角色分析工具") |
| print("="*70) |
| |
| |
| print(f"\n📖 加载小说: {novel_path}") |
| try: |
| with open(novel_path, 'r', encoding='utf-8') as f: |
| novel = f.read() |
| except: |
| print(f"❌ 无法加载文件: {novel_path}") |
| return |
| |
| print(f"✓ 已加载 {len(novel):,} 个字符") |
| |
| |
| print("\n📄 处理文本...") |
| processor = TextProcessor() |
| chunks = processor.chunk_text(novel) |
| stats = processor.get_statistics(novel) |
| |
| print(f"✓ 文本已分为 {len(chunks)} 个块") |
| print(f"✓ 检测语言: {stats['language']}") |
| |
| |
| print("\n👥 提取角色...") |
| extractor = CharacterExtractor() |
| characters = extractor.extract_main_characters( |
| chunks, |
| text_sample=novel[:3000], |
| language=stats['language'] |
| ) |
| |
| if not characters: |
| print("❌ 未找到角色") |
| return |
| |
| print(f"✓ 找到 {len(characters)} 个主要角色") |
| |
| |
| os.makedirs(output_dir, exist_ok=True) |
| |
| |
| print(f"\n🧠 开始分析角色 (最多 {max_characters} 个)...") |
| analyzer = CharacterAnalyzer() |
| |
| all_profiles = [] |
| analyze_count = min(max_characters, len(characters)) |
| |
| for i, char in enumerate(tqdm(characters[:analyze_count], desc="分析进度")): |
| char_name = char['name'] |
| |
| try: |
| |
| representative_chunks = analyzer.select_representative_chunks( |
| chunks, |
| char['info']['chunks'] |
| ) |
| |
| |
| profile = analyzer.analyze_character_batch( |
| char_name, |
| representative_chunks |
| ) |
| |
| |
| profile = analyzer.enhance_profile_with_examples( |
| profile, |
| chunks, |
| char['info']['chunks'] |
| ) |
| |
| all_profiles.append(profile) |
| |
| |
| char_filename = f"{profile['name'].replace(' ', '_').replace('/', '_')}.json" |
| char_file = os.path.join(output_dir, char_filename) |
| |
| with open(char_file, 'w', encoding='utf-8') as f: |
| json.dump(profile, f, ensure_ascii=False, indent=2) |
| |
| except Exception as e: |
| print(f"\n❌ 分析 {char_name} 失败: {e}") |
| continue |
| |
| |
| all_file = os.path.join(output_dir, "all_characters.json") |
| with open(all_file, 'w', encoding='utf-8') as f: |
| json.dump(all_profiles, f, ensure_ascii=False, indent=2) |
| |
| |
| report_file = os.path.join(output_dir, "analysis_report.txt") |
| with open(report_file, 'w', encoding='utf-8') as f: |
| f.write("="*70 + "\n") |
| f.write("角色分析报告\n") |
| f.write("="*70 + "\n\n") |
| f.write(f"小说文件: {novel_path}\n") |
| f.write(f"文本长度: {len(novel):,} 字符\n") |
| f.write(f"分析角色数: {len(all_profiles)}\n\n") |
| f.write("-"*70 + "\n\n") |
| |
| for i, profile in enumerate(all_profiles, 1): |
| f.write(f"{i}. {profile['name']}\n") |
| f.write(f" 核心特质: {', '.join(profile.get('core_traits', []))}\n") |
| f.write(f" 性格总结: {profile.get('personality_summary', 'N/A')}\n") |
| f.write("\n") |
| |
| |
| print("\n" + "="*70) |
| print("✅ 分析完成!") |
| print("="*70) |
| print(f"📁 输出目录: {output_dir}") |
| print(f"📊 分析角色数: {len(all_profiles)}") |
| print(f"📄 汇总文件: {all_file}") |
| print(f"📋 报告文件: {report_file}") |
| print("="*70) |
|
|
| def main(): |
| import argparse |
| |
| parser = argparse.ArgumentParser( |
| description="批量分析小说角色", |
| formatter_class=argparse.RawDescriptionHelpFormatter, |
| epilog=""" |
| 示例: |
| python batch_analyze.py novel.txt |
| python batch_analyze.py novel.txt -o my_characters -n 15 |
| """ |
| ) |
| |
| parser.add_argument("novel_path", help="小说文件路径") |
| parser.add_argument("-o", "--output", default="character_profiles", |
| help="输出目录 (默认: character_profiles)") |
| parser.add_argument("-n", "--num", type=int, default=10, |
| help="最多分析的角色数 (默认: 10)") |
| |
| args = parser.parse_args() |
| |
| batch_analyze(args.novel_path, args.output, args.num) |
|
|
| if __name__ == "__main__": |
| main() |