简介:这款微信数据库解析工具包面向需要管理与分析个人微信数据的用户,以及希望研究聊天记录提取、数据库解密、联系人/群组导出等技术的开发者。资源基于Kotlin工程实现,包含完整的Android项目结构,可支撑聊天内容备份、PC与手机端数据同步、数据挖掘等应用场景。包内共63个文件,以kt源码、xml配置、png界面图、gradle构建脚本为主,分别对应核心逻辑、界面布局、运行截图和构建配置;另附txt说明、docx附赠文档和jar工具包,方便对照学习,整体仅187KB,轻量紧凑,便于快速下载与本地编译。已有235人学习或下载,适合结合README与操作手册快速上手。通过该项目,读者既能掌握微信数据库解析的整体思路,也能基于示例代码进行二次开发,用于个人数据备份恢复或商业情报分析,同时需注意合法合规使用隐私数据。
1. 微信数据库解析工具到底在解决什么:从聊天记录到可分析数据资产
你手里这台电脑上,微信正趴着一整座数据金矿——几年积累的几万条聊天记录、成百上千的联系人、几十个群的群成员关系。官方提供的聊天记录备份只能看,不能查、不能统计、不能迁移,更别说数据挖掘。微信数据库解析工具解决的就是这个:把微信本地加密数据库解开,把聊天记录、联系人、群组信息导成普通 SQLite、CSV 或 HTML,之后随便你怎么备份、分析、二次处理。它适合三类人:想彻底备份本人聊天记录的普通用户,想研究社交关系的数据分析爱好者,以及需要把微信数据迁移到其他系统做归档的工程师。先说清楚边界:以下所有操作只针对你本人登录的微信账号和自有设备,涉及他人数据先拿到授权。
2. 解密前的关键一步:定位数据库文件与还原 SQLCipher 密钥
2.1 微信 PC 端数据库藏在哪里:MSG.db、MicroMsg.db 和 Media 目录
微信 PC 端的数据不是散装文件,而是按微信号目录集中管理。常见位置在微信安装目录下的WeChat Files里面,一级目录是微信号,二级目录才有真正的数据。核心文件是MSG.db,这是聊天记录的总库;MicroMsg.db管联系人与群信息;Media文件夹存图片、语音、视频;FileStorage存接收的文件。可能还有一个config目录存放运行配置。
老版本微信(3.x 之前)通常直接暴露这些文件,4.x 开始把数据迁移到了用户目录下类似xwechat_files的路径,文件名也可能变成msg或message开头。找它们的办法很直接:打开微信的「设置 → 文件管理 → 打开文件夹」,微信自己会告诉你存储根目录在哪里。如果微信正在运行,这个目录里还能看到MSG.db-wal和MSG.db-shm,这是 SQLite 的预写日志和共享内存文件,说明数据库正被占用。
这个阶段不要急着复制或删除任何东西,先把整个微信号目录的磁盘占用、文件修改时间记下来。后面你会发现,微信数据备份是否完整,其实在文件层面就能先判断个七八成:一个正常的账号目录里,MSG.db加MSG.db-wal的常见大小应该在几十 MB 到几 GB 之间,如果只有几百 KB,说明本地几乎没缓存聊天记录。
2.2 数据库不是普通 SQLite:SQLCipher 加密与密钥来源
微信 PC 端数据库用的是 SQLCipher,也就是带 AES 加密的 SQLite。普通 SQLite 文件头是SQLite format 3,你用data命令一眼就能认出来;微信数据库的文件头是随机密文,直接strings搜中文字符串什么都搜不到。
解密必须拿到数据库密钥。这个密钥不是你微信登录密码,而是每次拿手机扫码登录时,客户端与服务器协商出来的一个会话随机数。微信不会把密钥明文放在数据库旁边,常见情况是它存在进程内存里,或者以一种特定格式散落在配置文件中。不同微信版本生成的机制不一样,这就是为什么「pc 微信4.x 的数据库解密」在社区里一直是个热门词——版本一升级,老搜索偏移量就失效,工具也得跟着更新。
所以市面上的解析工具,核心工作不是处理 SQL,而是还原密钥。主流思路有两种:一种是从微信进程内存中定位密钥,像抓内存转储然后按特征搜索;另一种是在数据库读写函数里做 Hook,在微信真正执行 SQLCipher 操作时把明文 key 截获。两种方式都有风险,第一种对版本敏感,第二种需要注入进程,可能触发安全机制。我一般建议用现成工具包先尝试标准解密流程,不要一上来自己写内存扫描,那是一个大坑。
2.3 一个标准的解密流程:从导出密钥到挂载明文数据库
拿到一个微信数据库解析工具包时,不管它包装成什么样子,核心工作流都是三步:定位数据目录 → 导出密钥 → 挂载数据库。假设你通过工具已经拿到 64 位十六进制密钥,接下来最稳的做法是直接用sqlcipher命令把加密库解密成普通 SQLite 文件。
# 假设密钥保存在 key.txt,内容是纯 hex,例如 4a2b... KEY=$(cat key.txt) sqlcipher MSG.db <<EOF PRAGMA key = "x'$KEY'"; PRAGMA cipher_compatibility = 3; .headers on .mode csv .output msg_plain.csv SELECT 1; .output stdout EOF这段命令先给加密库注入密钥,PRAGMA cipher_compatibility = 3是为了兼容微信早期使用的 SQLCipher 3.x 格式,如果微信版本较新,可能需要换成4,或者干脆不设置。SELECT 1这一步看起来多余,但它有一个实际作用:让 SQLCipher 尝试执行一次查询,如果密钥错误,这里会直接报file is not a database;如果通过了,就说明密钥和格式都对了。
更常见的做法是先生成明文副本,再拿普通sqlite3工具慢慢分析,避免每次都带着解密参数操作。命令是:
sqlcipher MSG.db <<EOF PRAGMA key = "x'$KEY'"; PRAGMA cipher_compatibility = 3; PRAGMA journal_mode = DELETE; .dump EOF然后重定向到新文件。注意要在微信退出后进行,否则 WAL 文件里的数据不会被合入,后续你导出的记录会少一大截。
2.4 手机端 EnMicroMsg.db 为什么是另一套体系
题目里提到「微信PC端与手机端数据同步」,这里必须把手机端数据库也讲清楚,否则你辛辛苦苦导出的 PC 数据,跟手机一比对会发现对不上。手机端安卓微信的数据库文件名是EnMicroMsg.db,位于data/data/com.tencent.mm/MicroMsg/下,同样用 SQLCipher 加密。
早期安卓微信的密钥规则有一个流传很广的算法:对设备的 IMEI 和当前登录账号的 uin 拼接做 MD5,取前 7 位作为密钥。我在旧版本上验证过,这个算法确实能解开一部分老库,但近几年的微信已经改成随机密钥,并把密钥放到本地受保护的存储里,单纯靠设备信息已经算不出来。所以如果你没有 root 权限,也没法读取应用私有目录,手机端数据库基本只能通过官方备份方案导出,而不是像 PC 端那样直接拿文件就能解。
一个务实的策略是:以 PC 端数据库为主战场,因为 PC 本地天然有一份可操作的文件;手机端作为补充,通过微信自带的「聊天记录备份与迁移」功能导出到另一台设备,防止误删。两个端的数据源不同,时间跨度也不同,后面我会展开说怎么处理。
3. 提取聊天记录、联系人与群组:一套可直接照抄的 SQL 解析方案
3.1 先把聊天记录导成 CSV:MSG 表最小导出脚本
解密成功后,打开数据库第一步是看表清单:
sqlite3 decrypted.db ".tables"微信 PC 端库里的核心表是MSG,字段通常包括localId、Talker、Type、SubType、CreateTime、Content、IsSend、ImgPath等。CreateTime是毫秒时间戳,Talker是聊天对象的标识,普通联系人是微信号,群聊是xxx@chatroom,Type是消息类型,Content是消息内容。一个最简导出脚本长这样:
sqlite3 decrypted.db <<EOF .headers on .mode csv .output msg_export.csv SELECT datetime(CreateTime/1000, 'unixepoch', 'localtime') AS time, Talker, Type, IsSend, replace(substr(Content, 1, 500), char(10), ' ') AS content FROM MSG ORDER BY CreateTime; .output stdout EOF逻辑说明:datetime(CreateTime/1000, 'unixepoch', 'localtime')把毫秒时间戳转成本地时间,replace把换行符替换成空格,避免 CSV 字段里嵌了换行导致 Excel 打开串行。substr限制长度是为了防止超大消息把文件搞乱。导出的 CSV 用 Pandas 读取时,注意content列可能是严格字符串。
参数调整方面,如果你只需要某个人的聊天记录,在WHERE后面加AND Talker = 'filehelper'即可。如果需要所有MSG*表,微信在某些版本里会把历史消息拆成MSG_0、MSG_1这类分表,建议先跑一句:
SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'MSG%';把所有MSG%表都导出来再合表,否则时间线会断。
3.2 联系人导出:Friend0 表与常用字段映射
联系人信息主要在Friend0表,字段有UserName、NickName、RemarkName、ConRemark、Mobile、Signature、Type等。UserName是微信内部标识,NickName是好友在微信里展示的昵称,RemarkName是你给他设的备注,ConRemark通常是通讯录同步备注,Mobile是手机号。
导出语句:
sqlite3 decrypted.db <<EOF .headers on .mode csv .output contacts.csv SELECT UserName, NickName, RemarkName, Mobile, Signature, Type FROM Friend0 WHERE Type = 1; .output stdout EOF类型字段Type需要说明一下:1一般代表普通联系人,2代表公众号/服务号,3代表企业联系人。不加过滤直接导出,你会发现联系人表里有大量非好友账号,比如微信团队、文件传输助手。加Type = 1能让结果更贴近真实好友列表。
还有个容易被忽略的点:有些联系人的昵称或备注存在ChatRoom之外的RContact表里,尤其是从手机上同步过来的“新朋友”记录,Friend0里不一定全。如果你发现联系人数量比手机通讯录少,试着再导RContact表看看,把两个表的UserName做一次去重合并。
3.3 群组信息获取:ChatRoom 表与成员关系
群组信息不像联系人那么集中,需要几张表拼起来。常见结构是ChatRoom存群的基础信息,ChatRoomMember存群成员用户名,ChatRoomInfo存群成员的具体昵称或备注。导出所有群和群成员的关系:
sqlite3 decrypted.db <<EOF .headers on .mode csv .output group_members.csv SELECT m.ChatRoomName AS group_id, m.MemberName AS member_username, i.MemberNickName AS member_nickname FROM ChatRoomMember m LEFT JOIN ChatRoomInfo i ON m.ChatRoomName = i.ChatRoomName AND m.MemberName = i.MemberName; .output stdout EOF这里用LEFT JOIN而不是INNER JOIN,因为ChatRoomMember表会记录一些已经从群里退出的成员,ChatRoomInfo里不一定有对应昵称记录。保留NULL行再看怎么处理,总比把成员漏掉好。
群昵称是最难拿的:群主设置的群名称可能不在ChatRoom表里,而在一条Type = 10000的系统消息中,以 XML 形式存储。想拿群名,可以查MSG表中Talker以@chatroom结尾且Type = 10000的记录,按Content里的<roomname>标签去解析。这不是一条万能的路径,但很多情况下比ChatRoom表更准。
3.4 微信 dat 文件查看器思路:把加密图片还原成 JPG
微信媒体目录里的图片文件后缀是.dat,不是数据库的一部分,而是一种简单的异或加密。它的原理是:图片原始字节逐字节与某个单字节值做异或,得到 dat 文件。因为 JPEG 文件头固定是FF D8 FF,PNG 是89 50 4E 47,所以只需枚举 0 到 255 的异或值,尝试还原并比对文件头即可。
下面是一个最小还原脚本:
import os def restore_dat(src, dst): with open(src, 'rb') as f: data = f.read() if not data: return False for xor_key in range(256): # 只对前 16 个字节做异或测试,速度快很多 head = bytes([b ^ xor_key for b in data[:16]]) if head[:3] == b'\xff\xd8\xff': # JPEG out = bytes([b ^ xor_key for b in data]) with open(dst, 'wb') as f: f.write(out) return True return False逻辑说明:从 0 到 255 枚举异或密钥,先用文件头判断,命中 JPEG 魔数就确认密钥,然后对整个文件做异或。为什么要先测试前 16 字节?因为 dat 文件可能很大,动辄几 MB,全量做 256 次异或浪费 IO,先试文件头可以把单文件耗时压到毫秒级。实际使用时,同一个微信账号目录下的图片通常共用同一个异或值,所以第一次跑完可以把xor_key缓存起来,后续文件直接复用。
这个思路就是社区里常说的「微信dat文件查看器」背后的核心原理。不过注意,微信新版本升级后,个别媒体文件可能不再采用单字节异或,而是加了偏移扰动,这时候按上述脚本会还原失败,需要先对比同批次多张 dat 文件的前 64 字节规律再调策略。
4. 聊天内容备份和 PC/手机数据同步的落地做法
4.1 三种备份介质:明文 SQLite、CSV 归档、HTML 快照
导出数据是一回事,备份又是另一回事。只留 CSV 问题很多:消息里的表情图片、语音文件名、位置消息的 JSON 都没了,而且 CSV 没法做 SQL 查询。我更推荐做三层备份:
- 明文 SQLite:也就是把解密后的
decrypted.db保留下来,这是最完整的备份,后续任何解析都能重跑。 - CSV 归档:按联系人、按月份导出多个 CSV,适合做长期存档和快速检索。
- HTML 快照:把指定联系人的聊天记录生成一个单文件 HTML,聊天内容按时间顺序排成气泡样式,日常翻阅最直观。
生成 HTML 不需要复杂框架,用一个 Python 脚本就能把 CSV 转成带简单搜索框的静态页。核心代码:
import html def csv_row_to_html(row): sender = row['sender'] time = row['time'] content = html.escape(row['content']) is_send = row['is_send'] cls = 'send' if int(is_send) else 'recv' return f'<div class="{cls}"><span class="time">{time}</span><span class="sender">{sender}</span><p>{content}</p></div>'这个函数把每条消息渲染成一个div,用 CSS 控制左右气泡。没必要把全部库都搬进去,按Talker过滤后生成单联系人页面更适合日常查阅。注意html.escape一定要做,否则聊天里如果出现<script>标签,生成 HTML 后可能被浏览器执行,这是自用也要防的坑。
4.2 PC 与手机端数据为什么总对不上
做过同步作业的人都会遇到一个困惑:电脑上明明有完整聊天记录,手机上也好像全都在,但把两边导出数据一对比,发现 PC 端缺了几个月前的记录,或者手机端少了某几台设备上的图片。
原因要从微信同步机制说起。微信 PC 端的数据库不是手机端的全量镜像,而是按时间和会话维度缓存的最近数据。PC 登录后,会从服务器和手机端拉取最近一段时间的聊天记录,老记录是否在 PC 上,取决于你电脑存的微信文件有没有被清理过,也取决于当时的登录状态。手机端则是真正的全量存储主流,删除手机上的聊天记录,PC 端同样会受影响,因为 PC 端的不完整副本不会提供“恢复已删记录”的能力。
如果你想做一个可靠的跨端归档,不要指望拿 PC 库直接覆盖手机,也不要用手机备份覆盖 PC。正确做法是:把 PC 端导出的明文库和手机端通过官方备份得到的记录导入同一个 SQLite 库,以CreateTime、Talker、Content三个字段做联合主键去重。如果出现同一条消息两处以不同形式存在,优先采用内容更完整的那一个。
4.3 自动化备份:退出微信后同步副本才是安全姿势
你不需要每次打开微信就手动复制文件。完善的做法是写一个备份脚本,在微信完全退出后,把整个数据目录压缩到带日期的归档文件里。
#!/usr/bin/env bash WECHAT_DATA="$HOME/Documents/xwechat_files" BACKUP_DIR="$HOME/wechat_archives" DATE=$(date +%Y%m%d_%H%M%S) # 先确认微信进程不存在 if pgrep -f "WeChat" > /dev/null; then echo "WeChat is running, skip backup" exit 1 fi # 用 tar 打包并压缩,保留文件权限和时间戳 tar -czf "$BACKUP_DIR/wechat_$DATE.tar.gz" -C "$WECHAT_DATA" .这段脚本的关键是pgrep检查微信进程。如果微信在运行,直接复制数据库文件会复制到一个不一致的状态,即使加上-wal文件也可能漏掉最后几秒的数据,严重时还会复制到正在写的半截文件。备份完成后,用sha256sum校验压缩包大小,跟上一次备份对比,如果偏差太大就要考虑数据库是否损坏。
配合 Windows 计划任务或 macOSlaunchd,可以做到每晚凌晨退出微信后自动备份。不过要提醒一句:微信很多版本支持托盘驻留,进程不一定有主窗口,脚本里除了pgrep之外最好再判断有没有WeChat关键字的所有进程,避免误判。
5. 解密与提取的常见避坑:五个真实翻车现场
5.1 PRAGMA key 之后还是报 file is not a database
现象:按工具提示填了密钥,执行PRAGMA key没报错,但紧接着查表就返回file is not a database。
原因:多数情况是密钥格式不对,比如你填的是 ASCII 字符串而不是十六进制前缀,或者 SQLCipher 版本参数不匹配。还有一部分是MSG.db和MSG.db-wal没合库,加密数据库的页信息还没更新。
解决:先确认密钥长度必须是 64 位 hex;在PRAGMA key那行加上x'...'前缀。然后依次尝试PRAGMA cipher_compatibility = 3;、4;,如果能通就继续。如果还是不行,把目录下的-wal和-shm文件移走,再用原库执行解密,最后一步做完再恢复 WAL 文件。
5.2 导出的中文乱码,或者 type=49 消息变成一长串 XML
现象:CSV 里中文能正常显示,但出现大量类似<msg><appmsg>的内容,广告链接的标题、小程序卡片全混在一起。
原因:Type = 49的消息不是纯文本,而是腾讯的 XML 协议,Content字段里包含整个卡片结构;Type = 1的文字消息也可能因为微信引入了新编码变成带\u转义的形式。
解决:对Type做分派处理。文字消息直接取Content,链接/文件/小程序消息用正则从 XML 里提取<title>、<des>和<url>。示例:
import re def parse_content(type_value, content): if type_value == 1: return content if type_value == 49: title = re.search(r'<title>(.*?)</title>', content) return title.group(1) if title else content return content这个函数在导出 CSV 时调用,能过滤掉 90% 的协议噪音。注意有些系统消息Type = 10000也要单独处理。
5.3 微信正在运行时数据库被锁,导出进度卡死
现象:用脚本读取MSG.db时,偶尔能读,偶尔报database is locked,甚至复制文件时发现文件大小一直在变。
原因:SQLite 默认在 WAL 模式下运行,微信进程持有写锁,外部进程尝试写入或者做 checkpoint 时会被阻塞。
解决:最靠谱的办法是先退出微信再解析,微信退出时 WAL 会被安全合并。如果业务要求必须在线分析,可以用 SQLite 的immutable=1参数直接只读打开,比如sqlite3 "file:MSG.db?immutable=1" .tables,这样能绕过锁,但读到的可能是落后几秒的快照。更稳的是先复制整个目录到临时盘,再在副本上解析。
5.4 dat 图片转换后花屏、打不开
现象:暴力异或成功还原了 JPEG 文件头,但整张图片打开是花屏,或者只有上半部分正常。
原因:微信的图片文件不是所有字节都用同一个异或密钥。新版本微信在文件头加了一个随机偏移,实际异或是“字节值异或 key + 偏移”或使用分段密钥。
解决:不要继续做全文件异或,改用先抽取同一会话的 3 张 dat 文件,比较它们的前 16 字节,找出共同的线性关系。很多时候,前 4 字节是文件格式探测区,真正的数据从偏移 8 或 12 才开始做异或。转换脚本里加一个偏移量参数,比 256 次暴力枚举靠谱得多。
5.5 提取的聊天记录时间跨度不连续,少了一大截
现象:导出后统计时间范围,发现最长只有最近三个月,没有找到一年前的对话。
原因:微信 PC 端本地库通常按会话整理,老消息在当月被压缩清除;或者聊天记录存在多个分表里,你只导了MSG主表。
解决:第一步,用sqlite_master找出所有MSG%表并分别查最大最小时间;第二步,把分表合并到同一个视图,再导出。如果所有分表都缺那段时间,说明微信本地确实没缓存,需要从手机端备份或微信官方聊天记录迁移功能找回,而不是继续折腾 PC 数据库。
6. 让提取出的数据真正可用:从聊天记录到个人数据挖掘
6.1 用 pandas 统计高频词,看聊天主题
导出明文 SQLite 后,拿 pandas 直接读表就能做初步挖掘。先做清洗,过滤掉系统消息和 XML 噪音,再按空格、逗号切词。如果你处理的是中文,可以用 jieba 分词,但不用太复杂,统计一个范围里的固定词足够。
import pandas as pd df = pd.read_csv('msg_export.csv') text = df[df['type'] == 1]['content'].str.lower() words = text.str.split(r'[\s,,。!]').explode() top_words = words.groupby(words).size().sort_values(ascending=False).head(20)这里关键是先按type过滤,否则会把小程序卡片里的广告词全统计进去。
6.2 按小时统计活跃度,找到自己的“数字分身”
把时间列转成datetime后groupby小时,能得到你一天中聊天最密集的时段。很多人统计完会惊讶地发现,自己以为只在午休聊,实际深夜才是高峰。这个数据用来做数字排毒很有说服力。做图时用柱状图横轴 0-23 即可,不用画复杂热力图。
6.3 用联系人表反查数据一致性,再谈同不同步
最后,拿之前导出的group_members.csv和contacts.csv做一次关联,统计那种在群成员列表里出现但不在联系人表里的账号,再对比手机端通讯录能找出“那个改了备注但不常聊的好友”。这既是数据一致性验证,也是社交关系挖掘的起点。做完全部流程后,我最大的教训是:工具能解库,但救不了“聊了很多却忘了做正事”的人。希望这些步骤能帮你把自己的数据管清楚,不丢、能查、用得顺手。
本文还有配套的精品资源,点击获取