简介面向需要长期保存、导出与分析微信聊天记录的用户和开发者该资源提供了一套从备份提取、格式转换到年度报告生成的全流程方案涵盖聊天记录迁移、解析工具选型、数据可视化与隐私安全等要点既适合普通用户留存珍贵回忆也方便技术人员进一步做数据挖掘和二次开发。压缩包共238个文件约25MB其中94个Python脚本用于解析备份与格式转换17个HTML模板用于报告展示61个PNG和36个SVG图表素材用于统计图形呈现另含配置、说明文档及可执行程序目录结构清晰便于按需取用。已有646人学习下载。借助内置的解析与导出脚本可将微信备份转为网页、Word、CSV等格式并保留图文信息通过可视化模板可生成词云、活跃时段统计等年度报告适合希望永久留存聊天内容、或进一步做微信数据分析和二次开发的读者参考。1. 提取微信聊天记录并永久保存先看清加密库再谈导出和年度报告很多人以为微信聊天记录只能靠滚动截屏或者第三方备份工具一条条导真到自己想永久保存时才发现微信 PC 端把全部聊天内容存在本机的一个加密 SQLite 数据库里图片和语音也以加密的 dat 文件形式躺在磁盘上。做提取这件事第一步不是写导出脚本而是先把数据库解密、读懂表结构然后才能把它变成 HTML、Word、CSV 三种文档最后再基于同一份导出数据生成年度聊天报告。这套流程适合有大量重要对话需要归档的个人用户、想复盘自己一年沟通习惯的从业者以及需要把聊天数据交接给非技术人员的场景。难点不在“导出”这个动作而在解密、字段映射、编码和增量维护这些看不见的坑上。2. 先把加密库变成可读数据库文件定位、解密与最小验证2.1 数据库文件在哪三个常见位置与文件命名微信 PC 版不像手机端那样把数据存在私有沙箱里它把数据写进了本机用户目录。常见做法是直接搜索两个关键目录一个是资源文件目录另一个是数据库所在目录。不同版本位置略有差异但核心规律是看 “WeChat Files” 或 “xwechat_files” 这两个名字。老版本的数据一般位于%APPDATA%\Tencent\WeChat\WeChat Files\下每个微信号一个独立文件夹里面是Msg、Image、FileStorage等子目录新版本则在%APPDATA%\Tencent\WeChat\xwechat_files\下按微信号拆成多个目录。真正存放聊天记录的数据库是MSG.db这是一个几十 MB 到几个 GB 不等的 SQLite 文件表结构里保存了消息类型、发送者、时间戳、内容等字段。辅助库还有MicroMsg.db存联系人、ChatMsg.db部分版本存消息索引等。定位数据库时我一般不用文件管理器翻直接开一个终端跑下面这段命令把候选路径全部列出来dir /s /b %APPDATA%\Tencent\WeChat\*MSG*.db 2nul dir /s /b %APPDATA%\Tencent\WeChat\*MicroMsg*.db 2nul第一行是查找主消息库第二行是查找联系人库。如果这两个命令什么都没输出说明微信版本较新数据目录挪到了Documents下的 “WeChat Files” 或xwechat_files可以把路径替换成%USERPROFILE%\Documents\WeChat Files再跑一遍。找到文件后先复制一份副本再操作不要直接拿原始库去做解密和导出因为后续脚本如果有误写操作原始库一坏就再也没有后悔药了。2.2 解密最小路径拿到密钥、导出为可读 DB加密库用的是 SQLCipher直接拿 sqlite3 打开会报 “file is not a database”。SQLCipher 需要密钥才能打开而密钥不在任何配置文件中它由微信在启动时生成并保存在内存里。所以常见做法是让微信保持登录状态用工具从运行中的微信进程里读取密钥然后用这个密钥把加密库导出成非加密库。这里给出一套不依赖任何图形界面、命令行可复现的最小路径。前提是你已经安装了 Python 3 和 sqlcipher 命令行工具并且微信 PC 端正在运行# 1. 从内存/运行进程中读取密钥写入 key.txt # 常见工具会把密钥以 hex 形式输出 wechat_key_dumper key.txt # 2. 用 sqlcipher 解开加密库并导出为明文库 sqlcipher MSG.db \ PRAGMA keyxhex; \ PRAGMA cipher_migrate; \ ATTACH DATABASE MSG_plain.db AS plaintext KEY ; \ SELECT sqlcipher_export(plaintext); \ DETACH DATABASE plaintext; # 3. 验证明文库可读 sqlite3 MSG_plain.db SELECT COUNT(*) FROM MSG;第二步里的PRAGMA key是从key.txt读出来的密钥注意 SQLCipher 4 的 hex 密钥写法必须是x...这种格式少了x前缀会直接报 key 错误。PRAGMA cipher_migrate是为了兼容老版本微信留下的旧加密规格如果密钥正确但打开后乱码多半是少了这一步。ATTACH ... AS plaintext和sqlcipher_export的作用是把整个库“另存”成一个无密钥的新库之后就能用普通 sqlite3 工具或 Python 直接读取了。最后一步的COUNT(*)是验证手段如果返回几万到几十万行的数字说明库和解密流程都通了。2.3 导出的第一道验收表结构、行数与消息字段拿到MSG_plain.db后先别急着写导出脚本先去确认表结构和字段名。微信的库结构在不同版本间有调整字段名并不完全固定盲目按网上旧脚本写死了字段名会在某个版本上直接翻车。我先用 Python 快速看一遍表结构和样例数据import sqlite3 conn sqlite3.connect(MSG_plain.db) cur conn.cursor() # 列出所有表名 cur.execute(SELECT name FROM sqlite_master WHERE typetable;) for row in cur.fetchall(): print(row[0]) # 查看消息表的字段 cur.execute(PRAGMA table_info(MSG);) for desc in cur.fetchall(): print(desc[1], desc[2]) # 字段名, 字段类型 # 抽样 5 条消息确认内容字段和时间字段 cur.execute( SELECT createTime, isSender, type, talker, content FROM MSG ORDER BY createTime DESC LIMIT 5; ) for row in cur.fetchall(): print(row)这段代码的作用是把数据库的黑匣子打开给观众看字段名、类型、样例值一次全部露出。PRAGMA table_info(MSG)是关键它告诉我们到底有没有isSender、createTime、talker、content这些字段以及它们叫什么名字。微信部分版本把发送者字段命名为isSend把时间字段命名为msgCreateTime把会话 ID 命名为strTalker如果脚本写死了旧名字导出来全是空值。先跑这 3 步再写导出逻辑能把后面所有的排查工作拦在起点。3. 导出 HTML、Word、CSV三套格式的落地脚本与取舍3.1 导出 HTML静态网页版聊天记录怎么做HTML 是最灵活的导出格式适合永久保存和长线阅读。我的目标不是生成一个花哨的动态网页而是生成一个零依赖、双击就能打开的静态 HTML搜索框过滤消息、按日期分块、左侧联系人列表、消息气泡式排版。只要一个文件谁拿到都能看。实现上我用 Python 从解密库里读消息然后拼接 HTML 字符串。关键点有两个一是所有样式内联或写进style不要引用外部 CSS保证单文件可迁移二是图片、语音、文件路径统一用相对路径导出目录结构固定为html/网页文件 files/附件。import sqlite3, html, datetime conn sqlite3.connect(MSG_plain.db) rows conn.execute( SELECT createTime, isSender, content FROM MSG WHERE type1 ORDER BY createTime; ).fetchall() blocks [] for ts, is_sender, content in rows: dt datetime.datetime.fromtimestamp(ts / 1000) cls me if is_sender else them content html.escape(str(content)) blocks.append( fdiv classmsg {cls} fspan classtime{dt:%Y-%m-%d %H:%M}/span fspan classtext{content}/span f/div ) page f!doctype html html langzh-cnheadmeta charsetutf-8 title微信聊天记录导出/title style body {{ max-width: 800px; margin: 40px auto; font-family: sans-serif; }} .msg {{ margin: 8px 0; padding: 8px; border-radius: 8px; }} .me {{ background: #d3f0d3; text-align: right; }} .them {{ background: #f2f2f2; }} .time {{ color: #999; font-size: 12px; margin-right: 8px; }} /style/head body h1微信聊天记录导出/h1 {.join(blocks)} /body/html with open(chat.html, w, encodingutf-8) as f: f.write(page) print(HTML 生成完成:, len(rows), 条消息)这段代码的核心是cls变量发送方为自己时用绿色右对齐气泡对方为灰色左对齐气泡看存档时有非常强的可读性。html.escape必须加否则消息里的、、会把网页结构撑破。时间字段createTime是毫秒级时间戳所以要用ts / 1000转成秒再格式化成可读时间。生成之后用浏览器打开chat.html检查搜索、滚动、样式是否正常图片路径后面再补充。3.2 导出 Word一张参数表把版式改明白Word 导出的场景通常是给完全不懂技术的领导和家人看他们只认 Word。用python-docx生成 Word 比 HTML 省事得多但坑在样式表格列宽不听话、中文字体丢失、长对话分页混乱。我一般直接用表格承载消息列表三列分别是时间、发送方向、内容然后固定列宽和字体避免 Word 在不同机器上渲染失效。from docx import Document from docx.shared import Pt, Cm import sqlite3 doc Document() table doc.add_table(rows1, cols3) table.style Table Grid # 固定列宽时间 3cm方向 1.5cm内容自适应 for row in table.rows: row.cells[0].width Cm(3) row.cells[1].width Cm(1.5) # 内容列不设宽让 Word 默认撑开 conn sqlite3.connect(MSG_plain.db) rows conn.execute( SELECT createTime, isSender, content FROM MSG WHERE type1 ORDER BY createTime LIMIT 5000; ).fetchall() for ts, is_sender, content in rows: cells table.add_row().cells direction 我 if is_sender else 对方 cells[0].text datetime.datetime.fromtimestamp(ts / 1000).strftime(%Y-%m-%d %H:%M) cells[1].text direction cells[2].text str(content) doc.save(chat.docx)LIMIT 5000是故意写的因为几百 MB 的聊天记录一次性全塞进 Word 会导致文档卡死、打开缓慢甚至 Office 直接提示修复。Word 更适合做“精选片段”或“最近一年”的导出不可能也不应该替代 HTML 承担全量存档。列宽这里只设置了时间列和方向列内容列让它自然自适应这是血泪经验如果你强行把三列都设成固定宽度在 Windows 的 Word 里内容太长就会发生列宽无法拖动的现象越调越乱。中文字体方面python-docx默认的字体在某些系统上回退到宋体这是可接受的如果想更好看可以在doc.styles[Normal].font.name 微软雅黑里显式指定。3.3 导出 CSV字段设计、字符编码与 Excel 打开不乱的细节CSV 是给数据分析和程序化处理用的格式不需要可读性但需要干净的结构和被生态广泛支持。导出 CSV 时我推荐直接全量导出不要加 LIMIT因为 CSV 没有渲染压力几十万行也毫无问题。字段设计上至少要有时间戳、日期、发送方向、类型、会话ID、内容六列这会直接决定后续年度聊天报告能不能顺利做。import csv, sqlite3 conn sqlite3.connect(MSG_plain.db) rows conn.execute( SELECT createTime, isSender, type, talker, content FROM MSG; ).fetchall() with open(chat.csv, w, encodingutf-8-sig, newline) as f: writer csv.writer(f) writer.writerow([createTime, date, isSender, type, talker, content]) for ts, is_sender, msg_type, talker, content in rows: date datetime.datetime.fromtimestamp(ts / 1000).strftime(%Y-%m-%d %H:%M:%S) writer.writerow([ts, date, is_sender, msg_type, talker, content]) print(CSV 导出行数:, len(rows))编码必须用utf-8-sig这不是玄学而是在 Windows 上用 Excel 直接打开 CSV 时只有带 BOM 的 UTF-8 才能被正确识别为中文编码。如果用普通的utf-8Excel 打开后中文全是乱码虽然用记事本看没问题但那已经不是 CSV 跨生态交换的本意了。newline是 Python csv 模块的标准要求不加的话每行之间会多出空行。导完以后用 pandasread_csv读一遍校验字段数量这就是后面年度报告的输入文件。4. 从 CSV 到年度聊天报告统计口径、词频与可视化输出4.1 数据口径一年报告统计哪些指标怎么定年度聊天报告不是把消息数据简单求和而是定下一套可解释的口径否则做出来的数字自相矛盾。我的默认指标是总消息数、总字数、活跃天数、日均消息数、最长连续聊天天数、深夜聊天次数占比、消息最多的一天、Top10 联系人、高频词 Top20。这些指标看起来多实际在 pandas 里只需要几条聚合语句。口径上有一个容易混淆的点总字数是只统计type1的文本消息还是把图片、语音、文件都算进去我建议文字报告只统计文本消息但单独列一项“图片数量”和“语音数量”作为附属指标。理由是后续生成的词频依赖文本图片数量可以反映沟通密度而非表达密度。时间维度上以本地时间为准一天的定义是 00:00-23:59跨年那天的时间戳要小心转换。4.2 统计脚本基于 pandas 的指标计算代码CSV 已经导出下一步直接用 pandas 读进来算import pandas as pd df pd.read_csv(chat.csv) df[date] pd.to_datetime(df[date]) df[hour] df[date].dt.hour df[day] df[date].dt.date # 基础指标 total_msgs len(df) total_chars df[content].astype(str).str.len().sum() active_days df[day].nunique() avg_msgs_per_day total_msgs / active_days # 深夜时段23点-5点 night_mask df[hour].isin([23, 0, 1, 2, 3, 4, 5]) night_rate night_mask.mean() # 最活跃的一天 busiest_day df.groupby(day).size().idxmax() busiest_day_count df.groupby(day).size().max() # Top10 联系人 top_talkers ( df.groupby(talker) .agg(消息数(content, count), 字数(content, lambda s: s.astype(str).str.len().sum())) .sort_values(消息数, ascendingFalse) .head(10) ) print(总消息数:, total_msgs) print(总字数:, total_chars) print(活跃天数:, active_days) print(日均消息数:, avg_msgs_per_day) print(深夜消息占比:, f{night_rate:.1%}) print(最活跃的一天:, busiest_day, busiest_day_count) print(top_talkers)df[hour] df[date].dt.hour是后面深夜统计的基础isdin方式判深夜比写多个比较条件更清晰。talker字段在微信里是加密后的会话 ID 而非联系人名字所以 Top10 出来是一串字母数字这点要靠前面导出 CSV 时同时把MicroMsg.db里的联系人映射表也导出来做一个talker - 备注名的字典报告才有人味。4.3 报告怎么“年度化”对比、趋势图的落地单看一年总量没有说服力年度报告的真正价值在于对比今年和去年比哪个月聊天最频繁什么时候彻底断联。做对比的前提是至少有两年的 CSV 数据所以我建议 CSV 文件名带上年份例如chat_2023.csv、chat_2024.csv而不是一个笼统的chat.csv。import pandas as pd def load_year(path): df pd.read_csv(path) df[date] pd.to_datetime(df[date]) df[month] df[date].dt.to_period(M) return df df_old load_year(chat_2023.csv) df_new load_year(chat_2024.csv) # 两年月度消息趋势对比 monthly_old df_old.groupby(month).size() monthly_new df_new.groupby(month).size() trend pd.DataFrame({2023: monthly_old, 2024: monthly_new}).fillna(0) print(trend) # 活跃时段对比 for name, df in [(2023, df_old), (2024, df_new)]: hourly df[date].dt.hour.value_counts().sort_index() print(name, 凌晨3点消息数:, hourly.get(3, 0))这段代码把年度对比落到按月趋势和凌晨活跃度两个维度。fillna(0)很关键某个月完全没数据时 pandas 会产生 NaN不填零画图和计算都会出问题。导出年度聊天报告时我一般把 trends 转成 JSON 给 HTML 模板渲染如果能接一个图表库就画柱状图和折线图不接图表库就输出一张 markdown 风格表格放在 Word 里依然可读。词频统计我用jieba分词之后直接collections.Counter取 Top20注意要去掉停用词import jieba, re from collections import Counter def extract_keywords(content_list, topn20): stopwords {嗯, 啊, 了, 的, 在, 是, 我, 你} words [] for text in content_list: for w in jieba.cut(str(text)): if len(w) 2 and w not in stopwords and re.search(r[\u4e00-\u9fff], w): words.append(w) counter Counter(words) return counter.most_common(topn) keywords extract_keywords(df[content].tolist()) print(keywords)len(w) 2把单字切出来的碎片全过滤掉re.search只保留纯中文词汇数字、URL、英文缩写在个人年度报告里基本都是噪音。把 keywords 输出成keywords.json或直接放进 HTML 模板的标签云报告的可读性会立刻上一个台阶。5. 全流程避坑与排查解密失败、乱码、HTML 体积失控的现场记录5.1 解密失败与密钥失效两种高频报错的现场处置现象sqlcipher MSG.db之后命令返回file is not a database或SqliteError: key密钥看起来没错但库就是打不开。原因这类报错通常有两种。第一种是 PC 微信升级到了 4.x数据库加密方式整体调整旧版工具读出的密钥格式不再是 64 位 hex或者库文件本身被迁移到了新路径第二种是微信重新登录后内存中的密钥已经更换而key.txt里存的还是旧进程的密钥。解决先确认微信进程是否正在登录状态如果最近重新登录过必须重新执行读取密钥的步骤不能沿用旧文件。然后检查库文件的头部用xxd MSG.db | head -n1看前 16 字节是SQLite format 3\x00就是明文库是乱码则确认加密库。对 4.x 的库找支持新版本的工具而不是硬凑旧命令同时考虑回退微信版本批量导出后再升级回新版这是最笨但最省时间的路径。5.2 CSV 在 Excel 里“乱码”不是编码错是缺了 BOM现象chat.csv用记事本打开正常用 Excel 直接打开变成一堆我开头的乱码。这是新手遇到最多的一个问题而且容易被误判成数据损坏。原因Excel 在 Windows 区域设置下默认用 ANSIGBK去解析无 BOM 的 UTF-8 文件UTF-8 的中文字节被 GBK 逐字节解码自然成了乱码。解决写 CSV 时用encodingutf-8-sig这个编码会自动写入 BOM 头EF BB BFExcel 一旦看到 BOM 就会切到 UTF-8 解析。已经生成的乱码文件不用重导用 Python 读一次再写一次就行with open(chat.csv, r, encodingutf-8) as f: text f.read() with open(chat_fixed.csv, w, encodingutf-8-sig) as f: f.write(text)5.3 HTML 导出后图片全挂路径与 dat 文件解密的坑现象chat.html在浏览器里能打开但聊天记录里的图片位置全是裂图只显示一个小方块。原因微信把图片文件存成了加密的.dat格式文件名通常是一个 MD5 再加.dat后缀直接以相对路径引用到浏览器浏览器无法解码加密内容自然无法显示。解决导出 HTML 前先遍历微信的Image目录用 dat 文件查看器或简单脚本把.dat转成.jpg/.png。微信图片 dat 的解密算法很固定取文件第一个字节和异或字节 0x06算出偏移后逐字节异或即可还原。转换完再让 HTML 模板引用新生成的.jpg路径。另外注意相对路径HTML 在html/下图片在html/images/下写成images/xxx.jpg不要写成/images/xxx.jpg后者会被浏览器解析成磁盘根路径导致再次全挂。5.4 年度报告的“跨年”统计错误时区与消息时间戳的边界现象年度报告里 1 月 1 日凌晨的数据被算到了上一年或者 12 月 31 日晚 23:59 的消息跑到第二年导致月度趋势线在跨年点突然断崖。原因createTime是毫秒级 Unix 时间戳默认是 UTC 时区直接转本地时间时没有加时区偏移。在北京时间 UTC8 的场景下UTC 时间的 1 月 1 日 00:00 对应本地 1 月 1 日 08:00如果只按 UTC 日期分组就会出现早八点前消息归属错误。解决统一用 pandas 的带时区转换处理时间戳而不是datetime.fromtimestampdf[date] pd.to_datetime(df[createTime], unitms, utcTrue) df[date] df[date].dt.tz_convert(Asia/Shanghai) df[day] df[date].dt.dateutcTrue先明确时间戳的基准时区tz_convert(Asia/Shanghai)再转成本地时间最后dt.date取到的日期才正确。年度报告的所有分组统计都应基于这个处理后的date列不要在原始createTime上直接fromtimestamp。5.5 数据库越滚越大压缩、备份与增量导出的收益边界现象MSG_plain.db导出一次后过了半年再导出发现明文库体积已经 2GB每次解密导出耗时十几分钟磁盘空间吃紧。原因微信数据库本身在膨胀每导一次又生成一份全量明文副本等于两倍体积数据库文件在 SQLite 删除数据后不会自动收缩长期更新导出的旧文件会越积越大。解决导出后立刻sqlite3 MSG_plain.db VACUUM;压缩一次能释放大量空闲页。备份策略上不要每次全量导每年 1 月导一次全量作为基底之后每季度只增量导出“新增时间戳”范围的数据用WHERE createTime ?按最大时间戳切分。增量导出生成的 CSV 与往年 CSV 合并计算年度报告逻辑完全一致但耗时少 80%。数据库文件保留原始加密库明文库导完即可删除不要长期留存两份敏感数据。6. 进阶把导出做成每月一次的例行归档顺手验证结果到了这一步完整流程已经能跑通但我不建议你每次打开电脑再手动执行这五六步那一定会被遗忘。我的做法是把整条链路封装成一个脚本入口每月执行一次并自动输出校验报告。# 每天定时执行的核心步骤 python dump_keys.py # 从运行中的微信进程导出密钥 python decrypt_db.py # 解密 MSG.db 生成 MSG_plain.db python export_csv.py --year 2024 --output chat_2024.csv python export_html.py --input chat_2024.csv --output chat_2024.html python export_word.py --input chat_2024.csv --output chat_2024.docx --limit 3000执行完之后写一个简单的验证函数统计今天导出的行数和上个月对比如果行数小于上月 10%就打印警告提醒我关注是否漏了某段会话。年度报告在 1 月 1 日自动生成指标全部来自上一年合并后的 CSV这样每年拿到报告时所有数字都是稳定可复用的不会因为工具重装而丢失历史口径。这个流程我跑了两年最大的感触是导出 HTML 的意义并不在于给所有人看而在于给了自己一个随时可检索、可回看的时间胶囊CSV 则保证了未来哪怕换任何分析工具历史数据都是干净的、可迁移的。建议你先拿一个小号或聊天记录较少的号走一遍全流程确认每一步的输出都能对上再切到主力号做全量导出过程中把每一版脚本都加上日期后缀给未来的自己留条退路。希望这些经验对你有帮助让你的聊天记录真正成为可长期留存、可回头分析的个人数据资产。本文还有配套的精品资源点击获取