聊天记录导出:五个平台,五种互不兼容的同一件事
每个即时通讯平台都允许你导出数据,而每一个都自行决定了一段对话在磁盘上该长什么样。底下的内容是一样的——谁在什么时候说了什么——但形状毫无共同之处。有的是一份文本日志,时间戳格式取决于你手机的区域设置。有的是一棵以标识符为键的节点树,对话只是图里的一条路径。有的是一堆按频道和日期组织的文件,而人名存在完全另外的地方。
这个页面全都能读,并给出可读的文字稿。平台是从文件自身的结构判断的,不看文件名,所以你可以直接把导出丢进来,不必先告诉它这是什么。一切都在这个浏览器标签页里运行。
格式是怎么识别出来的
文件名在这里没用。这些平台里有一半把导出叫 conversations.json,何况文件在邮件和网盘之间流转时本来就会被改名。所以识别读的是结构:chat_messages 数组配上 sender 字段、mapping 对象配上 create_time、以字符串形式存放 Unix 时间戳的 ts 字段。每一个都是只有某一个平台才会产出的指纹。
有一个顺序上的细节很关键。iPhone 版 WhatsApp 的导出开头是 [12/03/2024, 14:23:01]——一个方括号,而 JSON 数组也是这么开头的。先测 JSON,就会把每一份 iPhone 聊天日志交给 JSON 解析器,然后失败。所以 WhatsApp 的行格式要在方括号判断之前检查,不能在之后。
五种形状
- WhatsApp 写的是纯文本日志,一行一条消息,时间戳格式随系统和区域而变。多行消息以没有任何标记的裸行续写,所以解析器必须把任何不带时间戳的行当成上一行的续行。
- ChatGPT 把每段对话存成一张节点图。每一次重新生成的回答、每一次编辑过的提问都作为分支保留下来,所以要还原你当时真正看到的内容,得从根节点出发、每一步都沿着最后一个子节点往下走。
- Claude 用的是扁平数组,是五者里最好处理的。唯一的皱褶是较新的导出把消息文本放在带类型的内容块列表里,较老的用一个普通字符串字段,两种都要读。
- Slack 是一棵目录树:一个频道一个文件夹,一天一个 JSON 文件,外加单独一份存放身份映射的
users.json。消息里指代人的方式是U0123ABCD,没有那份文件,每条消息的作者就只是一串代号。 - Discord 的导出(社区常用工具产出的那种)是一个对象,包含频道元数据和一个扁平的消息数组。直白,也最接近你从零设计时会写出来的样子。
哪些东西会被删掉
一份原始导出里有大量没人想读的内容。默认会去掉,开关可以把它们请回来:
系统通知。每份 WhatsApp 导出开头那条加密提示、入群退群、群名变更、「此消息已删除」、Slack 的 channel_join 子类型。在一个人来人往的群里,这些可能比真正的消息还多。
附件占位符。凡是有图片或语音的地方都会出现 <Media omitted>,而在一个以照片为主的聊天里,这就是文件的大部分。它只告诉你「发生过什么」,却不说是什么。
看不见的管道。ChatGPT 的导出里包含系统提示词和各种工具调用流量,并被标记为不在对话中显示。这些从来没有呈现给你,所以会被丢掉。
压缩包也能直接处理
Slack 和 ChatGPT 给的是 .zip 而不是散装 JSON,所以压缩包可以直接拖进来。读取器会先找 conversations.json,因为只要它在,它就是整份导出。找不到,就按 Slack 工作区来处理:先加载 users.json 好让身份能解析出来,再遍历按天分的文件,按日期顺序把每个频道汇成一份文字稿。不认识的文件会被跳过,而不是让整个压缩包失败。
为什么这件事在你的浏览器里做
在本站的所有输入里,这一类最要紧。一份聊天记录导出不是你写给读者看的文档——它是好几年的私人对话,里面说过什么就有什么。住址。健康。钱。还有关于其他人的事,而那些人从没同意过这一切。
把它上传给一个转换服务,等于要去信任一个陌生人的留存政策、他的访问控制、他的备份,以及他的未来。这里的解析是跑在这个标签页里的 JavaScript。你的文件由浏览器读取、在内存里转换、呈现给你。没有上传这一步,所以没有东西需要留存,也没有东西需要删除。这样做还顺带意味着它的运行成本为零,页面加载之后离线也能用。
各平台专页
如果你清楚自己手上是什么,各平台的专页还附带该平台的导出方法:WhatsApp、ChatGPT、Claude 和 Slack。它们跑的是同一个解析器,结果没有区别。