Python自动化脚本实现AI历史会话批量导出与备份
我最近被问到一个很有意思的问题Meta AI 左边那一长串历史会话能不能一次性整个导出对方特意强调了一句——不是单条会话里那十几轮的来回对话而是左侧列表里的多个会话直接打包带走。说实话这个问题比看起来要复杂因为官方界面里确实没有“全选导出”这种按钮很多人折腾半天只能一个个复制粘贴手都要断。这篇文章我就把这件事彻底讲清楚官方能给到什么程度、为什么没有一键导出、以及目前真正可行的一条“批量导出”路径是怎么跑通的。如果你只是想备份自己的对话或者想把过去几十上百个会话统一归档到本地慢慢翻这篇应该能帮你省下不少时间。1. 先搞清楚这个“批量导出”到底卡在哪1.1 官方到底给到哪一步我先说结论以目前公开可见的功能来看Meta AI 的界面里没有“多选会话→导出”这种操作。你能做的通常只有两类第一种在单个会话内部复制消息或者把某一条回复导出/分享出去。这属于单次粒度的操作适合“我就要这一轮问答”。第二种平台账号层面的“信息归档”下载。你在账号设置里能找到下载个人资料的入口导出的一般是内容发布、互动记录、登录信息这类数据。至于 AI 聊天会话会不会被完整包含在里面不同平台版本差别很大很多时候你会发现要么完全没这个东西要么导出来的只是残缺的标题列表消息正文根本不在。所以就出现了一个尴尬局面官方能给的要么太小单条消息要么不确定账号归档恰恰缺了“中间层”——把左侧会话当成一个整体单元来管理的能力。1.2 为什么产品不做“一键导出 N 个会话”我也想过这个问题。站在产品角度Meta AI 这种助手类产品会话本身就是轻量级的官方默认你的使用场景是“问完就走”而不是把 AI 对话当成需要长期保存的知识资产。一旦哪天 Meta AI 自己需要调整会话列表、清理历史数据你手头没有备份几十个会话说没就没。这一点亲身经历过就懂了——所以“批量导出”本质上是个数据主权问题你在对话里产生的文本应该能被你以完整、通用的格式带走。另外还有一个很现实的原因如果一个平台轻易给全量导出会极大方便数据迁移到竞品或者被用来搞大规模数据训练。平台当然不愿意主动开这个口子。这意味着在官方提供批量导出功能之前只能自己想办法。1.3 拆一下你的真实诉求备份、迁移还是检索动手之前先别急着找工具先问自己一个问题你导出这些会话到底要干什么我见过三类需求对应的处理方式完全不同纯备份。怕会话哪天被清掉只想留底。那要求就是“内容完整、格式通用、能随时打开看一眼”。迁移。想把对话搬到另一个工具或知识库里继续用。那要求就是“结构清晰、带时间戳、能自动归入新工具”。检索。你不是记得答案在哪条会话里嘛但条目太多找不到。那你真正需要的不是导出而是“能不能对这些会话做全文搜索”。把需求拆清楚之后你会发现“批量导出”只是第一步后面怎么整理其实更关键。我先讲怎么导出来第四节再讲导出之后怎么用。2. 绕过死路四条批量导出的可行路径横向对比2.1 路径一官方“信息归档”下载优点是最安全全程在官方体系内不需要任何自动化脚本也不需要动页面元素。缺点也很明显AI 会话内容不一定在里面。就算在下载回来往往是一堆 HTML 或者 JSON 文件里面混着大量帖子、互动记录等无关数据你还得自己写个解析脚本才能把会话列表单独捞出来。我的建议是这个路径可以作为第一站先试一下。你可以不用写代码先看看下载下来的归档里有没有 sessions 或者 messages 之类的关键字。如果有直接解析如果没有就不要在这个方向上浪费时间了。2.2 路径二开发者面板直接读本地存储Meta AI 网页端的界面打开 F12 开发者工具切到 Application 面板看一下 Local Storage 和 IndexedDB 里有没有会话数据。如果前端把会话列表和消息内容都缓存到了本地那确实可以很轻松直接从 IndexedDB 里把整张表导出来就行连模拟点击的功夫都省了。但实际体验是这类产品大多采用“服务端存储 前端按需拉取”的架构本地只存会话标题和更新时间这种用于列表展示的轻量数据完整消息内容通常不会全部缓存在浏览器里。所以你敢的话可以先看一眼有惊喜最好没有也不用气馁反正一分钟就能确认。2.3 路径三自动化脚本逐个会话抓取推荐这是目前最通用、最推荐的方式。思路特别简单人工操作时你会怎么导出点开一个会话滚动加载全部消息复制回到列表点下一个。自动化脚本就是在模拟这个过程。它不需要破解任何东西也不读取任何本地存储处理的都是你屏幕上已经渲染出来的、你自己账号里的内容本质上是把“人工复制粘贴”这一劳动密集的动作变成脚本执行。优势是稳定、可复现、不挑平台实现。只要页面能看到脚本就能抓到。这也是我最终自己跑通的方式第三节我会把过程和代码思路完整贴出来。2.4 路径四第三方插件和“导出工具”的风险网上确实能找到一些声称能“一键导出 AI 会话”的浏览器扩展或在线工具。我的态度很明确尽量别用。浏览器的权限模型是“你点了同意扩展就能读写这个网站的所有数据”。你很难知道一个第三方扩展拿到你全部 AI 对话之后是只存在本地还是顺便上传到了某个服务器。这种风险跟你的对话内容本身有多敏感完全成正比。今天导出一屏聊天记录明天可能就把账号信息一起带走了。为了省一点手动操作不值得拿隐私去赌。同理第三方在线转换工具也不要轻易用除非你能接受把对话直接发到别人的服务器上。2.5 选型结论四条的横向对比如下路径安全系数抓全率上手难度适用场景官方信息归档高不确定低先试水碰运气浏览器本地存储高视缓存情况中界面出现卡顿或可离线查看时自动化脚本抓取较高高中高会话数量多、需要稳定完整备份第三方插件/工具低不确定最低不推荐如果只是三五个会话手动复制也就算了。一旦超过两位数直接上自动化脚本一次配置以后随时全量备份。3. 实操记录我用自动化脚本把左侧全部会话导成了本地文件3.1 前置准备和脚本思路我用的工具是 Python 生态里一个叫 Playwright 的开源浏览器自动化库。选它的原因有三个第一它能复用本机浏览器直接打开登录好的会话不用每次重新处理登录验证 第二它支持滚动、等待、点击这些页面交互非常适合模拟人工操作 第三它抓取的是渲染完成后的 DOM 文本消息内容跟你在屏幕上看到的一模一样不需要解析接口返回的乱七八糟的 payload。先讲一下宏观流程打开 Meta AI 会话列表页反复滚动左侧列表直到所有历史会话都被加载出来收集所有会话项的标识逐个点击进入每个会话滚动加载全部消息抓取消息文本和时间信息写入 JSON / Markdown 文件返回列表进入下一个会话直到全部处理完。下面这段是核心框架选择器部分我做了通配处理因为不同入口、不同界面版本的选择器都不一样你看的时候需要替换成自己页面里实际的元素。from playwright.sync_api import sync_playwright import json, time from pathlib import Path # 输出目录 OUTPUT_DIR Path(meta_ai_sessions) OUTPUT_DIR.mkdir(exist_okTrue) # 需要替换成实际页面里的选择器 SESSION_ITEM_SELECTOR .session-item-class # 左侧会话项 MESSAGE_BLOCK_SELECTOR .message-block-class # 消息块 def scroll_session_list(page): # 模拟在左侧列表区域滚动 page.mouse.move(200, 200) page.mouse.wheel(0, 1200) page.wait_for_timeout(1200) def wait_until_stable(page, selector, stable_rounds3): # 连续多次数量不变则认为加载完成 last_count -1 stable 0 while stable stable_rounds: count page.locator(selector).count() if count last_count: stable 1 else: stable 0 last_count count scroll_session_list(page) return last_count def scrape_one_session(page, session_element): title session_element.inner_text().strip() session_element.click() page.wait_for_timeout(1800) # 在会话内部滚动让消息全部加载 total wait_until_stable(page, MESSAGE_BLOCK_SELECTOR, stable_rounds4) print(f会话《{title}》抓到 {total} 条消息块) messages [] for block in page.locator(MESSAGE_BLOCK_SELECTOR).all(): text block.inner_text().strip() if text: messages.append(text) return { session: title, messages: messages, message_count: len(messages) }整个脚本的灵魂是wait_until_stable这个函数持续模拟滚动直到某个元素数量连续几次都不变才认为已经加载到底。这个策略比固定“滚多少次”要可靠得多因为不同会话的消息条数差异很大有的十几条有的几百条。3.2 第一步让会话列表完整加载左侧列表一般也是懒加载的你打开页面时只会显示最近十几二十个会话往下滚动才会往深处翻。这里最容易出的问题是你以为滚到最底了其实是因为滚动位置不对列表数据根本没有触发加载。最好先把左侧列表所在的 DOM 区域单独定位出来滚动操作作用在那个区域内。实操时我是这样处理的先在页面上找到左侧列表的容器选择器用locator.scroll_into_view_if_needed()确保可见再反复执行滚轮事件。每滚一次就数一次会话项数量连续三轮数量不变才停下。还有一个细节有些界面滚到中间会出现“加载更多”按钮或者分页标记你在等稳定的时候注意看页面底部有没有出现这类元素有的话要先点击否则数量会一直不变让你误以为已经加载完了。等列表稳定后把每个会话项的文本和索引收集到一个 Python 列表里。这里我建议用索引而不是直接存可点击对象因为后面进入会话再返回时DOM 状态会刷新之前拿到的对象有可能失效。3.3 第二步逐个进入会话并抓全消息进入会话之后消息区同样是懒加载的。你要对每个会话重新执行wait_until_stable确保消息块全部渲染出来。抓消息时要注意一件事Meta AI 的消息区域里可能包含输入框、编辑区等非消息节点如果选择器写得太宽会把无关文本也抓进来。我一般先看消息块的特征比如是否包含特定的 role 标签或者消息气泡的样式类。宁可多过滤一步也别让脏数据混进来。消息块抓下来之后大多数人会习惯把所有文本拼成一个大字符串。我不推荐这么做。因为后续你要检索“这是哪条会话里的哪一轮问答”必须保留消息的独立性和顺序。所以我把每个消息块作为一个数组元素存起来顺序就是从页面 DOM 里读出来的原始顺序也就是对话发生的先后顺序。3.4 第三步整理成“会话级”JSON 与 Markdown抓到原始数据后我做了一层标准化。JSON 结构尽量统一为{ session_id: 20250718_1542_主题摘要, created_at: 2025-07-18T15:42:00, messages: [ { role: user, text: 问题内容, time: 2025-07-18T15:42:10 }, { role: assistant, text: 回答内容, time: 2025-07-18T15:42:30 } ] }如果页面结构里能区分用户和 AI 消息就把 role 字段填上区分不了的话就都归为 “message”保持顺序即可。不强行猜角色以免误导后面的检索。JSON 是给程序读的Markdown 是给人看的。每次导出我都会同步生成一个.md文件格式大概是# 会话标题 - 时间2025-07-18 15:42 - 消息数23 --- **用户**问题内容 **AI**回答内容 ---这样就算哪天不想用脚本了直接打开 MD 文件也能读。需要导入笔记软件时Markdown 又几乎是所有工具都能识别的基础格式通用性最好。3.5 关键代码片段主循环、断点续传和防御主循环其实很简单但必须加上三个防御措施当前会话抓取失败要能跳过而不是中断全部任务每个会话之间要有随机间隔避免高频操作处理完的会话要做标记下次运行不重复抓。标记这步对会话特别多的场景尤其重要。如果会话列表有三四十个中间网络一抖动断了重新跑一遍成本很高。所以我在输出目录里放了一个done.txt每次成功抓完一个会话就把它的标识写进去下次启动时先读这个文件已经在里面的直接跳过。def load_done(): done_file OUTPUT_DIR / done.txt if not done_file.exists(): return set() return set(done_file.read_text(encodingutf-8).splitlines()) def mark_done(session_id): with open(OUTPUT_DIR / done.txt, a, encodingutf-8) as f: f.write(session_id \n) def run(): with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(你的会话列表页地址) # 手动登录或复用登录上下文 input(登录完成后按回车继续……) total_sessions wait_until_stable(page, SESSION_ITEM_SELECTOR) print(总计发现会话数, total_sessions) done load_done() for i in range(total_sessions): items page.locator(SESSION_ITEM_SELECTOR) if i items.count(): break session_id items.nth(i).get_attribute(data-session-id) or str(i) if session_id in done: continue try: result scrape_one_session(page, items.nth(i)) save_to_json(result, session_id) save_to_markdown(result, session_id) mark_done(session_id) except Exception as e: print(f第 {i} 个会话失败{e}) time.sleep(2 (i % 3)) # 随机节奏别太急躁 browser.close()这里我用>meta_ai_sessions/ 2025-07-15_001.md 2025-07-15_002.md 2025-07-16_003.md ...文件名统一为“日期_序号”。原因前面说了标题里可能有特殊字符不能直接用作文件名但日期和序号一定能保证唯一性、可排序性。每个 Markdown 文件内部我建议保留一个简短的 H1 标题、时间字段和会话 ID。这样你在全文搜索的时候搜到内容的同时能立刻看到它属于哪一次会话、大概发生在什么时候不用再费力去猜来源。4.2 给一大坨对话做全文检索的两种轻量方案如果你只有几十个会话直接用一个支持文件夹检索的本地笔记软件就够了。它们能自动扫描目录里的 Markdown 文件提供全文搜索功能搜关键词很快能定位到具体文件再打开看上下文。如果连笔记软件都不想装也可以用一条命令在目录里做全文搜索。Windows 上可以考虑用 PowerShell 的Select-StringMac/Linux 上直接用grep -rngrep -rn 某段想找的内容 meta_ai_sessions/这样最省资源几百个文件也能秒出结果。缺点是没有排序、没有高亮更没法做语义检索但“我曾经问过什么”这种需求已经能覆盖大半了。4.3 进阶把会话灌进本地 RAG让历史对话“可被追问”如果你的会话数量要到三位数以上普通全文搜索就不太够用了。你会遇到一种常态搜“部署”能搜出一百条命中但你根本想不起来到底哪一句话是最终结论。这时候可以把导出的 Markdown 切块转入一个本地向量库做检索增强生成RAG。流程大致是这样把每个 Markdown 文件按段落或固定长度比如 500 字切成小段用本地嵌入模型把每段文本转换成向量存入向量数据库以后你想查“我之前关于 XX 的最终结论是什么”直接用自然语言提问系统做语义检索把最相关的那几段对话捞出来再交给本地模型组织成回答。这个方案同样是在本机跑不需要把你自己的对话内容发给任何外部服务。效果方面我对这个做法最直观的感受是导出文本从“死档案”变成了“可追问的记忆”你有任何模糊的问题都可以试着问一嘴命中率比纯关键词搜索高太多了。5. 边界和风险哪些事能做哪些尽量别碰5.1 自动化操作的合规边界我前面反复强调一个前提这个方案只适合导出你本人账号、你能正常看到的会话数据。它不是用来做批量爬虫的更不应该去抓取任何需要额外权限才能访问的内容。你的脚本只是在替你点鼠标、替你滚动页面、把屏幕上的文字存下来——就像你手动复制粘贴了几百次。这个性质上是站得住的。但如果你把同样的脚本改成“不登录也能抓公开数据”、“一个账号把站内所有会话都抓一遍”那就完全越界了属于影响平台正常服务的自动化行为我不想看到你往这个方向试。使用这个脚本请时刻记着“自己的数据、自己的账号、自己的备份”。5.2 个人隐私数据的落地保护AI 对话相比普通文本文档隐私浓度高得多。你问过什么问题、暴露过什么个人信息、写过什么代码都在里面。导出之后文件就躺在磁盘上别人拿到这份文件等于拿到你与 AI 的全部交流记录。所以我对导出的目录一直坚持两个习惯一是放到带有磁盘加密的系统分区里不要随意放在 U 盘或共享文件夹 二是如果一定要放移动介质先把整个meta_ai_sessions目录压缩成一个带密码的压缩包再拷走。这不是小题大做有次我为了图省事把导出目录直接扔进了网盘同步文件夹电脑上的网盘客户端正好开启设备间同步差点把这些数据云同步到了其他设备上后怕得很。5.3 一个容易踩的坑别把“单会话内多次对话”和“多会话导出”混在一起标题里那句话其实说得很精准——“不是单条会话里面的多次会对话”。这个区分很容易被忽略我在帮别人看问题时就见过不少误解。单条会话里的多次对话指的是你点进某一个会话之后里面几十轮用户和 AI 的问答这属于提升粒度问题而多会话导出指的是左侧列表里的多个独立会话属于横向扩展问题。这两个概念对应着完全不同的操作方法前者只需要在会话内部滚动复制后者必须处理会话列表的遍历、恢复和切换。如果你没搞清楚自己卡在哪个环节会陷入“我明明已经把消息抓下来了为什么还没有全部导出”的怪圈。先确认你的目标是哪一个再去看对应的解法。如果只是想把单个会话里的内容完整保留那你需要做的只是我在 3.3 节里那个会话内部滚动加载逻辑连列表遍历都不用写。我自己用这套脚本的实际感受是第一次跑通后后续每次备份就变成了一条命令的事情彻底摆脱了手动复制粘贴的噩梦。最后再给你两个可以立刻用上的技巧第一脚本里那个“登录完成后按回车继续”的输入提示千万别删它能让你用已经登录好的浏览器上下文避免在自动化窗口里重新处理登录流程第二第一次全量跑之前先试跑两三个会话看输出格式确认选择器和角色字段都对了再放开跑不然你会发现导出来一堆连自己都读不懂的乱结构化数据。批量导出不是目的导出之后你随时能找到想要的内容才是这件事真正值得做的地方。