前几天整理电脑文件,偶然翻出去年年初第一次打开 DeepSeek 时随手截的一张图。那时候还在想这玩意儿到底能干啥,结果一年过去,我发现自己每天说得最多的话,居然不是跟同事,不是跟家人,而是跟这个对话框。于是我做了一个有点无聊但又特别上头的决定:把这一整年的 DeepSeek 聊天记录全部导出来,做成一份属于自己的年度报告。这篇文就记录一下完整的操作路线,从导出、清洗、统计到可视化,以及中间踩过的各种坑。适合所有想把自己和 AI 的对话数据变成一份可读报告的人,也适合想做轻量级个人数据项目、但不知道从哪里下手的读者。
1. 为什么要把聊天记录做成“数字年轮”
1.1 聊天框里藏着最真实的年度流水账
聊天记录这东西,比日记诚实多了。日记还会斟酌措辞,会隐藏情绪,但深夜两点对着 AI 输出去的那句“为什么我写的代码又跑不通”,是完全没有伪装的。我翻了一遍自己这一年的对话,发现里面什么都有:写公众号选题时的头脑风暴、改 bug 改到崩溃的抱怨、看论文时随手丢进去的 PDF 段落、过年回家做菜临时查的菜谱、甚至还有几次凌晨三点情绪低落的碎碎念。
如果你也习惯把 DeepSeek 当成日常外挂大脑,你的对话历史其实就是一份天然的个人数字痕迹。它记录了你关注什么、焦虑什么、什么时候最活跃、什么时候效率最高。比起朋友圈那种精心编排的年度总结,聊天框里的数据更像流水账,但恰恰是这种流水账,才最接近真实的一年。
1.2 参考音乐年度报告,但不照搬
很多人听到“年度报告”四个字,第一反应是网易云音乐那种风格:配色好看、文案扎心、能分享朋友圈。我承认这个形式确实值得借鉴,但音乐报告能火,靠的是“情绪共鸣+数据惊喜”,把一首歌和一个瞬间绑定起来。而聊天记录本身比播放记录私密得多,也丰富得多,它天然带着上下文,每个数据背后都有一段具体的故事。
所以我的设计原则很明确:不追求页面多花哨,追求“让数字替我说出这一年的变化”。比如凌晨三点的提问次数、提问里高频出现的报错信息、AI 回复里出现最多的那句“你可以尝试”,这些数字堆在一起,不需要华丽词藻,就已经有冲击力了。你要做的只是把数据挖出来,再用一个舒服的排版呈现给读者,也就是你自己。
2. 三步导出聊天记录:三种方案与最终取舍
2.1 方案一:走官方 API 导出(适合 API 重度用户)
如果你平时主要不是用网页对话框,而是把 DeepSeek 接入到自己的工具链里,那情况其实更简单。所有经过 API 的对话请求,理论上都会经过你自己的服务端或调用日志。你只需要去捞自己的请求记录,把 prompt 和 response 配对归档即可。
我见过一些朋友会把这类调用记录直接存进数据库或者日志文件,那一步基本等于免检。但大部分人——包括我——日常还是在网页端对话为主,API 只用来跑一些自动化任务。这种情况下,网页端的对话历史才是我最需要的原始数据源。
注意:如果你平时只调 API 但没有留存请求日志,那这部分对话是找不回来的。我的习惯是从今天开始做请求日志归档,哪怕只是把每次调用的输入输出追加到一个 JSONL 文件里,年底做统计时就会非常省事。
2.2 方案二:浏览器开发者工具抓包(适合网页端用户)
这是我最推荐的方案,也是最终采用的主路径。原理很简单,网页端的所有页面展示都得靠接口返回数据,你只需要把那些接口拿到手就行。
具体操作步骤:
- 用 Chrome 或 Edge 打开 DeepSeek 对话页面,按 F12 打开开发者工具,切到 Network 面板。
- 在过滤栏里输入
chat或session之类关键词,把接口请求筛选出来。 - 刷新页面,或者点击左侧的会话列表,观察哪几个请求返回了对话内容。
- 找到返回 JSON 数据的请求,右键 Copy,选择 Copy as cURL,或者直接在 Preview 面板里逐条查看。
- 把所有会话的 JSON 响应保存到本地,拼接成一个文件。
这里有个关键点:很多网页端的会话列表是分页懒加载的,你必须手动滚动左侧列表,让所有历史会话都被请求一遍,否则导出的数据会缺一大截。我第一次抓的时候只滚了前几十条,统计出来的对话量少得可怜,后来才发现是没滚到底。
2.3 方案三:手动复制与第三方扩展(备胎方案)
如果你的历史对话量不大,手动复制粘贴也是一种办法。直接把对话内容粘贴进一个 txt 文件,逐条整理就行。缺点显而易见:效率低,格式混乱。但胜在零门槛,任何人都能做。
另外,浏览器扩展里有一些“一键导出 Chat 历史”的工具,理论上能用,但我不太推荐。倒不是工具本身有问题,而是这类扩展通常需要读取你的页面数据权限,安全上存在一定风险。如果你不在意隐私泄露,那可以用;如果在意的,还是抓包或手动复制最稳妥。
我最终的实际路径是:主力用方案二抓包,把网页端的 JSON 全量导出来;方案一作为补充,把我自己 API 调用日志里的对话也合并进去。两份数据合并后,基本覆盖了我这一年的全部对话。
3. 数据清洗:将“聊天记录”变成“数据集”
3.1 先搞懂导出的数据长什么样
抓包拿到的 JSON,一般结构是嵌套的,最外层是会话列表,里面每条会话包含标题、创建时间、更新时间,以及一个 messages 数组。每个 message 对象大致有以下几个字段:
{ "role": "user", "content": "为什么我的 Python 脚本运行这么慢?", "create_time": 1709273452000 }其中 role 有user和assistant两种,分别代表你和 AI;content 是文本正文;create_time 是毫秒级时间戳。部分版本可能还会带model、tokens之类的字段,统计时也能用到。
搞清楚数据结构之后,清洗工作就清晰了:把 JSON 拆成一条条消息,按时间排序,区分角色,剩下的就是统计的事。
3.2 清洗思路与 Python 实现
我平时不太写复杂脚本,但应付这种数据清洗,Python 加几个基础库就够了。核心操作可以分三步走。
第一步,读取 JSON 并遍历所有会话,把所有消息合并到一个大列表里:
import json with open('deepseek_sessions.json', 'r', encoding='utf-8') as f: sessions = json.load(f) all_messages = [] for session in sessions: for msg in session.get('messages', []): all_messages.append({ 'role': msg.get('role'), 'content': msg.get('content', ''), 'time': msg.get('create_time', 0) }) all_messages.sort(key=lambda x: x['time']) print(f"共解析到 {len(all_messages)} 条消息")如果你抓包时保存的是多个响应文件,那就循环读取多个 JSON 文件再合并,逻辑是一样的。
第二步,清洗内容字段。把常见的系统提示、空消息、纯 markdown 标记剔除掉,尤其注意 content 里可能出现多余的换行符和转义字符:
import re def clean_content(text): # 去掉代码块标记 text = re.sub(r'```[\w]*\n?', '', text) # 去掉图片占位符 text = re.sub(r'!\[.*?\]\(.*?\)', '', text) # 压缩连续换行 text = re.sub(r'\n{3,}', '\n\n', text) return text.strip() for msg in all_messages: msg['content'] = clean_content(msg['content'])第三步,做基础统计:总消息数、用户消息数、AI 回复数、总字数、日均对话轮次等。这些数字本身就能撑起年度报告的一半内容。
user_msgs = [m for m in all_messages if m['role'] == 'user'] assistant_msgs = [m for m in all_messages if m['role'] == 'assistant'] total_chars = sum(len(m['content']) for m in all_messages) user_chars = sum(len(m['content']) for m in user_msgs) print(f"用户消息: {len(user_msgs)} 条,总字数 {user_chars}") print(f"AI 回复: {len(assistant_msgs)} 条,总字数 {total_chars - user_chars}")3.3 大文本处理要注意的坑
有一年数据量大的话,直接一次性把全部内容读进内存可能会卡顿。我自己的处理方式是分批统计,比如按月把消息分组,每个月单独算完再汇总,这样既不占内存,还能顺便做出“月度活跃度”数据。
另一个坑是编码问题。Web 网页返回的 JSON 一般是 UTF-8,但如果你的导出文件经过了某些编辑器保存,可能变成 GBK 或带 BOM 头。读取时建议显式指定encoding='utf-8',遇到异常可以用errors='ignore'临时容错,但最好还是把源文件统一转码一遍。
还有一个小细节:content 里可能出现\u2019这类 Unicode 编码的字符,统计字数时不会被忽略,需要先转成中文再清洗。用text.encode('utf-8').decode('unicode_escape')可以处理一部分,但更稳妥的是直接在读取 JSON 时用ensure_ascii=False的原始文件,避免二次转义。
4. 分析维度设计:让数据替你讲故事
4.1 时间维度:你几点最活跃
数据清洗完,我第一个分析的维度是时间。把每条消息的create_time转换成可读时间,然后按小时、按月做聚合统计。
四小时维度的分析代码如下:
from datetime import datetime def hour_of(msg): return datetime.fromtimestamp(msg['time'] / 1000).hour hour_counter = {} for msg in user_msgs: h = hour_of(msg) hour_counter[h] = hour_counter.get(h, 0) + 1 # 按小时输出 for h in range(24): count = hour_counter.get(h, 0) bar = '#' * (count // 5) print(f"{h:02d}:00 {count:4d} {bar}")分析出来之后,我挺意外的。我一直觉得自己是夜间型选手,但数据显示我白天上午十点左右的提问频率更高,真正的高峰其实是下午三点前后。深夜当然也有一个次高峰,集中在凌晨一点到两点,而且那个时段的提问里,“为什么”“报错”“怎么办”这类词占比明显更高。白天和黑夜的我,完全两个状态。
月份的维度我也做了,用折线图看趋势会更直观。你能清楚看到哪几个月对话量暴增,比如我赶项目的那几个月,几乎每周的对话量都比平时翻倍;也能看出哪个月基本没怎么用,大概率是放假出去玩了。
4.2 内容维度:高频词与提问类型分类
时间维度只是外壳,真正有意思的是内容维度。这里我用到了 jieba 分词,把全年所有的用户消息分词,然后统计高频词。
import jieba from collections import Counter stopwords = set(['什么', '怎么', '一个', '这个', '那个', '可以', '吗', '了', '我', '你']) words = [] for msg in user_msgs: segs = jieba.lcut(msg['content']) words.extend([w for w in segs if len(w.strip()) > 1 and w not in stopwords]) counter = Counter(words) for word, count in counter.most_common(30): print(f"{word} {count}")高频词出来之后,你会立刻回忆起这一年的主题。我的前几名里有“代码”“部署”“报错”“端口”“写”“文章”“方案”,基本就是我这一年工作的关键词。更细一点的,还有“容器”“镜像”“接口”“权限”,一眼就能看出哪个项目占了最大比重。
光看词还不够直观,所以我又做了一个粗粒度的类型分类。用正则规则把用户消息大致分成四类:代码相关、写作相关、知识查询、情绪表达。分类逻辑不用做得太精确,够用就行,毕竟报告是给自己看的。
def classify(text): if re.search(r'代码|报错|bug|报错|接口|部署|函数|服务器|docker|端口', text): return 'code' if re.search(r'写|文章|标题|段落|措辞|润色|论文|稿子', text): return 'writing' if re.search(r'?|吗|是什么|怎么样|为什么|为什么', text): return 'knowledge' return 'emotion'分类后的结果还挺有意思。我的提问里,代码类占了四成多,写作类两成,知识查询两成,剩下的情绪表达不到一成。但值得一提的是,情绪类消息虽然数量最少,平均长度却最长。就像跟朋友倾诉一样,真正低落的时候,你会打很多字。
4.3 情感维度:用 AI 分析 AI 的对话
这是我整份报告里最特别的一块:我把自己的聊天记录交给了 DeepSeek 本身去做情绪标注。做法不复杂,把一段时间内的对话节选抽出来,配合一段提示词,让它判断我当时的心理状态。
我用的提示词大概长这样:
下面是我某一天和 AI 的对话节选,请判断我当天的主要情绪状态,从平静、焦虑、兴奋、疲惫、好奇中选择一个,并用一句话说明理由。 对话节选: {对话文本}然后按照返回结果打标,再统计各个情绪状态的占比。这就是典型的 LLM-as-labeler 思路,拿大模型当标注器用。人工给几千条对话逐条做情绪标注根本不现实,但交给模型几十分钟就搞定了。
出来的结果挺戳人的。数据显示,我有一段时间的对话情绪大概率是“焦虑”,翻回那几天的聊天记录,果然都是在赶一个上线前的问题,连续三四天每天都在问各种排查思路。看到那个数据的时候,当时的那股焦躁感好像又重新涌上来了一点点。这种感觉,是单纯看聊天记录体会不到的。
5. 生成年度报告:从图表到可分享的 HTML 页面
5.1 报告页面放哪些板块最出效果
统计分析做完之后,就要考虑怎么呈现。我建议不要贪多,报告的核心是让人一眼被击中。我最后保留的板块大概是这么几个:
- 总览数字:和 DeepSeek 聊了多少次、你说了多少字、AI 回了多少字,相当于写了多少篇长文。
- 24 小时活跃热力:你几点最活跃,凌晨的提问占比。
- 月度对话趋势:一年 12 个月,哪个月话最多。
- 提问类型分布:四类提问占比,附一句我知道你今年在忙什么。
- 年度高频词:Top 词汇列表。
- 年度之最:你提出过的最长问题、AI 回复的最长答案,以及那段内容的节选。
- 年度关键词:一年里出现频率最高的一个词,配一句解读。
每个板块我都在下方加了一句“人话解读”,不是冷冰冰的图表,而是像朋友在跟你聊天一样,比如“这个月你的对话量翻了一倍,应该是那件大事吧”。
5.2 Python 出图:matplotlib 实现中文可视化
图表生成我用了 matplotlib,核心就三步:设置中文字体、绘图、保存成 base64 嵌入 HTML。下面是设置字体的部分:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False hours = list(range(24)) counts = [hour_counter.get(h, 0) for h in hours] plt.figure(figsize=(10, 5)) plt.bar(hours, counts) plt.title('我的一天:24小时对话分布') plt.xlabel('小时') plt.ylabel('消息数') plt.savefig('hour_dist.png', dpi=150, bbox_inches='tight')这里有个容易踩的坑:如果系统没装 SimHei 字体,中文标签会变成方块乱码。备选方案是改用系统自带的文泉驿或者微软雅黑,或者在代码里指定字体路径。总之先跑一个小图测一下字体,再批量生成所有图表,别一口气画几十张图然后发现全乱码。
指标图表生成之后,我会用 base64 压缩成字符串,直接嵌入 HTML,这样最终只有单个文件,发给任何人都能打开,不存在跨设备打不开图片的问题。
import base64 with open('hour_dist.png', 'rb') as f: img_data = base64.b64encode(f.read()).decode('utf-8') img_html = f'<img src="data:image/png;base64,{img_data}" />'5.3 用 HTML 模板拼装最终报告
我会把整份报告设计成一个纵向滚动的页面,类似电子杂志。配色上我用的是白底加深色文字,加一个亮色渐变标题区。不用复杂框架,纯 HTML 加一点内联 CSS 就够了。想省事的话,也可以直接把 matplotlib 输出的 PNG 按顺序平铺在页面上,配文字标题,效果一样能打动人。
这里分享一个排版技巧:不要过度堆数据。数据分析结果往往很多,但落到报告上,只挑最有戏剧性的那部分。比如你不必把全部 24 小时的数据列出来,只标注“最高点出现在下午三点,最低点出现在上午 9 点”就够了。观众或者未来的你,关心的不是数据完整性,而是那些能唤起记忆的点。
6. 常见问题与排查技巧实录
6.1 抓包拿到的数据不完整怎么办
最典型的症状是:会话列表只有几十条,明显少于你在页面上看到的会话数。原因基本可以确定是懒加载机制。
解决办法很简单:回到对话页面,手动把左侧会话列表滚动到底,触发所有分页请求。操作时注意看 Network 面板里是否持续出现新的会话请求,直到滚到底部之后不再有新请求为止。然后再重新抓包导出。
如果你的会话数量特别多,比如上千个,那滚动会比较辛苦。可以配合浏览器自动化工具,模拟滚动操作,把所有响应存下来再合并。
6.2 时间戳对不上或时区错乱
设备时区不同,可能导致导出的时间戳和你的本地时间相差几个小时。大部分导出数据的时间戳用的是 Unix 毫秒时间戳,本身是 UTC 时间,转成当地时间需要根据你的时区做偏移。
我的建议是:清洗时统一用一个时区换算函数,不要中途混用。同时注意一些特殊时段,比如夏令时切换前后,时间可能出现一小时的偏差。个人报告对时间精度要求不高,但如果你按小时统计活跃度,偏差一小时就会把高峰时段搞错。
6.3 个人隐私如何脱敏
这是很多人忽略但绝对不能忽略的问题。聊天记录里几乎肯定有你自己的真实姓名、同事昵称、项目代号、邮箱地址、手机号码,甚至家庭住址。做报告之前,务必做一轮脱敏处理。
最简单的做法是用正则替换:
def desensitize(text): # 手机号脱敏 text = re.sub(r'(1[3-9]\d{9})', '138****8000', text) # 邮箱脱敏 text = re.sub(r'\b[\w+.-]+@[\w-]+\.[\w.]+\b', 'name@example.com', text) return text如果是准备公开发布的报告,建议把脱敏后的文案重新读一遍,确保没有任何能关联到个人的信息残留。哪怕只是自己收藏,脱敏后的版本也更安全,万一哪天想分享给朋友,不至于手忙脚乱。
6.4 别人问我“这有什么用”时,我的回答
做这件事实诚地说,没有直接的经济价值,也不是某项工作需要交付的成果。但做完之后,我对自己这一年的认知清晰了很多。这个价值没法用数字衡量。
如果非要给它找点用,它至少可以证明:你不经意间留下的数据,足够拼出一张“你究竟在忙什么”的画像。这些碎片化的消息,本来是聊完就散的,但把它们聚到一起,竟然能拼出一个完整的人——一个会在深夜焦虑、会在白天拼命、会为了一个小问题纠结半天的你。
我在实际做的时候还有一个体会:技术社区里经常看到有人分享各种复杂的工作流工具,用来编排和管理 DeepSeek 这类模型,但对个人年度报告这种轻量项目,完全没必要上那么重的方案。一个浏览器开发者工具、一个 Python 脚本、一堆 JSON 文件,就能跑通全流程。越轻量的工具越容易坚持,也越容易让你把注意力放在数据本身而不是工具上。
最后再分享一个小技巧:导出数据后,记得第一时间做一份脱敏副本再开始排版。不然满屏都是自己的名字和项目代号,你根本没法客观地看待这些数据,总觉得每一条都在揭示什么。脱敏之后,你反而能以旁观者的身份,好好看看这一年到底是怎么过的。如果你也想做,别想着一步到位,先从“一个数字加一张图”开始,跑通流程后再慢慢加维度和设计,就已经比大多数人强了。