你有没有想过,有一天深夜改完bug,顺手翻开自己和DeepSeek的聊天记录,会看到一整年都写在聊天框里的自己?年初我刷到各种App的年度报告,突然冒出个念头:支付宝有年度账单,网易云有听歌报告,那我这一年跟DeepSeek说的话,能不能也做成一份"年度聊天报告"?说干就干,这是个完全不用大模型、全靠脚本和统计就能完成的周末项目,做完之后你会得到一个蛮特别的自我观察视角。
这篇就把我完整走通的流程拆开讲:怎么把对话数据导出来、怎么清洗成可统计的结构、哪些数字最值得看、报告怎么排版才不像晒图,以及我做完之后发现的几个扎心规律。不管你是单纯好奇、想给生活留个纪念,还是想练手Python数据处理,都可以照着做。
1. 为什么要把聊天框当成"个人编年史"
1.1 对话记录是最诚实的电子足迹
年度账单展示的是你的消费路径,听歌报告展示的是你的情绪浓度,唯独AI聊天记录,展示的是你"没想清楚但必须开口问"的瞬间——这些瞬间天然没有修饰。
我翻记录时最大的感触是:你在搜索引擎里留下的关键词是裁剪过的,你在社交平台发的东西是表演过的,但你跟AI说话的时候是真的在问问题,而且往往问得特别具体。比如"这个报错为什么在第37行"、"房租合同到期前30天通知是否合法"、"我妈说这句话到底是不是生气了"。这些内容是象牙塔里不会写、朋友圈里不会发的东西,却是你真实生活的索引。
1.2 一个反直觉的发现:AI没变,变的是你
真正让我决定做这份报告的触发点,是我翻到三月份的一条记录:我给DeepSeek下指令,写了一大段结构化Prompt——"你是一名资深运维专家,请以表格形式输出……",五月份开始变成"这个nginx配置哪里错了帮我看看",到十一月份直接甩一段报错日志加一个问号。AI的能力边界没有变,但我和它对话的方式发生了很大的变化。
这种变化用回忆去感受是模糊的,用数据去统计却是清晰的。Prompt长度曲线、提问句式分布、每个月的对话密度,都在告诉我:这三百多天里,我的提问能力、信任半径和处理问题的方式,其实一直在悄悄改变。
1.3 谁适合做这件事
如果你满足下面任一条件,这个项目你值得做:第一,长期用DeepSeek网页版或第三方客户端聊技术问题,聊天记录已经积累到大几千条;第二,你有使用Python做数据分析的基础或者想练手;第三,你对"个人复盘"这件事上瘾,觉得年度报告不应该只有消费和音乐,还应该有"我这年到底在想什么"的记录。
整个项目成本很低:一个能跑Python的电脑、一段空闲的下午、加上一点耐心。统计都在本地完成,不需要把你的对话原文上传到任何第三方服务。
2. 没有官方导出按钮,数据该从哪里捞
第一关也是最容易劝退人的:DeepSeek官方目前没有"一键导出全部对话"的功能。网页版能翻历史会话,App端能继续上次对话,但想把所有记录批量弄出来,需要绕几条路。
2.1 先搞清楚你的DeepSeek"住在哪"
数据源决定了导出方案,先问自己一个问题:我平时用的是哪种方式?
- 网页版:对话存在DeepSeek云端,浏览器里能看到历史会话列表,但没有批量导出入口。
- 手机App:类似网页版,能看到历史列表,导出能力同样有限。
- 第三方桌面客户端(如Chatbox、Cherry Studio这类):大多数会把会话数据存在本地数据库里,这是最好处理的。
- 自己写脚本调API:所有对话都要经过你本机或服务器的请求,服务端日志就是最完整的数据源。
我自己的情况是主力用第三方客户端,偶尔用网页版续聊,所以主要走了"本地数据库导出"这条路。
2.2 本地客户端的SQLite:最优导出路径
很多第三方客户端底层用SQLite存聊天记录。以我用的客户端为例,数据库文件一般放在用户配置目录下:
- Windows:
%APPDATA%\客户端名\ - macOS:
~/Library/Application Support/客户端名/ - Linux:
~/.config/客户端名/
不确定路径就直接搜:
find ~ -name "*.db" 2>/dev/null | grep -i -E "chat|deep|sqlite"找到数据库文件后,先看看表结构:
sqlite3 ~/.config/chatbox/chatbox.db .tables .schema messages .schema conversations常见的表结构很简单:conversations存会话ID和时间,messages存每条消息的角色、内容和时间戳。把它导成JSONL格式的中间文件,后面统计就方便了:
import sqlite3, json, datetime conn = sqlite3.connect("chatbox.db") conn.row_factory = sqlite3.Row rows = conn.execute(""" SELECT m.conversation_id, m.role, m.content, m.created_at FROM messages m ORDER BY m.created_at ASC """).fetchall() with open("export.jsonl", "w", encoding="utf-8") as f: for r in rows: line = { "conversation_id": r["conversation_id"], "role": r["role"], "content": r["content"], "timestamp": r["created_at"] } f.write(json.dumps(line, ensure_ascii=False) + "\n") print("导出完成,共", len(rows), "条")稍微需要注意的是,各家客户端的字段名不一定叫content,有的叫text,有的按payload存JSON。遇到这种情况别硬猜,多用schema messages看清楚再写。
2.3 网页版没有批量导出:用浏览器开发者工具抓接口
如果你主力用网页版,会稍微麻烦一点。通用思路是用浏览器开发者工具观察网页在加载历史会话时请求了哪个接口,然后把接口返回的JSON数据抓下来。
打开网页版,按F12进入开发者工具,切到Network面板,筛选XHR请求,然后手动滚动历史会话列表,让页面触发加载更多。找到返回会话数据的接口后,右键这条请求,选择Copy as cURL,再把命令转成Python脚本批量拉取。
这段话不要当成一个"照抄就能用"的教程,因为网页版前端结构随时可能调整,接口名和参数今天能用明天不一定能用。你真正要学的是观察思路:页面要显示历史,前端就必须向服务器要数据,这个数据流就在开发者工具里。抓到之后,历史列表、会话详情、消息内容都能逐层拿下来。
2.4 自己写脚本调API:靠埋点日志
如果你是那种用Python脚本直接调DeepSeek API的人,反而最简单——前提是你平时有留日志的习惯。如果没留,那就只能从下一次开始埋点了。所谓埋点就是每次请求都追加一条记录:
import json, datetime, uuid def log_chat(messages, reply, model="deepseek-chat"): with open("chat_log.jsonl", "a", encoding="utf-8") as f: f.write(json.dumps({ "conversation_id": uuid.uuid4().hex, "ts": datetime.datetime.now().isoformat(), "messages": messages, "reply": reply, "model": model }, ensure_ascii=False) + "\n")这个做法适合长期自建工具的场景。它有个额外好处:不只是用户输入和AI输出,连你的system prompt、参数配置都记录下来了,回溯的时候信息量比客户端导出的还大。
2.5 实在兜底:截屏OCR
如果上面几招都行不通,最粗暴的兜底方案是截屏加OCR。把历史会话逐屏截下来,用自带OCR或PaddleOCR把文字识别出来。这条路非常费手,只适合对话量少、又不追求完整性的情况,我个人的建议是能不用就不用。
四种方案做一个对比,方便你对号入座:
| 方案 | 适用人群 | 难度 | 完整度 | 隐私风险 |
|---|---|---|---|---|
| 本地SQLite导出 | 第三方客户端用户 | 低 | 高 | 全程本地 |
| 浏览器接口抓取 | 网页版重度用户 | 中 | 中高 | 依赖登录态 |
| API埋点日志 | 脚本调用APII的人 | 低(需长期维护) | 极高 | 日志存本地 |
| 截屏OCR | 只有手机App记录的人 | 高 | 低 | 本地处理 |
3. 清洗和统计:把几万行文本压缩成十几个数字
数据导出来之后是脏的:有时间戳错乱、有跨会话重复、有夹杂系统提示词的内容,还有大量半截代码块。别急着做可视化,先花一点时间把数据洗干净。
3.1 建立统一的数据结构
不管数据来自哪条路径,最后都统一成四列:conversation_id、role、content、timestamp。用pandas读进内存:
import pandas as pd data = [] with open("export.jsonl", "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue obj = json.loads(line) data.append({ "cid": obj["conversation_id"], "role": obj["role"], "content": obj["content"], "ts": pd.to_datetime(obj["timestamp"], unit="s", utc=True) }) df = pd.DataFrame(data) print(df.shape)3.2 一个必踩的坑:时区导致熬夜曲线偏移
我第一次统计时段分布的时候,发现凌晨三点的对话多得离谱,仔细一看才意识到问题:数据库里存的时间戳是UTC,我本地在东八区,没做时区换算,导致所有对话时间整体偏移了8小时。原本发生在深夜11点的对话,被记录成早上7点;原本凌晨两点问的问题,被当成上午十点。
改正很简单:
df["ts_local"] = df["ts"].dt.tz_convert("Asia/Shanghai") df["hour"] = df["ts_local"].dt.hour df["date"] = df["ts_local"].dt.date这个坑会在后面所有时间统计里放大,比如熬夜指数、上下班规律、活跃时段,所以一定在清洗阶段就处理掉。
3.3 清洗时还需要处理的几件事
- 去重:如果你导出过两次,同一个会话的同一批消息会出现两遍。识别方法很简单,用内容的MD5或消息ID去重。
- 过滤系统消息:有的客户端会把"你发送了文件""对话已满删除"这类系统提示也写进消息表,需要按特定字段或内容规律过滤掉。
- 过滤Prompt模板:如果你的客户端在每条消息前自动插入系统提示词(比如"你是智能助手DeepSeek"),统计词频前要把这些固定文案去掉,否则词云会被刷屏。
3.4 统计口径:哪些数字真正有信息量
这是全项目最核心的部分。我不建议把API一天的Token消耗拿来当报告主体,那是账单而不是年度报告。我更关心下面几组口径:
第一组,基本体量:总对话条数、总字数(分别统计用户端和AI端)、会话个数、单条消息最长字数。这一组告诉你"这一年你写了多少东西"。
第二组,时间规律:每日对话数的活跃日历、按时段聚类的活跃热力图、最长连续对话间隔。"你一年有37天没打开过对话窗口"这种结论就来自这里。
第三组,内容分类。我用关键词规则做了一个粗粒度分类:
rules = { "代码调试": ["报错", "error", "bug", "debug", "traceback", "崩溃", "日志", "异常"], "技术学习": ["教程", "原理", "源码", "架构", "什么意思", "怎么用", "为什么"], "生活杂事": ["房租", "医保", "快递", "外卖", "体检", "高铁", "请假"], "写作润色": ["润色", "改写", "措辞", "email", "汇报", "ppt", "总结"], } def classify(text): t = text.lower() for cat, kws in rules.items(): for kw in kws: if kw in t: return cat return "其他"数据量大、且有API预算的朋友也可以尝试把每条消息丢给大模型做分类,但我不建议把上万条消息全部喂进模型,那既烧钱又慢。我的做法是:先用规则分类拿到总体比例,再随机抽200条做人工复核,比例基本靠谱。
第四组,提问句式演化。用正则统计用户消息的开头模式:
import re patterns = { "求助型": re.compile(r"^(请|帮|能|可以|麻烦)"), "提问型": re.compile(r"^(为什么|怎么|什么|是否|有没有)"), "短指令": re.compile(r"^[^,。!?\n]{0,10}?$") } df["style"] = df.loc[df["role"]=="user", "content"].apply( lambda x: "求助型" if patterns["求助型"].search(x) else "提问型" if patterns["提问型"].search(x) else "其他" )统计结果会告诉你一个有意思的事实:你依赖AI的方式在变化,从"帮我做"变成"告诉我怎么做",再变成"直接上代码块"。
这一章的成果是一张汇总表:每个月的活跃天数、对话条数、平均字长、分类占比、句式占比。它构成了整份报告的全部原料。
4. 报告的美术课:让数字从表格变成有回忆感的作品
数据和统计做好了,接下来要做的是"创作"环节。我发现很多人在这一步容易翻车——把一堆柱状图和饼图堆在一起,像一份年终财务报表,而不是一份年度报告。要让数字有温度,关键是选对可视化和叙事结构。
4.1 主视觉:GitHub风格的年度热力日历
最能传达"这一年"感觉的图,是GitHub贡献图那种按日填色的日历热力图。它一眼就能展示全年的对话密度:有些日子热闹得像过年,有些时候连续几周一片空白。
实现方式不算复杂,Python生态里有成熟方案:
import calplot import pandas as pd series = df.groupby("date").size() calplot.calplot(series, cmap="YlOrBr", figsize=(12, 4))如果你不想额外装库,也可以用pyecharts的日历图,输出HTML成本更低,配色更适合中文审美。热力图的视觉占位是整个报告最重的一块,它直接决定了报告"像不像年度报告"。
4.2 副视觉:词云和高频句
第二张值得做的图是词云。我建议分开做两张:一张统计用户输入的关键词,一张统计AI回复里的动作词。处理词云注意两件事:第一,中文分词用jieba,分词前先过滤停用词;第二,WordCloud默认字体不支持中文,不设置字体路径会出来一堆乱码方块。
import jieba from wordcloud import WordCloud texts = " ".join(df[df["role"]=="user"]["content"].tolist()) words = [w for w in jieba.cut(texts) if len(w) > 1] wc = WordCloud( font_path="simhei.ttf", width=1200, height=800, background_color="white" ).generate(" ".join(words)) wc.to_file("wordcloud_user.png")除了词云,我还加了"高频句子片段"统计,找出你重复说过最多的话。比如"继续"两个字可能出现了几百次,这在AI对话场景里是很有意思的细节。
4.3 叙事结构:用时间线而不是排行榜
年度报告最忌讳平均主义,要把全年的变化讲成故事。我建议按时间线组织章节:
- 年初:你是怎么开始接触DeepSeek的,第一次对话聊了什么方向;
- 上半年:高频话题是什么,哪类问题占大头;
- 转折点:哪一个月对话量突然暴增,那是因为发生了什么(对应你现实里的项目或考试);
- 深夜时刻:凌晨时段你在聊什么,比例如何;
- 年末对比:年初和下年尾的提问风格对比,Prompt长度和句式迁移。
给每个章节准备一个"金句"作为标题。我整理了几条可以直接套的文案公式:
- "你一共输入了X万字,相当于X部中篇小说。"
- "这一年里,你最爱问的问题类型是X,它占了全部问题的X%。"
- "X月X日你聊了X条消息,是全年最活跃的一天,那天你在忙什么?"
- "你有X天没有打开对话窗口,希望那些日子你都过得很顺。"
4.4 一个必须做对的决定:隐私边界
做报告的时候,你手里握着的是自己一整年的真实对话。它能上架朋友圈的部分,只有聚合数据和图表;至于具体话术、私人信息、涉及朋友和同事的内容,一个字都不要出现在成图里。
我的做法是:所有图表只放统计值,不放原始文本;词云生成前手工替换敏感词;如果你要把报告发给别人看,请在文案里把所有具体案例改成脱敏示例,否则这就是一份大型隐私泄露现场。
5. 统计结果给我的一面镜子:三个扎心的数据规律
数据跑完之后,我从报告里读出了几件自己平时完全没意识到的事实。这一章是这份项目最有意思的副产品,也可以看作"年度报告"真正的价值所在。
5.1 "怎么办"型问题占比过高
我统计了用户消息里的句式分布,占比第一的不是"为什么",也不是"是什么",而是"怎么办"。这个结果让我愣了半天。仔细想想,确实:工作里遇到不熟悉的中间件,我问AI怎么办;身体有点不舒服,我问AI挂哪个科;深夜焦虑感上来,我也问AI该怎么调整。
AI在这里扮演的角色,早已超出了工具,更像一个永远不会不耐烦的树洞加决策助理。数据揭示的事实是:我太依赖外部反馈来做决策了,哪怕很多事我自己明明已经有答案,只是想让AI替我确认一下。
5.2 Prompt风格在悄悄变短,"去AI味"却在增加
几个月前的我,写Prompt是论文式的:角色设定、目标、约束、输出格式,一样不少。到了后期,我的提问越来越像发给同事的微信——"这个函数超时了"七个字加一张截图。
有意思的是,"去AI味"这个动作却越来越多:让AI帮我润色一封邮件之后,我经常还要再补一句"再自然一点,别像AI写的"。这句"再自然一点"在我的记录里出现了十几次。这背后其实是一个常见的迁移过程:早期我们学着让"人话"变成"Prompt话术"来适配模型,后期又反过来要求模型输出"人话"来适配阅读场景。对AI对话的老用户来说,这个转变几乎是必经之路。
5.3 对"继承上一个对话"的依赖
我看到热搜里总有人问"DeepSeek怎么继承上一个对话",这确实是个普遍问题。我的统计显示:长会话里,中途插入一个新问题的人,很少会主动开一个新会话,反而倾向于在旧会话里硬聊下去。这导致两个现象:第一,旧会话的上下文越来越长,早期的细节被挤掉;第二,旧会话里的问题类型越来越杂,一个叫"Week4项目调试"的会话,末尾居然在聊做饭。
数据告诉我:会话标题掩盖了对话漂移。把逻辑相关的问题拆成新会话,其实是对模型上下文的一种保护,也是对问题本身的一种梳理。
5.4 高峰期与繁忙提示的规律
导出时间统计后还有一个意外收获:我的活跃高峰集中在晚上九点到十一点,而恰恰是这个时段,我遇到"服务器繁忙"提示的次数也最多。这个规律对应了很多人想问的"DeepSeek为什么服务器繁忙"——除了服务端负载本身的变化,用户侧的高峰使用也在这个时段叠加。
后来我把文章类和配置类任务挪到早上做,晚上只保留需要互动的调试类问题,体感顺畅了不少。如果你也常被繁忙提示困扰,可以看看自己的使用时段,也许稍微偏移一小时,体验就能差出一截。
6. 零基础照着做:完整复现流程与避坑清单
前面几章讲完了思路,这一章给一份可以直接执行的流程清单。你不需要完全照搬,但顺序建议保持一致:导出、清洗、统计、可视化、排版。
6.1 环境准备
Python 3.10以上,安装以下依赖:
pip install pandas jieba wordcloud calplot pyecharts如果你打算做更细的情感分析,再加一个snownlp就够了。不建议为了做报告去搭深度学习环境,没必要。
6.2 导出文件转成标准jsonl
不管数据源是SQLite还是抓接口,统一清洗脚本骨架如下:
import json, re, hashlib seen = set() clean = [] with open("export.jsonl", "r", encoding="utf-8") as f: for line in f: obj = json.loads(line) content = obj.get("content", "").strip() if not content: continue digest = hashlib.md5(content.encode("utf-8")).hexdigest() if digest in seen: continue seen.add(digest) # 过滤系统提示词 if content.startswith("system:"): continue clean.append(obj) with open("clean.jsonl", "w", encoding="utf-8") as f: for obj in clean: f.write(json.dumps(obj, ensure_ascii=False) + "\n") print(len(clean))6.3 生成统计指标
按3.4的口径,输出一份汇总Markdown:
stats = {} stats["total_messages"] = len(df) stats["user_messages"] = int((df["role"]=="user").sum()) stats["ai_messages"] = int((df["role"]=="assistant").sum()) stats["user_chars"] = int(df[df["role"]=="user"]["content"].apply(len).sum()) stats["max_streak"] = compute_max_streak(df["date"].unique())计算连续活跃天数可以用一个简单循环:排好日期后,逐日比对差值,统计最长连续序列。
6.4 图表输出的三个优先级
第一优先做日历热力图,它最像"年度报告";第二优先做月度对话量折线图和24小时分布条形图,它们最直观;第三才是词云,因为词云需要调整字体和停用词,费时间但出效果。
6.5 容易掉进去的三个坑
第一,时间戳排序。用API脚本自建日志的朋友,如果开过并发请求,写入顺序是乱的,统计前务必按ts排序,否则"连续活跃天数"全是错的。
第二,jsonl文件的编码。最好全程用encoding="utf-8"读写,避免在Windows下导出GBK乱码。代码块里如果包含emoji或者特殊字符,写入时不要用open默认参数。
第三,会话归属不唯一。第三方客户端里,同一个会话可能被改名或复制过,导致统计"会话数"时偏大。这种情况建议只统计conversation_id去重后的值,不要拿消息表里最大会话ID当结论。
6.6 什么时候做数据收尾最合适
这里分享一个时间点建议:如果你想在1月1日看到完整年度报告,最好从12月中旬就开始做数据收尾。因为12月下旬的对话还会增量增长,你不可能等到最后一刻才来写脚本。我的做法是12月20日导出一次数据,12月29日再次导出做增量合并,1月1日只跑一次最终统计,正好把整年数据闭合成一年的完整跨度。
我个人做完这整个项目后最深的体会是:与其说这是一份给AI的年度报告,不如说是一份给自己的提问记录。那些写在聊天框里的句子,是别人看不到的思考轨迹——但你自己应该看一看。如果你也想做,不用等年末,哪怕只统计最近三个月,也会看到很多平时注意不到的规律。一个小技巧:先把导出和清洗流程跑通,保存好脚本,以后每个月导出一次增量数据,到年底你就有了一整年的完整素材。