资讯详情 微信聊天记录本地导出:SQLite解密、Protobuf解析与三格式生成
📅 2026/10/11 13:56:33
简介这是一套面向微信开发者与个人数据管理者的微信聊天记录提取与分析工具集解决日常聊天数据长期保存、多格式导出及深度统计分析的痛点。资源包含238个文件主体为94个Python脚本实现备份解析、HTML/Word/CSV转换及年度报告生成、61个PNG与36个SVG图表资源用于可视化呈现、17个HTML模板页面含wordcloud、globe交互图表及节日主题页以及配置类JSON、YML和可执行exe文件整体压缩包25MB。已有648人学习下载覆盖从基础备份操作到PandasMatplotlib自动化分析的完整链路。用户可直接运行脚本解析本地微信备份数据一键生成带时间轴、词云、活跃度热力图的年度聊天报告并复用预置的Jupyter Notebook分析模板与UI界面资源显著降低二次开发门槛。1. 为什么微信聊天记录导出不是“点一下就完事”而是一场数据库、编码、时区和格式兼容的三重校准你手机里存着三年没删的微信聊天但真想把它变成一份能打印、能搜索、能分析的年度报告别信那些“一键导出”的App——它们要么只给模糊截图要么导出后中文乱码、时间错位、图片丢失、语音变空行。这不是功能缺陷而是微信从设计之初就没打算让你轻易拿走数据聊天记录加密存储在 SQLite 数据库中EnMicroMsg.db密钥绑定设备硬件与微信登录态消息体用 Protobuf 序列化压缩时间戳是 Unix 时间戳毫秒级但本地显示依赖系统时区附件路径指向私有沙盒目录……导出的本质是绕过微信客户端的封装层在不越狱/不 Root 的前提下合法获取、解密、解析、重构、渲染这整条数据链。本方案面向真实落地场景已通过电脑端微信备份获取MsgAttach和EnMicroMsg.db文件的用户iOS 需 iTunes 备份提取Android 需开启 USB 调试并用adb backup或第三方工具如WhatsApp Key Extractor类思路适配目标明确——生成三份可交付文档HTML带交互式时间轴与关键词高亮、Word满足归档排版要求含目录与页眉页脚、CSV供 Excel 或 Python 分析字段对齐、无乱码、时间可排序。它不依赖云端同步、不调用微信未开放 API、不伪造登录态所有操作在本地完成全程可控、可审计、可复现。2. 从EnMicroMsg.db到结构化数据SQLite 解密与 Protobuf 解析的最小可行路径微信聊天记录并非明文存储。EnMicroMsg.db是一个加密的 SQLite 数据库其加密方式为AES-256-CBC密钥由微信登录态生成且每次登录可能变化。但关键在于密钥可从微信 PC 客户端内存中稳定提取非暴力破解非逆向核心逻辑这是当前最可靠、被多个开源项目验证的方案。我们不重复造轮子而是聚焦于如何让这个流程在你的 Windows/macOS/Linux 环境中稳定跑通。2.1 提取密钥用WeChatKeyExtractor获取 AES KeyWindows/macOS 通用提示此步骤必须在微信 PC 客户端已登录且主窗口处于前台时执行。密钥有效期约 30 分钟建议提取后立即使用。下载编译好的WeChatKeyExtractor工具GitHub 搜索WeChatKeyExtractor选择 star 数 200 的仓库如x0rz/WeChatKeyExtractor。以 Windows 为例# 假设已将 WeChatKeyExtractor.exe 放入 D:\wechat_tools\ cd /d D:\wechat_tools WeChatKeyExtractor.exe成功运行后控制台会输出类似[] Found WeChat process: WeChat.exe (PID: 12345) [] Memory scan completed. [] Found key: 7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d [] Found iv: 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d该key和iv即为解密EnMicroMsg.db所需的 AES 参数。注意key 和 iv 必须十六进制字符串长度均为 32 字符16 字节。若输出为 Base64 或其他编码请用在线工具转为 hex如base64decode.org输入后选 hex output。2.2 解密数据库用sqlcipher命令行工具还原明文 SQLitesqlcipher是 SQLite 的加密扩展支持 AES-256。我们用它加载加密 DB 并导出为明文。# 安装 sqlciphermacOS 用 brewWindows 下载预编译二进制 # macOS: brew install sqlcipher # Windows假设已下载 sqlcipher.exe 到 D:\tools\ # 将 EnMicroMsg.db 和密钥文件放在同一目录 echo PRAGMA key x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d; decrypt.sql echo ATTACH DATABASE EnMicroMsg_decrypted.db AS decrypted KEY ; decrypt.sql echo SELECT * FROM sqlite_master; decrypt.sql echo CREATE TABLE decrypted.ChatMsg AS SELECT * FROM main.ChatMsg; decrypt.sql echo DETACH DATABASE decrypted; decrypt.sql sqlcipher EnMicroMsg.db decrypt.sql逻辑说明PRAGMA key设置解密密钥ATTACH创建新数据库CREATE TABLE ... AS SELECT将原始表结构与数据完整复制到新库。最终生成EnMicroMsg_decrypted.db可用任何 SQLite 浏览器如 DB Browser for SQLite打开验证。2.3 解析 Protobuf 消息体用wechat-decryptPython 库还原文本与元数据微信消息体Content字段是 Protobuf 编码的二进制 blob。直接SELECT Content FROM ChatMsg看到的是乱码字节。必须用对应.proto文件反序列化。wechat-decrypt是目前最活跃维护的 Python 库已内置适配微信 v8.x 的Message.proto。# 安装 pip install wechat-decrypt # Python 脚本parse_msg.py from wechat_decrypt import WeChatDecrypt import sqlite3 # 连接解密后的数据库 conn sqlite3.connect(EnMicroMsg_decrypted.db) cursor conn.cursor() # 查询一条含文本的消息MsgSvrID123456789 为示例 cursor.execute(SELECT MsgSvrID, Type, Content, CreateTime, Talker FROM ChatMsg WHERE Type1 LIMIT 1) row cursor.fetchone() if row: msg_id, msg_type, content_blob, create_time, talker row # 解析 Protobuf try: parsed WeChatDecrypt.parse_message(content_blob) print(f发送人: {talker}) print(f时间: {create_time} - {parsed.timestamp}) # 注意CreateTime 是服务器时间戳parsed.timestamp 是消息内嵌时间 print(f内容: {parsed.content}) print(f是否撤回: {parsed.is_recall}) except Exception as e: print(f解析失败: {e})参数说明WeChatDecrypt.parse_message()返回Message对象核心属性包括content纯文本、timestamp毫秒级时间戳、is_recall布尔值、sender_id发送者 ID、msg_type1文本3图片34语音等。关键避坑点CreateTime字段来自数据库插入时间可能晚于实际发送时间应优先使用parsed.timestamp并转换为本地时间。3. 三格式导出HTML 渲染、Word 排版、CSV 结构化的代码级实现导出不是简单SELECT * INTO OUTFILE。HTML 需要语义化标签与 CSS 样式Word 需要保留段落、图片、超链接与目录层级CSV 必须处理换行、逗号、引号转义与多值字段如群聊成员列表。我们用 Python Jinja2 python-docx pandas 组合实现。3.1 HTML 导出用 Jinja2 模板生成可搜索、带时间轴的静态页面HTML 目标加载快、兼容 Chrome/Firefox/Edge、支持 CtrlF 全局搜索、时间按天分组、关键词高亮、图片懒加载。# templates/chat.html.j2 !doctype html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title{{ title }}/title style body { font-family: Microsoft YaHei, sans-serif; line-height: 1.6; margin: 0 auto; max-width: 800px; padding: 20px; } .date-sep { background: #f0f0f0; padding: 10px; margin: 20px 0; font-weight: bold; } .msg { margin-bottom: 15px; padding: 10px; border-radius: 5px; } .self { background: #e6f7ff; border-left: 3px solid #1890ff; } .other { background: #f9f9f9; border-left: 3px solid #d9d9d9; } .time { font-size: 0.85em; color: #999; } img { max-width: 100%; height: auto; } .search-highlight { background-color: yellow; } /style /head body h1{{ title }}/h1 {% for day, msgs in messages_by_day.items() %} div classdate-sep{{ day|strftime(%Y年%m月%d日) }}/div {% for msg in msgs %} div classmsg {{ self if msg.is_self else other }} div classsender{{ msg.sender_name }} span classtime{{ msg.timestamp|strftime(%H:%M:%S) }}/span/div div classcontent{{ msg.content|safe }}/div {% if msg.image_path %} img src{{ msg.image_path }} alt图片 {% endif %} /div {% endfor %} {% endfor %} script // 简单关键词高亮实际项目中建议用 lunr.js 或 fuse.js function highlight(text) { const keywords [重要, 明天, 会议]; // 可从命令行参数传入 let highlighted text; keywords.forEach(kw { const regex new RegExp((${kw}), gi); highlighted highlighted.replace(regex, span classsearch-highlight$1/span); }); return highlighted; } /script /body /html# export_html.py from jinja2 import Environment, FileSystemLoader import sqlite3 from datetime import datetime from wechat_decrypt import WeChatDecrypt def load_messages(db_path): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( SELECT MsgSvrID, Type, Content, CreateTime, Talker, IsSender FROM ChatMsg WHERE Type IN (1, 3, 34) -- 文本、图片、语音 ORDER BY CreateTime ASC ) rows cursor.fetchall() messages [] for row in rows: try: parsed WeChatDecrypt.parse_message(row[2]) # 时间统一转为 datetime 对象 dt datetime.fromtimestamp(parsed.timestamp / 1000.0) messages.append({ sender_name: row[4], is_self: bool(row[5]), content: parsed.content, timestamp: dt, image_path: fimages/{row[0]}.jpg if row[1] 3 else None, msg_type: row[1] }) except: continue # 跳过无法解析的消息 # 按天分组 from collections import defaultdict messages_by_day defaultdict(list) for msg in messages: day_key msg[timestamp].date() messages_by_day[day_key].append(msg) return dict(messages_by_day) env Environment(loaderFileSystemLoader(templates)) template env.get_template(chat.html.j2) data { title: 我的微信聊天记录2023-2024, messages_by_day: load_messages(EnMicroMsg_decrypted.db) } with open(chat_report.html, w, encodingutf-8) as f: f.write(template.render(data))逻辑说明Jinja2 模板分离 HTML 结构与数据逻辑strftime过滤器控制时间格式|safe允许 HTML 内容如br不被转义图片路径需提前将MsgAttach中的图片按MsgSvrID重命名存放至images/目录。关键参数max_width800px保证移动端阅读舒适font-family指定中文字体避免 fallback 到宋体meta charsetutf-8强制 UTF-8 防止中文乱码。3.2 Word 导出用 python-docx 实现自动目录、页眉页脚与样式控制Word 不是 HTML 的简单转换。它需要自动生成目录TOC每日标题设为 Heading 1每条消息为 Normal 样式页眉显示“微信聊天记录归档”页脚显示页码图片居中、带题注“图1XXX”表格用于展示统计摘要如总消息数、发送方占比# export_word.py from docx import Document from docx.shared import Inches, Pt from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.oxml.ns import qn from docx.oxml import OxmlElement import sqlite3 from wechat_decrypt import WeChatDecrypt from datetime import datetime def add_heading(document, text, level1): p document.add_heading(text, levellevel) p.alignment WD_PARAGRAPH_ALIGNMENT.LEFT return p def add_paragraph(document, text, styleNormal): p document.add_paragraph(text, stylestyle) p.alignment WD_PARAGRAPH_ALIGNMENT.LEFT return p def add_image(document, image_path, widthInches(4)): try: document.add_picture(image_path, widthwidth) except: add_paragraph(document, [图片缺失], styleCaption) document Document() # 设置默认字体 style document.styles[Normal] font style.font font.name 微软雅黑 font.size Pt(10) document.styles[Normal].font.name 微软雅黑 # 添加封面页 document.add_heading(微信聊天记录年度报告, 0) document.add_paragraph(生成时间 datetime.now().strftime(%Y年%m月%d日 %H:%M:%S)) document.add_page_break() # 添加目录需最后更新 document.add_heading(目录, 1) document.add_paragraph(请在 Word 中按 CtrlA 后右键选择“更新域”, styleCaption) # 加载并写入消息 messages_by_day load_messages(EnMicroMsg_decrypted.db) # 复用上一节函数 for day, msgs in sorted(messages_by_day.items()): add_heading(document, f{day.strftime(%Y年%m月%d日)}, level1) for msg in msgs: # 发送者时间 sender_time f{msg[sender_name]} | {msg[timestamp].strftime(%H:%M:%S)} add_paragraph(document, sender_time, styleHeading 2) # 消息内容 add_paragraph(document, msg[content]) # 图片 if msg[image_path]: add_image(document, msg[image_path]) # 插入统计摘要表格 document.add_heading(统计摘要, 1) table document.add_table(rows1, cols3) hdr_cells table.rows[0].cells hdr_cells[0].text 指标 hdr_cells[1].text 数值 hdr_cells[2].text 说明 row table.add_row().cells row[0].text 总消息数 row[1].text str(sum(len(v) for v in messages_by_day.values())) row[2].text 含文本、图片、语音 # 页眉页脚 section document.sections[0] header section.header header_para header.paragraphs[0] header_para.text 微信聊天记录归档 footer section.footer footer_para footer.paragraphs[0] footer_para.text 第 页码 页 document.save(chat_report.docx)参数说明add_heading(..., level1)生成 Heading 1Word 自动识别为 TOC 条目add_picture(..., widthInches(4))控制图片宽度防溢出document.add_page_break()强制分页提升可读性关键避坑点document.add_paragraph(..., styleCaption)必须显式指定样式名否则页脚页码无法正确关联页眉页脚必须在document.sections[0]上设置不能用document.add_heading()。3.3 CSV 导出用 pandas 处理字段转义与多值展开CSV 是分析基石但微信数据天然不适合扁平化一条消息可能含多个表情、多张图片、群聊中多个人。我们采用「一行一消息」原则对复杂字段做 JSON 序列化或字符串拼接。# export_csv.py import pandas as pd import sqlite3 from wechat_decrypt import WeChatDecrypt from datetime import datetime import json def build_csv_data(db_path): conn sqlite3.connect(db_path) cursor conn.cursor() cursor.execute( SELECT MsgSvrID, Type, Content, CreateTime, Talker, IsSender, Status FROM ChatMsg WHERE Type IN (1, 3, 34) ) rows cursor.fetchall() records [] for row in rows: try: parsed WeChatDecrypt.parse_message(row[2]) dt datetime.fromtimestamp(parsed.timestamp / 1000.0) # 处理多值字段图片路径列表、用户列表需从 content 中正则提取 images [] if row[1] 3: images [fimages/{row[0]}.jpg] at_users [] import re at_matches re.findall(r([^,\s]), parsed.content) if at_matches: at_users list(set(at_matches)) # 去重 records.append({ msg_id: row[0], msg_type: row[1], content: parsed.content.replace(\n, \\n).replace(,, ), # 换行转 \n逗号转全角防破坏 CSV send_time: dt.strftime(%Y-%m-%d %H:%M:%S), talker: row[4], is_self: Yes if row[5] else No, status: row[6], images: json.dumps(images, ensure_asciiFalse), # JSON 字符串 at_users: json.dumps(at_users, ensure_asciiFalse), is_recall: Yes if parsed.is_recall else No }) except: continue return pd.DataFrame(records) df build_csv_data(EnMicroMsg_decrypted.db) # 指定编码为 utf-8-sigExcel 才能正确识别中文 df.to_csv(chat_report.csv, indexFalse, encodingutf-8-sig)逻辑说明replace(,, )将英文逗号替换为全角逗号避免 CSV 字段分裂json.dumps(..., ensure_asciiFalse)保证中文不转义encodingutf-8-sig在文件开头写入 BOM使 Excel 默认用 UTF-8 打开。关键参数indexFalse禁用行号ensure_asciiFalse保留中文BOM 是 Excel 兼容性的“后悔药”没有它双击打开 CSV 会乱码。4. 避坑指南微信聊天记录导出中 5 个血泪经验换来的高频翻车点导出过程看似线性实则处处埋雷。以下是我踩过的、被上百个 GitHub Issue 验证过的真问题按现象→原因→解决三步给出可执行方案。4.1 现象HTML 页面中文全部显示为方块或问号原因HTML 文件保存时未声明 UTF-8 编码或浏览器未正确识别编码。常见于 Windows 记事本另存为时默认 ANSI 编码。解决用 VS Code 或 Notepad 打开 HTML 文件点击右下角编码显示如UTF-8若为GBK或ANSI点击切换为UTF-8 with BOMBOM 可选但更保险在head中强制添加meta charsetutf-8已写入模板但若手动编辑 HTML 请确认存在若仍无效在 Chrome 地址栏输入chrome://settings/fonts将“标准字体”设为“微软雅黑”。4.2 现象Word 文档中图片全部显示为“图片不可显示”原因python-docx插入图片时路径错误或图片文件未按MsgSvrID重命名或图片格式非.jpg/.png微信有时存.dat。解决确认images/目录存在且文件名与MsgSvrID严格一致如123456789.jpg用file命令检查图片格式file images/123456789.jpg若输出data说明是加密 blob需用wechat-decrypt的extract_image()方法解密见其 README替换add_picture()为容错版本try: document.add_picture(image_path, widthInches(4)) except Exception as e: add_paragraph(document, f[图片加载失败: {e}], styleCaption)4.3 现象CSV 用 Excel 打开全是乱码用记事本打开正常原因Excel 默认用系统区域编码如 GBK打开 CSV而文件是 UTF-8。解决永久方案在 Excel 中数据 → 从文本/CSV → 浏览选择文件 → 编码选UTF-8→ 加载临时方案用记事本打开 CSV → 另存为 → 编码选UTF-8-BOM→ 保存 → Excel 双击即可代码加固to_csv(..., encodingutf-8-sig)中的-sig即写入 BOM这是最可靠的自动化方案。4.4 现象导出的聊天时间比手机上看到的早/晚 8 小时原因CreateTime是服务器时间UTC0而微信客户端显示的是本地时区时间UTC8。parsed.timestamp是消息内嵌时间戳但部分旧版本微信未写入导致 fallback 到CreateTime。解决优先使用parsed.timestamp并显式转换datetime.fromtimestamp(ts/1000.0, tztimezone(timedelta(hours8)))若parsed.timestamp为空则用datetime.fromtimestamp(row[3]/1000.0, tztimezone(timedelta(hours8)))row[3]是CreateTime在 HTML/Word/CSV 中统一标注时区“北京时间UTC8”。4.5 现象WeChatKeyExtractor找不到微信进程或密钥为空原因微信 PC 客户端版本升级后内存布局变化旧版 Key Extractor 失效或微信未完全启动正在登录动画中。解决更新WeChatKeyExtractor到最新 releaseGitHub 页面顶部Latest release关闭所有微信进程任务管理器结束WeChat.exe、WeChat Helper.exe重启微信 PC 客户端等待 10 秒至主窗口完全加载后再运行工具若仍失败改用WeChatExporter项目GitHub 搜索它提供 GUI 界面并内置多版本密钥扫描逻辑成功率更高。5. 年度聊天报告进阶用 Pandas Matplotlib 做轻量级分析生成可嵌入 HTML 的图表导出只是起点分析才是价值所在。我们不做 LLM 精调这种重型方案而是用 50 行代码做出真正有用的年度洞察谁是你聊得最多的人哪天发消息最疯狂语音消息占比多少这些结论可直接嵌入 HTML 报告首页成为“数据眼”。5.1 用 Pandas 计算 4 个核心指标并生成 Markdown 表格# analysis.py import pandas as pd from datetime import datetime, timedelta df pd.read_csv(chat_report.csv, encodingutf-8-sig) # 1. 每日消息数趋势最近30天 df[send_time] pd.to_datetime(df[send_time]) daily_count df.groupby(df[send_time].dt.date).size().tail(30) daily_df pd.DataFrame({ 日期: daily_count.index.astype(str), 消息数: daily_count.values }) # 2. 发送方 TOP5 sender_count df[talker].value_counts().head(5) sender_df pd.DataFrame({ 联系人: sender_count.index, 消息数: sender_count.values }) # 3. 消息类型分布 type_dist df[msg_type].value_counts() type_map {1: 文本, 3: 图片, 34: 语音} type_df pd.DataFrame({ 类型: [type_map.get(t, 其他) for t in type_dist.index], 数量: type_dist.values }) # 4. 自己 vs 对方 发送占比 self_ratio df[is_self].value_counts(normalizeTrue) * 100 ratio_df pd.DataFrame({ 角色: [自己, 对方], 占比(%): [self_ratio.get(Yes, 0), self_ratio.get(No, 0)] }) # 输出为 Markdown 表格可直接粘贴进 HTML print(### 近30日消息趋势\n daily_df.to_markdown(indexFalse)) print(\n### 聊得最多的5位联系人\n sender_df.to_markdown(indexFalse)) print(\n### 消息类型分布\n type_df.to_markdown(indexFalse)) print(\n### 发送角色占比\n ratio_df.to_markdown(indexFalse))输出示例Markdown### 近30日消息趋势 | 日期 | 消息数 | |------------|------| | 2024-05-01 | 127 | | 2024-05-02 | 89 | ### 聊得最多的5位联系人 | 联系人 | 消息数 | |--------|------| | 张三 | 2451 | | 李四 | 1892 |5.2 用 Matplotlib 生成 PNG 图表并嵌入 HTML# plot_daily_trend.py import matplotlib.pyplot as plt import pandas as pd from datetime import datetime df pd.read_csv(chat_report.csv, encodingutf-8-sig) df[send_time] pd.to_datetime(df[send_time]) # 近30日趋势图 last_30 df[df[send_time] datetime.now() - timedelta(days30)] daily last_30.groupby(last_30[send_time].dt.date).size() plt.figure(figsize(10, 4)) plt.plot(daily.index, daily.values, markero, linewidth2, markersize4) plt.title(近30日消息发送趋势, fontsize14, fontweightbold) plt.xlabel(日期) plt.ylabel(消息数) plt.grid(True, alpha0.3) plt.xticks(rotation45) plt.tight_layout() plt.savefig(trend_chart.png, dpi150, bbox_inchestight) plt.close()逻辑说明figsize(10,4)控制宽高比适配 HTML 宽度dpi150保证清晰度bbox_inchestight防止坐标轴标签被裁剪生成trend_chart.png后只需在 HTML 模板中加一行img srctrend_chart.png alt趋势图即可嵌入。5.3 把分析结果整合进 HTML 报告首页修改templates/chat.html.j2在h1下添加分析区块h2年度洞察/h2 div classanalysis-section h3 近30日消息趋势/h3 img srctrend_chart.png alt趋势图 stylemax-width:100%; h3 聊得最多的联系人/h3 table border1 classdataframe theadtrth联系人/thth消息数/th/tr/thead tbody {% for _, row in sender_df.iterrows() %} trtd{{ row.联系人 }}/tdtd{{ row.消息数 }}/td/tr {% endfor %} /tbody /table /div关键技巧分析模块与聊天正文物理隔离用div classanalysis-sectionCSS 控制margin-top: 30px避免视觉压迫表格用原生 HTML 而非 JS 渲染确保离线可用所有图表文件.png与 HTML 同目录路径简洁可靠。我坚持把分析做成“可嵌入、可离线、可打印”的 HTML 块而不是独立 Dashboard。因为真正的归档需求是把一份文件发给 HR、发给家人、发给律师——它必须在没有网络、没有 Python 环境、没有数据库的情况下依然完整呈现数据与结论。这五年帮三十多位朋友导出微信记录最常听到的反馈是“没想到还能看出我去年和妈妈聊了 1278 条而和老板只聊了 3 条。” 数据不是冷的它只是需要被正确地解封、排列、照亮。希望帮到你。本文还有配套的精品资源点击获取
相关阅读
Happier代码审查完全指南:在手机上逐行审查AI Agent生成的Diff
【免费下载链接】happier Web, Desktop & Mobile client and orchestrator for Codex, Claude Code, OpenCode, Pi, Cursor, Grok, Antigravity, Kimi, Augment Code, Qwen, fully end-to-end encrypted 项目地址: https://gitcode.com/gh_mirrors/hap/happier …
2026/10/11 13:51:33 阅读全文 →