#!/usr/bin/env python3 """微信聊天记录解析器 支持的输入格式: - txt / csv(带时间戳的聊天记录文本) - html / htm(带样式的聊天记录页面) - json(结构化聊天数据) - sqlite / db(数据库文件) - 纯文本(手动复制粘贴) Usage: python3 wechat_parser.py --file --target --output [--format auto] """ import argparse import json import re import os import sys from datetime import datetime from typing import Optional from pathlib import Path def detect_format(file_path: str) -> str: """自动检测文件格式""" ext = Path(file_path).suffix.lower() if ext == '.json': return 'liuhen' # 留痕导出 elif ext == '.csv': return 'wechatmsg_csv' elif ext == '.html' or ext == '.htm': return 'wechatmsg_html' elif ext == '.db' or ext == '.sqlite': return 'pywxdump' elif ext == '.txt': # 尝试区分 WeChatMsg txt 和纯文本 with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: first_lines = f.read(2000) # WeChatMsg 格式通常有时间戳模式 if re.search(r'\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}', first_lines): return 'wechatmsg_txt' return 'plaintext' else: return 'plaintext' def parse_wechatmsg_txt(file_path: str, target_name: str) -> dict: """解析 WeChatMsg 导出的 txt 格式 典型格式: 2024-01-15 20:30:45 张三 今天好累啊 2024-01-15 20:31:02 我 怎么了? """ messages = [] current_msg = None # WeChatMsg 时间戳 + 发送者模式 msg_pattern = re.compile(r'^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+(.+)$') with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: for line in f: line = line.rstrip('\n') match = msg_pattern.match(line) if match: if current_msg: messages.append(current_msg) timestamp, sender = match.groups() current_msg = { 'timestamp': timestamp, 'sender': sender.strip(), 'content': '' } elif current_msg and line.strip(): if current_msg['content']: current_msg['content'] += '\n' current_msg['content'] += line if current_msg: messages.append(current_msg) return analyze_messages(messages, target_name) def parse_liuhen_json(file_path: str, target_name: str) -> dict: """解析留痕导出的 JSON 格式""" with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) messages = [] # 留痕格式可能有多种结构,尝试常见的 msg_list = data if isinstance(data, list) else data.get('messages', data.get('data', [])) for msg in msg_list: messages.append({ 'timestamp': msg.get('time', msg.get('timestamp', '')), 'sender': msg.get('sender', msg.get('nickname', msg.get('from', ''))), 'content': msg.get('content', msg.get('message', msg.get('text', ''))) }) return analyze_messages(messages, target_name) def parse_plaintext(file_path: str, target_name: str) -> dict: """解析纯文本粘贴的聊天记录""" with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() return { 'raw_text': content, 'target_name': target_name, 'format': 'plaintext', 'message_count': 0, 'analysis': { 'note': '纯文本格式,需要人工辅助分析' } } def analyze_messages(messages: list, target_name: str) -> dict: """分析消息列表,提取关键特征""" target_msgs = [m for m in messages if target_name in m.get('sender', '')] user_msgs = [m for m in messages if target_name not in m.get('sender', '')] # 提取口头禅(高频词分析) all_target_text = ' '.join([m['content'] for m in target_msgs if m.get('content')]) # 提取语气词 particles = re.findall(r'[哈嗯哦噢嘿唉呜啊呀吧嘛呢吗么]+', all_target_text) particle_freq = {} for p in particles: particle_freq[p] = particle_freq.get(p, 0) + 1 top_particles = sorted(particle_freq.items(), key=lambda x: -x[1])[:10] # 提取 emoji emoji_pattern = re.compile( r'[\U0001F600-\U0001F64F\U0001F300-\U0001F5FF' r'\U0001F680-\U0001F6FF\U0001F1E0-\U0001F1FF' r'\U00002702-\U000027B0\U0000FE00-\U0000FE0F' r'\U0001F900-\U0001F9FF]+', re.UNICODE ) emojis = emoji_pattern.findall(all_target_text) emoji_freq = {} for e in emojis: emoji_freq[e] = emoji_freq.get(e, 0) + 1 top_emojis = sorted(emoji_freq.items(), key=lambda x: -x[1])[:10] # 消息长度统计 msg_lengths = [len(m['content']) for m in target_msgs if m.get('content')] avg_length = sum(msg_lengths) / len(msg_lengths) if msg_lengths else 0 # 标点习惯 punctuation_counts = { '句号': all_target_text.count('。'), '感叹号': all_target_text.count('!') + all_target_text.count('!'), '问号': all_target_text.count('?') + all_target_text.count('?'), '省略号': all_target_text.count('...') + all_target_text.count('…'), '波浪号': all_target_text.count('~') + all_target_text.count('~'), } return { 'target_name': target_name, 'total_messages': len(messages), 'target_messages': len(target_msgs), 'user_messages': len(user_msgs), 'analysis': { 'top_particles': top_particles, 'top_emojis': top_emojis, 'avg_message_length': round(avg_length, 1), 'punctuation_habits': punctuation_counts, 'message_style': 'short_burst' if avg_length < 20 else 'long_form', }, 'sample_messages': [m['content'] for m in target_msgs[:50] if m.get('content')], } def main(): parser = argparse.ArgumentParser(description='微信聊天记录解析器') parser.add_argument('--file', required=True, help='输入文件路径') parser.add_argument('--target', required=True, help='前任的名字/昵称') parser.add_argument('--output', required=True, help='输出文件路径') parser.add_argument('--format', default='auto', help='文件格式 (auto/wechatmsg_txt/liuhen/pywxdump/plaintext)') args = parser.parse_args() if not os.path.exists(args.file): print(f"错误:文件不存在 {args.file}", file=sys.stderr) sys.exit(1) fmt = args.format if fmt == 'auto': fmt = detect_format(args.file) print(f"自动检测格式:{fmt}") parsers = { 'wechatmsg_txt': parse_wechatmsg_txt, 'liuhen': parse_liuhen_json, 'plaintext': parse_plaintext, } parse_func = parsers.get(fmt, parse_plaintext) result = parse_func(args.file, args.target) # 输出分析结果 os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True) with open(args.output, 'w', encoding='utf-8') as f: f.write(f"# 微信聊天记录分析 — {args.target}\n\n") f.write(f"来源文件:{args.file}\n") f.write(f"检测格式:{fmt}\n") f.write(f"总消息数:{result.get('total_messages', 'N/A')}\n") f.write(f"ta的消息数:{result.get('target_messages', 'N/A')}\n\n") analysis = result.get('analysis', {}) if analysis.get('top_particles'): f.write("## 高频语气词\n") for word, count in analysis['top_particles']: f.write(f"- {word}: {count}次\n") f.write("\n") if analysis.get('top_emojis'): f.write("## 高频 Emoji\n") for emoji, count in analysis['top_emojis']: f.write(f"- {emoji}: {count}次\n") f.write("\n") if analysis.get('punctuation_habits'): f.write("## 标点习惯\n") for punct, count in analysis['punctuation_habits'].items(): f.write(f"- {punct}: {count}次\n") f.write("\n") f.write(f"## 消息风格\n") f.write(f"- 平均消息长度:{analysis.get('avg_message_length', 'N/A')} 字\n") f.write(f"- 风格:{'短句连发型' if analysis.get('message_style') == 'short_burst' else '长段落型'}\n\n") if result.get('sample_messages'): f.write("## 消息样本(前50条)\n") for i, msg in enumerate(result['sample_messages'], 1): f.write(f"{i}. {msg}\n") print(f"分析完成,结果已写入 {args.output}") if __name__ == '__main__': main()