微信聊天记录里藏着大量有价值的信息但它的存储格式一直是个让人头疼的问题。PC端微信用的是加密的SQLite数据库手机端导出的又是各种奇奇怪怪的dat文件想把这些数据拿出来做点事情比如喂给AI做分析、导入笔记软件做知识管理中间隔着一道不低的墙。最近我在折腾一个开源方案把微信聊天记录解密、清洗、格式化之后直接对接Codex和Obsidian整个过程跑通之后确实省了不少事。这篇文章就把我踩过的坑、试过的方案、最终跑通的流程完整记录下来适合有Python基础、想把自己的聊天数据用起来的读者参考。1. 微信聊天记录到底存在哪里格式是什么样的1.1 PC端微信的数据库结构PC端微信4.x版本的数据默认存放在Documents\WeChat Files\目录下每个登录过的账号会有一个以wxid开头的文件夹。进去之后核心目录是Msg里面按月份分文件夹存放MSG0.db、MSG1.db这样的SQLite数据库文件。这些db文件是加密的直接用SQLite工具打开会提示file is not a database。加密方式用的是SQLCipher密钥派生自微信账号相关的信息。不同版本的微信密钥的生成算法有差异这也是为什么网上能找到的解密工具往往只针对特定版本有效。我实测下来4.x版本的密钥获取需要从微信进程的内存中读取这一步是整个流程里最关键的环节。除了Msg目录还有FileStorage目录存放图片、视频、文件等附件Contact目录存放联系人信息。聊天记录的主体在db文件里附件是单独存储的通过消息里的路径字段关联。1.2 手机端导出的dat文件是什么手机端微信的聊天记录导出后常见的是.dat文件。这个dat不是标准格式而是微信自己的一套封装。文件头通常有固定的magic number后面跟着加密或压缩的数据。网上有一些微信dat文件查看器工具原理就是识别文件头、做对应的解码。dat文件里可能是图片微信对图片做了异或加密、可能是语音silk编码、也可能是文本消息的备份。不同来源的dat文件结构不一样有的是从手机备份里提取的有的是通过迁移聊天记录功能生成的。处理之前得先判断这个dat到底装的是什么。1.3 为什么不能直接读核心障碍有两个一是加密SQLCipher的加密不是简单异或没有密钥基本不可能暴力破解二是格式不统一PC端是db手机端是dat还有各种备份格式没有一套通用的解析方案。我试过直接用Python的sqlite3库去打开PC端的db文件报错信息很明确——不是有效的数据库文件。换成pysqlcipher3需要提供密钥但密钥从哪来又是个问题。后来找到的思路是从运行中的微信进程里读密钥这个方案在Windows上可行Linux和macOS上要复杂一些。2. 解密环节从进程内存里把密钥捞出来2.1 密钥获取的基本原理微信在启动时会根据账号信息生成一个密钥用于加解密本地的SQLite数据库。这个密钥在微信运行期间会存在于进程的内存空间中。我们的目标就是把这个密钥从内存里读出来。具体做法是找到微信进程读取它的内存然后根据密钥的特征比如长度、字符集、在内存中的位置规律来定位。不同版本的微信密钥的特征不一样需要针对性地调整搜索策略。我用的方案是参考了一个开源项目的思路先枚举微信进程的内存区域然后在这些区域里搜索符合SQLCipher密钥特征的字节序列。SQLCipher的密钥通常是32字节的十六进制字符串或者passphrase在内存里会以特定的形式出现。2.2 实际操作步骤在Windows上我用的工具组合是Python pymem库。pymem可以枚举进程、读取内存比直接调Windows API方便很多。import pymem import re def find_wechat_key(process_nameWeChat.exe): pm pymem.Pymem(process_name) # 枚举内存区域 for region in pm.list_memory_regions(): try: data pm.read_bytes(region.BaseAddress, region.RegionSize) except Exception: continue # 搜索符合密钥特征的模式 # 这里的具体模式需要根据微信版本调整 matches re.findall(rb[0-9a-f]{64}, data) for m in matches: # 进一步验证这个key能不能解密db if validate_key(m): return m return None这段代码是简化版实际用的时候要注意几点内存区域可能很大全量读取会消耗大量内存和时间正则匹配到的候选key可能有多个需要逐个验证微信版本更新后密钥的特征可能变化正则要跟着调。验证key的方法是拿它去尝试解密db文件如果能成功读出SQLite的头部SQLite format 3说明key是对的。from pysqlcipher3 import dbapi2 as sqlcipher def validate_key(key): try: conn sqlcipher.connect(path/to/MSG0.db) conn.execute(fPRAGMA key{key.decode()}) conn.execute(SELECT count(*) FROM sqlite_master) return True except Exception: return False2.3 踩坑记录第一个坑是权限问题。读取其他进程的内存需要管理员权限普通用户运行会报Access Denied。解决办法是以管理员身份运行Python脚本。第二个坑是微信版本更新。我一开始用的方案是针对某个特定版本的微信自动更新之后密钥特征变了脚本就失效了。后来改成更通用的搜索策略不依赖固定的偏移量而是靠特征匹配稳定性好了很多。第三个坑是内存读取的性能。微信进程的内存可能有几个GB全量读取一次要几十秒。优化方法是先过滤掉明显不可能包含密钥的区域比如只读的代码段只扫描可读写的数据段速度能快不少。注意读取进程内存这个操作本身是合法的技术手段但只应该用于处理你自己的数据。不要用来获取他人的聊天记录这是底线。3. 数据清洗从原始db到结构化消息3.1 理解微信的消息表结构解密后的db文件里核心表是MSG字段包括localId、TalkerId、MsgSvrID、Type、SubType、IsSender、CreateTime、StrContent、StrTalker等。Type字段标识消息类型1是文本3是图片34是语音43是视频49是各种应用消息链接、文件、引用等。StrContent对于文本消息是直接的内容对于其他类型可能是XML或JSON格式的元数据。比如图片消息的StrContent里会有图片的路径、md5等信息。TalkerId关联到Contact表可以拿到聊天对象的昵称、备注等信息。群聊的消息里发送者的信息在BytesExtra字段里需要额外解析。3.2 清洗流程设计我的清洗流程分几步先按TalkerId分组把同一个聊天对象的消息聚在一起然后按CreateTime排序接着根据Type字段做不同的处理——文本直接取StrContent图片和文件提取路径和文件名语音和视频做标记最后统一输出成结构化的JSON或Markdown。def extract_messages(db_path, key): conn sqlcipher.connect(db_path) conn.execute(fPRAGMA key{key}) cursor conn.execute( SELECT localId, TalkerId, Type, SubType, IsSender, CreateTime, StrContent, StrTalker FROM MSG ORDER BY CreateTime ) messages [] for row in cursor: msg { id: row[0], talker_id: row[1], type: row[2], sub_type: row[3], is_sender: row[4], timestamp: row[5], content: row[6], talker: row[7] } messages.append(msg) return messages3.3 处理特殊消息类型文本消息最好处理直接拿StrContent就行。图片消息的StrContent里通常有img标签里面包含cdnbigimgurl、md5等属性需要正则提取。文件消息的XML里有title文件名和totallen文件大小。引用消息Type49SubType57比较麻烦它的StrContent是一个XML里面嵌套了被引用的消息内容和当前回复的内容。需要解析XML把两部分拆开。系统消息Type10000是你撤回了一条消息、xxx邀请xxx加入了群聊这类通常不需要保留清洗时可以过滤掉。语音消息Type34的StrContent里只有时长和格式信息实际的语音数据在Media目录下的silk文件里。如果要转文字需要额外的语音识别步骤这个我暂时没做只是标记了语音消息的位置。3.4 输出格式的选择输出成什么格式取决于后续怎么用。如果是要喂给Codex做分析JSON比较合适结构清晰程序好处理。如果是要导入ObsidianMarkdown更合适可以直接作为笔记。我做了两个输出通道一个生成JSON包含完整的元数据一个生成Markdown按日期分文件每条消息一行带时间戳和发送者。Markdown的格式大概是这样## 2024-01-15 - **10:23** 张三: 明天的会议改到下午三点了 - **10:25** 我: 收到我调整一下日程 - **10:30** 张三: [图片] 会议室的预订截图这种格式在Obsidian里看起来很清楚而且可以用Dataview插件做查询和统计。4. 对接Codex让AI帮你分析聊天记录4.1 为什么要对接Codex聊天记录里有很多非结构化的信息比如讨论过的项目、约定的事项、分享的链接。人工翻看效率很低但让AI来提取关键信息就快很多。Codex作为代码生成和分析工具处理结构化数据的能力很强把聊天记录转成它认识的格式就能让它帮忙做总结、提取待办、分析话题分布。4.2 数据准备Codex对输入的长度有限制不能一次性把几年的聊天记录全塞进去。我的做法是按时间窗口切分比如按周或按月生成一个摘要文件然后让Codex逐个处理。每个摘要文件里包含这个时间窗口内的所有消息格式化成Codex容易理解的文本。我试过直接用Markdown格式Codex能很好地解析。关键是每条消息要有明确的时间戳和发送者标识这样AI才能理解上下文。def generate_weekly_summary(messages, output_dir): from collections import defaultdict from datetime import datetime weeks defaultdict(list) for msg in messages: dt datetime.fromtimestamp(msg[timestamp]) week_key dt.strftime(%Y-W%W) weeks[week_key].append(msg) for week, msgs in weeks.items(): lines [] for m in msgs: dt datetime.fromtimestamp(m[timestamp]) sender 我 if m[is_sender] else m[talker] content clean_content(m[content], m[type]) lines.append(f- **{dt.strftime(%m-%d %H:%M)}** {sender}: {content}) with open(f{output_dir}/{week}.md, w, encodingutf-8) as f: f.write(f# {week} 聊天记录\n\n) f.write(\n.join(lines))4.3 给Codex的提示词设计提示词的质量直接决定输出质量。我试了几版最终稳定下来的提示词是这样的你是一个聊天记录分析助手。以下是我和某人的微信聊天记录 请帮我完成以下任务 1. 提取所有约定的事项和待办标注时间和相关人 2. 总结讨论过的主要话题每个话题用一句话概括 3. 找出所有分享的链接和文件列出标题和用途 4. 标记出可能需要跟进的消息 输出格式用Markdown分四个部分。这个提示词的关键是把任务拆得足够具体每个任务都有明确的输出要求。太笼统的提示词比如帮我分析一下得到的结果往往很泛没什么用。4.4 实际效果和局限实测下来Codex在提取待办和总结话题方面表现不错准确率大概在80%左右。漏掉的主要是那些表述比较隐晦的约定比如那到时候再说这种AI不太能判断是不是真的有待办。链接和文件的提取基本没问题因为格式比较固定。话题总结有时候会把相关的话题合并得太粗需要人工再细分。一个重要的局限是Codex看不到图片和语音的内容。如果聊天里大量使用图片沟通AI能拿到的信息就有限。解决办法是先用OCR把图片转成文字再一起喂给AI。语音的话需要先做语音识别这个我还没集成进去。提示给AI的聊天记录里可能包含隐私信息如果用的是云端服务要注意数据安全。敏感内容建议先脱敏再处理。5. 对接Obsidian把聊天记录变成知识库5.1 Obsidian的目录结构设计Obsidian的核心是Markdown文件加双链。我把聊天记录按人-年-月的层级组织ChatRecords/ ├── 张三/ │ ├── 2024/ │ │ ├── 2024-01.md │ │ ├── 2024-02.md │ │ └── ... │ └── 2023/ ├── 李四/ └── ...每个人的文件夹下按年份分年份下按月份分文件。这样既不会单文件太大也方便按时间检索。每个月份文件的开头加一个元数据块记录这个月的消息数量、活跃天数等信息方便后续用Dataview查询。--- person: 张三 year: 2024 month: 01 message_count: 342 active_days: 18 --- # 2024年1月 与张三的聊天记录 - **01-01 09:15** 张三: 新年快乐 ...5.2 用Dataview做查询Obsidian的Dataview插件可以对这些Markdown文件做结构化查询。比如我想知道和张三最近三个月的消息量趋势TABLE message_count, active_days FROM ChatRecords/张三 WHERE year 2024 SORT month DESC LIMIT 3或者找出所有提到某个关键词的消息LIST FROM ChatRecords WHERE contains(file.content, 项目截止)Dataview的查询能力有限复杂的分析还是得靠外部脚本。但对于日常的检索和统计已经够用了。5.3 双链和标签的自动生成Obsidian的双链是它的核心价值。我在生成Markdown的时候会自动把一些实体加上双链。比如聊天里提到的人名、项目名、地点如果已经在知识库里有对应的笔记就自动加上[[ ]]。实现方式是维护一个实体列表生成Markdown时做字符串匹配替换。这个列表可以手动维护也可以从已有的Obsidian笔记里自动提取。def add_links(content, entity_list): for entity in entity_list: if entity in content: content content.replace(entity, f[[{entity}]]) return content标签的生成类似根据消息内容自动打上#待办、#链接、#图片这样的标签。这样在Obsidian的标签面板里就能快速筛选。5.4 增量更新的处理聊天记录是持续增长的不可能每次都全量重新生成。我的做法是记录上次处理到的时间戳每次只处理新消息追加到对应的月份文件里。def incremental_update(last_timestamp, db_path, key): conn sqlcipher.connect(db_path) conn.execute(fPRAGMA key{key}) cursor conn.execute( SELECT localId, TalkerId, Type, IsSender, CreateTime, StrContent, StrTalker FROM MSG WHERE CreateTime ? ORDER BY CreateTime , (last_timestamp,)) # 处理新消息追加到对应文件 ...增量更新要注意的是微信的消息时间戳是秒级的如果同一秒内有多个消息用可能会漏掉。稳妥的做法是用然后在写入时去重。6. 整个流程的自动化串联6.1 用脚本把各环节串起来手动一步步操作太麻烦我写了一个主脚本来串联整个流程def main(): # 1. 获取密钥 key find_wechat_key() if not key: print(未找到密钥请确认微信正在运行) return # 2. 解密并提取消息 messages extract_messages(DB_PATH, key) # 3. 增量过滤 last_ts load_last_timestamp() new_messages [m for m in messages if m[timestamp] last_ts] # 4. 生成Markdown generate_markdown(new_messages, OBSIDIAN_DIR) # 5. 生成Codex输入 generate_codex_input(new_messages, CODEX_DIR) # 6. 更新last_timestamp if new_messages: save_last_timestamp(max(m[timestamp] for m in new_messages)) print(f处理完成新增 {len(new_messages)} 条消息) if __name__ __main__: main()这个脚本可以手动跑也可以设置成定时任务。Windows上用任务计划程序Linux上用cron每天跑一次就行。6.2 定时任务的配置Windows任务计划程序的配置要点触发器设为每天一次操作设为运行Python脚本起始目录设为脚本所在目录。注意要勾选使用最高权限运行否则读内存会失败。Linux上cron的配置0 8 * * * cd /path/to/script /usr/bin/python3 main.py /var/log/wechat_export.log 21每天早上8点跑一次日志输出到指定文件方便排查问题。6.3 异常处理和日志自动化流程最怕的是静默失败。我在关键步骤都加了日志和异常处理import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(wechat_export.log), logging.StreamHandler() ] ) try: key find_wechat_key() except Exception as e: logging.error(f获取密钥失败: {e}) raise常见的失败场景包括微信没运行、版本更新导致密钥特征变化、db文件被锁定、磁盘空间不足。每种情况都要有对应的日志输出方便快速定位。7. 几个实际使用中的经验教训7.1 性能优化的几个点全量处理几年的聊天记录数据量可能有几十万条消息。我一开始的脚本跑一次要十几分钟后来做了几个优化降到两分钟左右。第一个优化是批量读取。不要一条条查用fetchall()一次性把结果拉出来。第二个优化是减少重复的密钥验证密钥获取一次就够了不用每条消息都验证。第三个优化是Markdown生成时用字符串拼接而不是频繁的文件写入最后一次性写文件。7.2 数据备份的重要性处理聊天记录之前一定要先备份原始的db文件。我有一次脚本写错了把解密后的db文件覆盖了原始文件结果原始数据没了。虽然解密后的数据还在但万一解密过程有问题原始数据就是最后的保障。备份的方法很简单把整个WeChat Files目录复制一份就行。注意微信运行的时候db文件可能被锁定复制之前先退出微信。7.3 隐私保护的边界聊天记录涉及双方的隐私处理的时候要有边界意识。我的原则是只处理自己的聊天记录不处理他人的如果要把数据分享出去比如发到社区一定要先脱敏把真实姓名、手机号、地址这些替换掉。给AI处理的时候也要注意如果用的是云端服务数据会离开本地。敏感内容建议用本地的模型处理或者先做脱敏。7.4 版本兼容性的坑微信更新很频繁每次更新都可能影响密钥获取和db结构。我的经验是不要追最新版用一个稳定的版本把自动更新关掉。等社区确认新版本没问题了再升级。另外不同平台的微信Windows、macOS、Linux数据结构有差异脚本不能通用。我主要是在Windows上用的macOS上的方案需要另外适配。7.5 关于开源方案的选型网上能找到不少开源的微信聊天记录导出工具选型的时候主要看几点是否支持你用的微信版本、是否还在维护、文档是否清晰、社区是否活跃。我试过几个有的已经很久没更新了有的只支持老版本微信。最终我选择的是自己组装方案用开源项目里的密钥获取思路加上自己写的清洗和输出逻辑。这样灵活性最高出了问题也好排查。整个流程跑通之后我现在每天早上的定时任务会自动把前一天的新消息同步到Obsidian同时生成Codex的分析输入。需要做周报或者回顾项目进展的时候直接看Obsidian里的汇总就行省了很多翻聊天记录的时间。这套方案不算完美但对我来说已经够用了。如果你也在折腾类似的事情希望这些经验能帮你少走点弯路。
