
简介SQLite作为轻量级嵌入式数据库广泛应用于移动应用本地存储而SQLCipher则为SQLite提供AES-256加密能力保障敏感数据安全。在工程实践中加密数据库的解密与数据迁移是数据备份、数据迁移及隐私管理的关键环节。本文从SQLCipher的加密原理出发解析微信本地数据库EnMicroMsg.db的密钥生成机制并基于Python演示完整的数据库解锁、数据读取与清洗流程。同时结合AI训练场景介绍如何将导出的聊天记录转化为结构化对话语料用于模型微调或自动回复系统。无论你是需要本地备份数据、构建个人知识库还是为AI应用准备高质量数据集本文都能提供清晰可落地的技术方案。1. 项目概述为什么我要折腾微信本地数据1.1 核心需求解析先说清楚这个项目到底在做什么。简单来说就是把自己微信账号在本地设备上产生的聊天记录、联系人信息、会话数据等从微信的私有数据库里读出来转成通用的 csv、html 格式一方面方便自己长期备份和检索另一方面可以把这些数据清洗后送给 AI 模型做微调训练或者基于这些数据搭建一个自动回复机器人。这个需求听起来很“极客”但实际场景比想象中接地气得多。我见过做电商的人想把自己跟客户的成交聊天记录导出来训练一个自动应答助手做自媒体的人想把公众号后台跟粉丝的互动记录整理成知识库也有人纯粹是聊天记录太多了手机换了好几次怕哪天数据丢了想做一个不依赖微信官方的本地存档。无论哪种场景核心动作都是一样的——拿到本地数据库文件解出里面的明文数据。1.2 适用人群与前置条件这个项目适合以下几类人有一定 Python 基础想折腾本地数据处理的开发者需要把自己聊天数据转化为训练语料的 AI 爱好者有数据备份和迁移需求不想被云同步绑定的普通用户想研究移动端数据库结构和加密机制的逆向爱好者仅限自己设备在动手之前必须强调一个底线本文所有操作只针对自己名下设备、自己账号产生的数据。微信的聊天记录属于个人隐私数据任何人未经授权读取他人设备上的聊天记录都是违法行为。这个项目的正确打开方式是管理自己的数据资产而不是去“获取”别人的信息。1.3 项目技术栈速览我实际跑通这套方案用的技术栈如下模块选型说明数据库文件EnMicroMsg.db微信 SQLite 数据库位于手机私有目录数据库读取SQLCipher sqlite3微信使用 SQLCipher 加密需要通过编译工具读取解密方案获取数据库密钥后通过 Python 脚本处理密钥由手机 IMEI 和微信 UIN 经过 MD5 生成数据导出Python sqlite3 csv / jinja2直接操作解密后的数据库导出 csv 和 htmlAI 训练适配数据清洗 对话对构建将单条消息整理为细粒度对话语料自动回复基于导出的历史对话做检索式回帖用简单相似度匹配即可无需上大模型这个技术栈在 Windows、macOS、Linux 上都能跑我的核心脚本在 macOS 上开发在 Windows 10 上也完整跑通过。2. 工具选型解析为什么是 SQLCipher 而不是其他2.1 微信数据库加密机制的底层逻辑微信的数据库文件 EnMicroMsg.db 不是普通 SQLite 文件而是经过 SQLCipher 加密的。SQLCipher 是 SQLite 的加密扩展它对整个数据库文件进行 AES-256 加密文件头有特定的 salt每页数据都是密文。如果直接拿 sqlite3 命令行打开只会报 “file is not a database” 的错误。微信选用 SQLCipher 而不是自己写一套加密是因为 SQLCipher 是成熟的方案兼容性好、性能损耗小而且提供了透明的加解密接口——在拿到正确密钥的情况下可以用标准的 SQLite API 直接操作不需要手动解密每一页。密钥的生成逻辑是微信早期版本的核心机制。它使用手机 IMEI设备唯一标识和微信的 UIN用户标识拼接后做 MD5 哈希取结果的前 7 位作为数据库密钥。这里有个关键点微信有“越狱/root 检测”系统被 root 后微信会选择不写入 IMEI而是用默认值 “1234567890ABCDEF” 替代否则数据库无法生成。这个细节我后面还会再提到。2.2 主流读取方案的横向对比我调研了社区里常见的几种方案整理成表格方便大家对比方案优点缺点适用场景官方聊天记录备份操作简单、无需处理加密格式不开放无法直接拿到明文数据普通用户换机迁移root 后直接读取数据库数据完整、可控性最强需要 root 手机可能触发微信风险控制极客玩家自用PC 端微信数据库读取不需要 root环境更好操作不同版本加密参数可能变化需要适配大多数人推荐使用第三方“聊天记录导出工具”开箱即用来源不明可能存在隐私风险不推荐不熟悉命令行的人我推荐大家优先考虑 PC 端微信的数据库文件因为 PC 端环境完全可控不需要 root 手机而且 Windows 上调试 SQLCipher 比在手机上操作要简单得多。当然如果你手里只有手机端的数据那就要走移动端方案。后面我会分别讲两条路。2.3 我踩过的选型坑第一次做这个项目的时候我试图直接用 Python 的 sqlite3 模块打开数据库结果报错。后来换了 pysqlcipher3这个库需要编译在 Windows 上编译容易出错需要安装 Visual C Build Tools。在 macOS 上相对顺利但在 Python 3.9 以上的版本安装这个依赖库时经常遇到兼容性问题。如果不想折腾编译还有一个更省事的方案用 SQLCipher 提供的命令行工具sqlcipher直接打开数据库文件执行PRAGMA key ...后就能像普通 sqlite3 一样导出数据。这个工具的优点是稳定、通用缺点是得手动写 SQL 命令批量处理的时候效率低一点。我在实际操作中用的是pysqlcipher3虽然编译麻烦但自动化程度高后续写 Python 脚本导出 csv 和 html 的时候非常顺手。3. 核心细节解析与实操要点3.1 数据存储位置与文件清单先说数据在哪。手机上微信的数据目录在内部存储的/data/data/com.tencent.mm/MicroMsg/hash/下这个目录里最关键的文件就是EnMicroMsg.db。这里需要 root 权限才能访问所以很多人在这一步就被卡住了。PC 端微信的数据目录相对好获取。Windows 上一般位于C:\Users\用户名\Documents\WeChat Files\wxid\db\macOS 上位于~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/version/hash/db/注意 macOS 目录名因微信版本不同有差异我见过2.0b4.0.18这类版本号目录还有一串很长的 hash 目录进去之后才是db文件夹。这里通常会有一个MSG.db文件大小往往是几十 MB 到几个 GB 不等这就是 PC 端聊天记录的数据库文件。PC 端数据库同样使用了 SQLCipher 加密但密钥获取方式与手机端不同需要从内存中提取或通过特定工具计算。我在实际操作中发现PC 端数据库的加密参数在微信 3.x 版本后发生了变化老社区教程里的方法不一定适用需要针对当前版本做适配。所以如果你只是想把自己手机上的数据导出来反而建议先用手机方案把数据备份出来后续再研究 PC 端。3.2 获取手机端数据库密钥的完整路径先明确一下思路。手机端要成功读出数据卡点有两个第一是拿到EnMicroMsg.db文件第二是拿到密钥。密钥的计算方式如下密钥 MD5(IMEI UIN) 取前 7 位IMEI 是手机硬件标识可以通用代码获取这里有一个重要细节如果手机已 root微信会将 IMEI 替换为默认字符串1234567890ABCDEF。UIN 是微信用户标识可以在手机本地配置文件中找到。这个值位于/data/data/com.tencent.mm/shared_prefs/system_config_prefs.xml中查找键值default_uin即可看到。有的教程说 UIN 在CompatibleInfo.cfg或system_config_prefs.xml里我实测下来system_config_prefs.xml里的default_uin最稳定。需要注意的是UIN 可能是负值也可能是 0如果是 0 说明微信用了备用方案你需要去找别的上下文数据。有了 IMEI 和 UIN 后Python 里这几行代码就能算出密钥import hashlib def get_db_key(imei: str, uin: str) - str: raw f{imei}{uin}.encode(utf-8) return hashlib.md5(raw).hexdigest()[:7]这里uin要使用字符串形式不要转成 int因为微信拼接的是字符串原始值。我用一个真实案例验证过拼接时如果 UIN 是负数必须保持负数符号一起拼接否则密钥不对。3.3 读取数据库的完整 Python 流程拿到文件和密钥之后核心的数据库读取动作如下from pysqlcipher3 import dbapi2 as sqlite conn sqlite.connect(EnMicroMsg.db) cursor conn.cursor() cursor.execute(PRAGMA key 你的7位密钥) cursor.execute(PRAGMA cipher_migrate;) # 兼容不同 SQLCipher 版本 cursor.execute(SELECT name FROM sqlite_master WHERE typetable;) tables cursor.fetchall() print(tables)执行这段代码后如果报错file is not encrypted or is not a database说明你的密钥不对或者 SQLCipher 版本跟微信不匹配。如果能看到表名列表说明已经成功解锁了数据库。数据库解锁之后最常用到的表是message表。这张表的结构在不同版本微信中略有差异但核心字段相对稳定字段名说明msgId消息唯一标识msgSvrId服务器消息 IDtype消息类型1文本3图片34语音43视频等isSend0收到1发出createTime发送时间Unix 时间戳talker会话对象好友的 wxid 或群聊 idcontent消息内容文本消息直接存文本图片/语音存的是路径或 XML我需要特别强调content字段。对文本消息来说它就是明文内容但对图片消息它是一串 XML包含缩略图路径和原图路径对系统消息比如“××× 撤回了一条消息”它也是 XML。导出的时候一定要做类型过滤否则清洗数据时会有大量无意义内容混入。3.4 多账户信息获取的正确理解方式标题里提到了“支持多账户信息获取”很多人第一反应是同时登录多个微信账号然后批量导出数据。其实这个在技术上并不复杂同一个数据库目录下可能会有多个wxid子目录每个子目录对应一个账号的数据遍历一遍即可。我的脚本里是这样设计的import os import glob WECHAT_DATA_ROOT os.path.expanduser(~/Documents/WeChat Files) def list_accounts(root: str): # 每个账号的数据存放在以 wxid 命名的子目录 accounts [] for entry in glob.glob(os.path.join(root, wxid_*)): name os.path.basename(entry) db_path os.path.join(entry, db, MSG.db) if os.path.exists(db_path): accounts.append({wxid: name, db_path: db_path}) return accounts这样遍历之后就能拿到本机上所有微信账号的数据库路径。但这里必须加一个限制条件只能处理本机上你登录过的账号。如果某个账号已经退出登录它的本地数据可能已经被清理你拿到的可能是空壳目录。实际操作中我发现 PC 端不同账号的数据目录命名规则不完全一致有的旧版本微信直接用微信号命名目录有的用wxid_前缀所以上面的正则只写了wxid_*如果你目录名不一样需要自行调整。4. 实操过程与核心环节实现4.1 数据导出的完整步骤这一节是全文的干货核心。我建议你按下面的步骤操作每一步都不要跳。首先准备 Python 环境。我使用的是 Python 3.10安装了以下依赖pip install pysqlcipher3 pandas jinja2pandas用来做数据清洗非常方便jinja2用来生成 HTML 报告也很顺手。然后把微信数据库文件复制到一个工作目录。这一步非常重要永远不要直接对原库做写操作因为 SQLCipher 解密过程中如果出现版本迁移cipher_migrate可能改写文件头一旦断电或中断原库就废了。复制完成后写一个统一的读取模块import hashlib import pandas as pd from pysqlcipher3 import dbapi2 as sqlite def decrypt_and_load(db_path, key): conn sqlite.connect(db_path) cur conn.cursor() cur.execute(fPRAGMA key {key};) cur.execute(PRAGMA cipher_migrate;) cur.execute(SELECT type, name FROM sqlite_master WHERE type IN (table,view);) tables [row[1] for row in cur.fetchall()] data {} if message in tables: df pd.read_sql_query(SELECT * FROM message;, conn) data[message] df conn.close() return data这里sqlite_master查询是通用的可以在拿到数据库后先看看有哪些表再决定要读哪张。有的版本表名叫msg有的是message需要多一步表名判断。拿到 DataFrame 之后清洗和导出的逻辑如下def clean_messages(df): df df[df[type] 1] # 只保留文本消息 df df[[createTime, isSend, talker, content]] df[createTime] pd.to_datetime(df[createTime], units) df df.dropna(subset[content]) return df def export_to_csv(df, output_path): df.to_csv(output_path, indexFalse, encodingutf-8-sig) def export_to_html(df, output_path, template_pathNone): from jinja2 import Template with open(template_path, r, encodingutf-8) as f: tpl Template(f.read()) html_content tpl.render(datadf.to_dict(orientrecords)) with open(output_path, w, encodingutf-8) as f: f.write(html_content)有一个细节很关键to_csv时我用了utf-8-sig编码而不是utf-8。因为 Excel 打开utf-8编码的 csv 时会出现中文乱码utf-8-sig带有 BOM 头Excel 可以正确识别。如果你只是给 AI 训练用其实utf-8就够但考虑到通用性我建议统一用utf-8-sig。HTML 导出的模板可以做成一个带搜索功能的页面。我用最简单的 Jinja2 模板实现了一个按会话分组、带时间线和消息方向区分的界面。下面是一个简化版的模板关键部分!DOCTYPE html html langzh-cn head meta charsetutf-8 title聊天记录导出/title style body { font-family: sans-serif; margin: 2rem; } .msg { margin: 0.5rem 0; padding: 0.5rem; border-radius: 4px; } .in { background: #f0f0f0; } .out { background: #d0e8ff; text-align: right; } /style /head body h1聊天记录导出/h1 {% for m in data %} div classmsg {{ out if m[isSend] else in }} span{{ m[createTime] }} {{ 我 if m[isSend] else m[talker] }}:/span p{{ m[content] }}/p /div {% endfor %} /body /html注意这里如果data很大一次性渲染所有记录会导致浏览器卡死。我通常在导出 HTML 前按talker分组每个会话生成独立文件或者在前端做分页。这是一个实战中容易踩的坑。4.2 导出结果的验证与质量检查导出不是跑完脚本就完了必须验证数据完整性。我的做法是比较导出 csv 的行数和数据库message中文本消息的行数两者应该完全一致。随机抽三条消息到微信客户端里手工核对确认content、时间、收发方向都正确。检查talker字段是否有空值。在某些微信版本中群聊消息的talker可能为空或只有群 ID需要补充解析字段才能映射到群名称。我自己曾遇到过一个现象同一账号在手机端和 PC 端导出的数据条数不一致因为两端消息的离线同步策略不同PC 端可能没有同步全部历史消息。所以在导出前你要想清楚你到底是想要手机端全量数据还是 PC 端足够用的数据。这个决定了你前期收集文件的方式。4.3 从聊天记录构建 AI 训练语料导出 csv 只是第一步真正喂给 AI 之前还要做一轮“对话对构建”。因为大部分 LLM 微调需要的格式是“一问一答”的结构而聊天记录是一串单向交错的消息流直接丢给模型没有意义。我的处理方法如下def build_conversation_pairs(df, max_turns4): pairs [] current [] for _, row in df.iterrows(): if row[isSend] 0: current.append({role: user, content: row[content]}) else: current.append({role: assistant, content: row[content]}) if len(current) max_turns * 2: pairs.append({messages: current[-max_turns*2:]}) return pairs这样得到的每个样本是一个多轮对话片段既保留了上下文又不会太长。对 micro-batch 训练很友好。清洗环节要特别注意过滤以下几类内容微信自带的表情符号如 [微笑]、[强]在文本消息中会以[表情]形式出现链接、小程序卡片等富文本信息纯数字或纯标点的消息可能是验证码、口令等撤回通知等系统消息我用一个简单的规则集处理import re def filter_noise(text): if not isinstance(text, str): return None text text.replace([表情], ) text re.sub(r[^], , text) # 去掉类 XML 标签 if len(text.strip()) 2: return None if re.fullmatch(r[\d\.\s], text): return None return text.strip()这里我特意保留了中英文和标点因为训练通用问答模型时真实语料的多样性比干净程度更重要。4.4 自动回复的实现思路与简单示例自动回复这块我先说明我的立场用自己导出的历史聊天记录做检索式回复没有问题但如果你要做“全自动替用户回复消息”的 Bot必须让接收方知晓自己在与机器人对话否则涉及欺骗存在严重的伦理和法律风险。本文只讲技术思路。我的简易版自动回复是基于历史对话的检索匹配。先把历史消息中的“问题-回答”对建立索引收到新消息时用 TF-IDF 或简单余弦相似度找最相似的历史问题将其对应回答作为候选回复from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity def build_faq(training_pairs): questions [p[messages][0][content] for p in training_pairs if p[messages][0][role] user] answers [p[messages][-1][content] for p in training_pairs if p[messages][-1][role] assistant] vectorizer TfidfVectorizer(analyzerchar_wb, ngram_range(1, 2)) return vectorizer, questions, answers def get_reply(query, vectorizer, questions, answers, top_k3): q_vec vectorizer.transform([query]) q_matrix vectorizer.transform(questions) scores cosine_similarity(q_vec, q_matrix).flatten() best scores.argsort()[-top_k:][::-1] return [answers[i] for i in best if scores[i] 0.25]这里用char_wb分析器对中文比较友好ngram_range(1,2)可以兼顾字符和双字组合。阈值 0.25 是我调出来的经验值太低会经常答非所问太高了会经常没有回答建议你根据自己的数据量调整。如果要更高级的生成式回复可以基于导出的语料对开源模型做 LoRA 微调但那个工程量就大了而且对硬件有要求本次先不展开。5. 常见问题与排查技巧实录5.1 数据库打开失败的几种典型原因这是大家遇到最多的一类问题。下面是我实际排查中见过的高频场景现象可能原因解决方案file is not encrypted or is not a database密钥错误核对 IMEI/UIN 拼接顺序确认 UIN 正负号同样的错误但密钥肯定没问题SQLCipher 版本不匹配执行PRAGMA cipher_migrate;后再查询数据库能打开但message表不存在文件版本过旧或不是微信主库检查目录下是否有其他 db 文件密钥计算看起来正确但一直报错手机已 rootIMEI 被替换尝试默认 IMEI1234567890ABCDEF密钥错误是最常见的。我见过有人把 UIN 从字符串转成了整数导致负数符号丢失这个错误非常隐蔽因为你看到 UIN 是-123456789之后如果int()转换后再拼到字符串里符号其实还在问题不大但如果手动编辑配置文件时把负号删了那就完全对不上了。5.2 不同微信版本的字段兼容性问题微信的数据库结构一直在微调。早年间message表的关键字段是msgId、type、isSend、createTime、talker、content后来部分版本增加了msgSeq、status、imgPath等字段。我的建议是不要用SELECT *之后直接按列名硬编码而是先取出 DataFrame 的列名和预期字段做交集再继续处理expected_cols [msgId, type, isSend, createTime, talker, content] available_cols [c for c in expected_cols if c in df.columns] df df[available_cols]这样无论哪个版本核心字段只要能覆盖就不会崩。如果某张表没有content字段多半是存储语音或视频的索引表不是聊天记录主表需要检查表名是否选对。5.3 CSV 中文乱码与 Excel 打开异常这是导出后最常见的“看起来很简单但很烦人”的问题。微信里的中文内容默认是 UTF-8但 Windows 版 Excel 打开无 BOM 的 UTF-8 文件时默认按 GBK 解析于是中文全部变成乱码。解决办法一是上面提到的用utf-8-sig编码导出二是如果你已经导出了utf-8文件可以用文本编辑器打开后另存为带 BOM 的编码三是在 Excel 里用“数据-自文本”导入手动指定文件编码为 UTF-8。另外还有一个细节csv字段中如果消息内容本身包含逗号、换行、引号导出时pandas会自动加引号转义。如果你手工拼字符串导出就一定要记得处理否则训练数据会被截断。5.4 数据量大导致的内存溢出问题如果你的聊天记录超过几万条一次性pd.read_sql_query读取整个表就会比较吃内存可能十几万条消息就占用几个 GB 内存。解决办法是分段读取用 SQL 的LIMIT/OFFSET或按createTime范围分批拉取batch_size 20000 offset 0 dfs [] while True: batch pd.read_sql_query( fSELECT * FROM message WHERE type1 LIMIT {batch_size} OFFSET {offset};, conn ) if batch.empty: break dfs.append(batch) offset batch_size df pd.concat(dfs, ignore_indexTrue)这种方式在导出数 GB 数据库时非常实用内存占用基本恒定。5.5 多账户场景下会话混淆问题当你遍历多个账号的数据时容易犯的一个错误是不同账号数据库里的talker可能是同一个好友的 wxid但实际是两个账号各自的好友关系不能直接合并。我在脚本里特地给每个账号加了一个前缀df[source_account] account_wxid这样后续如果要做一个跨账号的全局查询能清楚地知道每一条数据来源不会串号。5.6 关于“获取微信信息”范围的合规提醒我必须再强调一次这个项目的正当使用范围仅限于处理你本人账号、本人设备上的数据。可能有人会想“我能不能读取别人备份的数据库”这在法律上属于侵犯公民个人信息即使对方是你朋友未经授权也是不合规的。另外微信官方对用户协议里对自动登录、批量读取本地数据有约定大规模抓取或商用可能违反平台规则轻则功能受限重则账号被处理。这篇文章的价值在于帮助你管理自己的数据、学习数据库解密和 AI 数据处理技术而不是给黑产工具提供指引。我在实际使用中只处理我自己名下的两个账号数据这是底线。6. 进阶扩展数据可视化与知识库构建6.1 从聊天记录生成年度聊天报告除了导出 csv 和 html我还做了一个简单的年度聊天报告脚本。原理是统计每个会话的消息数量、时间分布、活跃天数然后渲染成带图表的 HTML 页面。这里给大家一个简化版的统计思路def generate_report(df, talkerall): selected df if talker all else df[df[talker] talker] stats { total_messages: len(selected), sent_messages: (selected[isSend] 1).sum(), receive_messages: (selected[isSend] 0).sum(), active_days: selected[createTime].dt.date.nunique(), } return stats时间分布可以用pandas的resample按天/周/月聚合再在前端用 Chart.js 或 ECharts 渲染柱状图。这种报告送给女朋友或者团队伙伴都很有纪念意义比花钱买第三方的聊天报告工具要踏实数据完全在自己手里。6.2 聊天记录构建个人知识库另一个我实际在用的方向是把聊天记录中你发出的有价值内容比如你认真写的长消息、技术解答、经验总结抽取出来构建成个人知识库。这些内容质量参差不齐但经过筛选后可以作为你个人风格的语料用来做风格化写作微调。抽取规则可以很简单内容长度大于 100 字不包含链接和图片 XML消息类型为文本由你本人发出isSend 1然后再用关键词聚类或简单主题模型分组。我试过用jieba分词后再用TF-IDF做聚类效果尚可够用。最终把每个主题下的文本保存成独立的md文件方便后续人工编辑。6.3 自动化定时导出的方案如果你希望每天都自动备份一次聊天记录可以写一个定时任务。Windows 上用计划任务macOS 上用crontab或launchd。核心脚本要做好幂等设计导出文件带日期后缀只处理增量部分或者直接覆盖旧的全量文件。我的方案是每天凌晨 2 点执行一次全量导出导出完成后把 csv 打包成tar.gz并保留最近 30 天。这个方案的优点是逻辑简单缺点是数据量大的时候每天全量导出会占用不少磁盘空间。如果数据量达到几个 GB建议改成增量导出记录上次导出的最大msgId下次只拉取msgId 上次最大值的记录。6.4 结合本地大模型实现离线自动回复如果对公有云 API 有顾虑可以在本地跑一个小模型做回复生成。把导出的对话历史转成 JSONL 格式用 Llama-Factory 或 LLaMA-Factory 对 Qwen 等开源模型做 LoRA 微调。硬件要求不算特别夸张一张 16GB 显存的消费级显卡就能跑 7B 级别模型的微调。不过这是一个大工程建议先把本文前面的基础流程跑通再考虑微调。微调前需要把语料格式化为{conversation: [ {role: user, content: 今天天气怎么样}, {role: assistant, content: 我今天没有看天气要不我帮你查一下} ]}自己导出的聊天记录不可能都是这样干净的问答格式所以清洗部分是整个微调流程中最耗时的一环。我建议先人工筛选出几百条高质量对话再做自动清洗和扩充效果远好于直接全量灌入。7. 收尾一些必须写在最后的话说实话这个项目我第一次做的时候花了整整一个周末最耗时间的不是写代码而是排查 SQLCipher 版本兼容性问题。当时数据库密钥明明对但一直报file is not encrypted最后发现是新版 SQLCipher 默认参数变了需要执行cipher_migrate才能读老库。这种问题在社区里很少有人写清楚所以我在文里特意加了这个细节。如果你也想动手做我的建议是先不要一上来就追求完整工具链。第一步先手动跑通数据库解密能看到表名就算成功第二步再导出 csv第三步再考虑 AI 训练。每走一步都验证一下数据对不对否则等到导出上万条数据后才发现之前解密步骤就有问题返工成本很高。另外导出数据之后一定要做好保管。聊天记录里有大量隐私信息csv 或 html 文件就是明文建议存放在加密磁盘或者带密码的压缩包中不要随意同步到网盘。这个项目本身是为了让你更好地掌握自己的数据如果因为保管不善造成数据泄露那就得不偿失了。最后再分享一个实用小技巧微信的数据库文件在做任何解密操作前建议先用md5sum记录原始文件的哈希值。如果解密过程异常可以快速对比哈希确认原文件有没有被改动。我就是靠这个习惯在一次cipher_migrate意外中断后成功判断出原库文件已损坏及时从备份恢复避免了几百 MB 数据彻底报废。希望这篇记录能帮到你。如果你在这个项目上遇到了其他问题也可以按照文中提供的排查思路先从文件来源、密钥正确性、版本兼容这三个维度入手大概率能定位到原因。本文还有配套的精品资源点击获取