有人问我:Meta AI 左侧那一排会话,能不能一次性全导出?注意,不是导出某一个会话里面的几十轮对话,是把左侧列表里的一堆会话,批量备份到本地。
这个问题听起来像个偏门需求,真上手做的时候却很有代表性。我见过不少人有同样困惑:想把一个账号下的AI对话历史整理走,换个工具继续用,或者给自己留一个可检索的知识库。但问题是,找遍界面也找不到一个“全选导出”按钮。这篇我就从产品现状讲起,给你手动、接口、脚本三条路线,最后放一个能改着用的批量导出脚本,顺带把路上会踩的坑都标出来。
1. 先搞清楚,“导出多个会话”到底要导出什么
1.1 别把“复制当前页”当成导出
很多人一开始觉得,导出不就是把文字拷出来嘛。实际操作起来才会发现,页面里能选中的文字只是当前这个会话、当前屏幕显示出来的内容。左侧那一长串会话列表,压根不在你的选中范围里。
真正意义上的“导出多个会话”,至少要包含三个层次的数据。第一层是会话的元信息,比如会话标题、创建时间、最后一次更新时间;第二层是会话里的每一轮消息,包括用户提问、AI回答,以及角色标识;第三层是结构化的格式,能够被其他工具读取,比如 JSON 或 Markdown。
这些需求叠加在一起,就跟“复制粘贴”完全不是一回事了。所以做方案之前,我会先问自己一句:到底要导到哪一层?是要一个能阅读的文档,还是要一份能程序化处理的原始数据?这两种目标,后续方案完全不同。
1.2 你的目标格式决定了整个方案
这里我列过一个简单的需求分级,你可以对照着看:
| 需求级别 | 典型场景 | 推荐导出格式 | 实现难度 |
|---|---|---|---|
| 单条消息复制 | 临时引用某段回答 | 纯文本 | 最简单 |
| 单个会话导出 | 把一次长对话存成笔记 | Markdown 文档 | 中等 |
| 多会话批量备份 | 换工具迁移、知识库整理 | JSON + Markdown 双份 | 较高 |
| 全量数据审计 | 分析对话模式、统计用量 | 结构化 JSON | 最高 |
我用下来最有感触的是:想要“批量导出”的人,大多数其实是要第三个层次。他们不是缺某一段文字,而是想把一整个会话库带走。
2. 先说结论:官方目前没给这个能力
2.1 平台上现有的导出入口有哪些
以我目前能看到的产品形态来说,Meta AI 的会话管理做得比较轻。常见的操作基本是这些:单条消息旁边会有复制按钮,你可以把某一次回复单独复制走;有些入口支持分享会话链接,把整个对话通过链接给别人看;语音类的消息可以单独另存为音频文件。
但是“左侧列表 → 批量选择 → 导出为文件”这个入口,在我见过的版本里是没有的。不仅 Meta AI 没有,市面上大多数类似产品在这方面都不约而同地保守。你很难找到一个 AI 助手,允许你把全部历史会话一次性打包下载。
所以如果你正盯着左侧列表发愁,很正常,不是你不会用,是产品压根没有把入口放出来。
2.2 为什么批量导出迟迟不来
很多人想不通,一个导出按钮而已,为什么这么难做?我之前也好奇过,后来结合行业里的普遍情况,有几点推测。
从产品优先级看,AI 助手这类产品把精力都放在生成质量、响应速度、多轮理解上,会话管理属于基础功能,定期清理就够了。批量导出这种低频需求,排期大概率会一直往后放。
从数据开放看,批量导出意味着把完整对话历史交给用户,这本身涉及数据格式设计、权限控制、隐私保护。对一个团队来说,每多一个导出格式,就要多维护一套兼容逻辑。只要不是付费点,优先级就很难提上去。
从商业角度看就更直接了。平台通常希望你把对话留在自己的生态系统里,导出得越顺畅,用户迁走的成本就越低。所以“官方不做批量导出”,很多时候不是做不到,而是权衡之后选择不做。
3. 官方没有,那就自己动手:三条可行路线
3.1 路线A:纯手工整理,适合十几个会话以内
如果你的会话数量很少,比如就五六个,那最靠谱的办法反而是纯手工。
具体操作是:逐个点开会话,从第一轮消息全选到最后一轮,粘贴到一个支持 Markdown 的编辑器里,然后手动补上会话标题、日期、备注。最后另存为本地.md文件。
听起来简单,实际操作有两个坑。第一个坑是长会话自动滚动。页面在加载历史消息时会自己跳位置,容易把你正在选的文本弄乱。我的做法是先把页面滚动速度调慢,选定文本后立刻用鼠标右键复制,不要等全选动画跑完再操作,那基本会失败。第二个坑是代码块缩进。直接从页面全选复制,代码块经常丢掉换行和缩进,这个一般无解,只能靠编辑器里的格式化功能补救。
这条路最大的优点是不依赖任何额外工具,也不会触发风控。缺点是会话一多就废,超过二十个你会复制到怀疑人生。
3.2 路线B:从浏览器开发者工具里拿JSON
懂一点网页技术的话,可以用开发者工具直接从接口层取数据。这条路比手工稳定,也比脚本轻量。
操作流程是:把 Meta AI 页面打开,按 F12 进入开发者工具,切到 Network 面板,勾选 Fetch/XHR,清空现有记录,然后刷新页面。接下来你能看到页面在后台发出不少请求。
这时候要在这些请求里找跟会话列表相关的。通常名字里会带 conversation、thread、history 这类关键字。点开请求看 Preview,如果是 JSON,基本就是数据源。会话列表接口一般会返回所有会话的标题和 ID;点开单个会话的时候,又会有一个请求返回当前会话的所有消息。
找到之后,右键那个请求,选 Save as HAR 或者 Copy response,把内容存成.json文件,原始数据就到手了。这条路比手工靠谱,因为它拿到的就是结构化数据,包含角色、内容、时间戳这些字段。缺点是每次都要手动操作,而且接口的字段名很乱,需要自己花时间解析。
3.3 路线C:脚本自动化批量导出,适合大量会话
当你面对的会话数量到了几十上百个,手工和开发者工具都不够用了。这时候最合适的是写一个自动化脚本,让浏览器自己跑一遍:登录、滚动左侧列表加载更多会话、逐个点击、读取消息、存成文件。
我自己的经验是,第一次搭这套脚本需要一两个小时,但建好之后,以后每次备份都是几分钟跑完的事。而且脚本跑出来的结果是固定格式,后续整理成知识库、迁移到别的工具,都非常顺手。
唯一需要留神的是别开太猛。脚本模拟真人操作,每步加一点随机延时,通常不会有问题。如果你疯狂并发、无视频率去点,就会触发平台的风控,后面我会专门讲这个问题。
4. 跑一个能用的批量导出脚本
4.1 设计思路和工具选型
自动化方案我推荐用 Python 加 Playwright,而不是 Selenium 或者纯 HTTP 请求。原因有三点:Playwright 自带自动等待,能减少页面没加载好就点击的报错;它支持持久化用户目录,第一次手动登录之后,之后跑脚本不用重新登录;它的选择器引擎也更强,能根据文本、角色、层级关系定位元素,改起来方便。
安装环境很简单:
pip install playwright playwright install chromium如果你用的是其他 Chromium 内核的浏览器,也可以指定 channel 参数复用,不一定非要下载它自带的浏览器。
4.2 主脚本代码和关键点说明
下面这个脚本是个通用骨架。我故意不把选择器写死,因为界面只要改版,任何提前写死的选择器都会失效。你需要跑之前打开开发者工具,找到自己页面上会话列表和消息区的真实选择器,替换到代码里。
""" 批量导出 Meta AI 会话列表 用法:先手动登录一次,之后脚本使用持久化登录态 """ from playwright.sync_api import sync_playwright import time import random import json from pathlib import Path OUT_DIR = Path("meta_ai_exports") OUT_DIR.mkdir(exist_ok=True) def main(): with sync_playwright() as p: browser = p.chromium.launch_persistent_context( user_data_dir="./playwright_userdata", # 持久化登录态 headless=False, # 有头模式,方便观察 locale="zh-CN", ) page = browser.new_page() page.goto("https://www.meta.ai/") # 换成你实际访问的入口 input("请在浏览器中手动登录,登录完成后回到这里按回车继续...") # 第一步:滚动左侧会话列表,触发懒加载 for _ in range(30): page.mouse.wheel(0, 1000) time.sleep(random.uniform(0.6, 1.2)) # 第二步:找到所有会话入口 # 注意:这个选择器需要根据实际页面调整 threads = page.query_selector_all("a[href*='chat'], [role='listitem']") print(f"发现 {len(threads)} 个会话") # 第三步:逐个点开会话,抓取内容 for idx, thread in enumerate(threads, 1): try: title = thread.inner_text().strip() or f"conversation_{idx}" thread.click() time.sleep(random.uniform(1.5, 3)) # 第四步:读取消息区域 # 注意:这里的选择器是核心,界面变化时需要现场调整 message_nodes = page.query_selector_all( "[data-testid*='message'], [class*='message']" ) lines = [] for node in message_nodes: lines.append(node.inner_text()) payload = { "title": title, "source": "meta_ai", "exported_at": time.strftime("%Y-%m-%d %H:%M:%S"), "content": "\n".join(lines), } (OUT_DIR / f"{idx:03d}.json").write_text( json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8", ) print(f"[{idx}] 已导出:{title[:30]}") except Exception as e: print(f"[{idx}] 失败:{e}") browser.close() if __name__ == "__main__": main()这段脚本有几个设计细节值得说。第一步滚动左侧列表时,我用的是一轮一轮慢慢滚,每轮之间加随机延时,目的是触发页面的懒加载,又不让操作节奏看起来像机器。如果你发现滚完 30 轮还是没加载完全部会话,可以把数字调大,或者改成滚到页面底部之后等待 2 秒再继续。
第三步点击会话的时候,我用inner_text()先拿到标题,再点击,这样即使选择器匹配到多个元素,也能知道每一个处理的是谁。导出文件名用序号加标题,可以避免文件名里出现非法字符。
4.3 导出之后,把JSON整理成Markdown
原始 JSON 适合程序处理,但不适合人看。所以我会在跑完脚本之后再执行一个小转换,把 JSON 变成 Markdown 存一份。这样一份是数据、一份是笔记,两不误。
from pathlib import Path import json for fp in sorted(Path("meta_ai_exports").glob("*.json")): data = json.loads(fp.read_text(encoding="utf-8")) md_lines = [f"# {data['title']}", ""] md_lines.append(data["content"]) md_lines.append("") md_lines.append(f"导出时间:{data['exported_at']}") md_path = fp.with_suffix(".md") md_path.write_text("\n".join(md_lines), encoding="utf-8") print(f"已生成:{md_path.name}")如果你希望 Markdown 里也能区分用户和 AI 的回答,那就不能像我上面这样简单把文本拼成一大段。更好的做法是在脚本里把每个消息节点分别打上“用户”或“AI”的角色标签,然后再转 Markdown。这需要在页面里找到每个消息节点对应的角色容器,通常通过 aria-label 或者父节点的 class 能判断出来。界面结构清楚之后,这是一步很小的改动,但整理出来的东西会专业非常多。
5. 踩坑实录:这些问题我基本每次都会遇到
5.1 会话列表只抓到一截,后面全是空的
这是最常遇到的问题。场景是:脚本统计到了 5 个会话,但左侧列表肉眼看着明显有三四十个。
原因基本是懒加载没有彻底触发。页面只在滚动到靠近底部的时候才去加载下一批会话,而脚本滚得太快,或者还没等加载完成就继续滚,最后一截数据始终没进来。
我的解决办法分两步。第一步是控制节奏:不要连续快速滚动,改成“滚动一下,停下等 1 秒,再滚动一下”,让网络请求有时间返回。第二步是判断加载状态:如果页面里有 loading 指示器或者骨架屏,等待它消失再继续滚。如果实在判断不准,还有一个笨办法:滚动到底之后等待 3 秒,再滚回顶部,重新统计一次会话数量,跟第一次的数量做对比,一致才继续。
5.2 点开会话之后取到的消息不完整
另一个高频问题:打开一个特别长的会话,脚本只读到了开头几段,后面的内容没有出现。很多页面用了虚拟滚动,尤其是 AI 对话这种消息列表特别长的情况,页面只渲染屏幕范围内的消息节点,滚出视口的节点会被销毁。
所以你不能指望打开页面之后一次性拿到所有内容。好的做法是点开会话之后,找到消息列表的滚动容器,然后模拟“从上往下慢慢滑”的操作,每滑一段就把新读到的内容追加到数组里,一直到滚动容器高度不再变化。这个过程比较费时间,但这是虚拟列表下唯一稳妥的路径。
还有个细节:有些页面会在你向上滚动时自动展开更早的历史记录,这时候你看到的顺序是倒着的。我的做法是每次滚动后给新追加的内容前面加上窗口序号,最后再统一排序拼接,避免顺序错乱。
5.3 登录态、限流、验证码这类账户问题
脚本跑着跑着突然要求重新登录,或者干脆收到限流提示,这事我也踩过。原因通常是操作频率过高,被判定为异常行为。
面对这类问题,首先是别图快。我在脚本里加了随机延时,目的就是让每个操作之间的间隔不太规律。其次,如果长时间运行,宁可中间暂停一下再继续,也别开着脚本去吃饭,让它在极端时间内发起几百次请求。最后,登录态尽量不要每次都重新登录,用持久化用户目录可以大大减少验证次数。第一次手动登录之后,之后的会话只要能恢复 Cookie 就是安全的。
另外,如果脚本是在特殊网络环境下跑的,页面上偶尔会弹出额外的安全验证步骤。这种情况只能手动介入,判断到页面 URL 变成验证页时,主动停下来等人工处理。
5.4 导出到了最后,分不清哪些没抓到
脚本跑完不代表完事。我经常跑完之后发现导出的文件里少了某几个会话,但它们并没有报错,只是静默跳过了。
要避免这种情况,就得在脚本里留审计信息。每次处理完一个会话,把会话标题写进一个清单文件;整个脚本跑完,把清单跟左侧列表的总数做对比。如果总数对不上,就把缺失的标题打印出来,单独补抓一次。
我在脚本里已经放了打印标题的逻辑,你再加一步:把同一个清单追加进一个exported_titles.txt。这样每次导出都有痕迹,哪里断了也能一眼看出来。
6. 最后说点实在的
我自己用这一套流程给人迁过不少对话数据,最大的体会是:真正的难点往往不在脚本本身,而在于你根本没想清楚自己要导出什么。格式定清楚了,脚本怎么改都很顺手;格式没定清楚,再好的工具导出来也是一堆用不上的文本。
如果你只是想把三五个重要会话备份走,手工复制反而比写脚本更快,不要上来就追求自动化。但如果你面对的是几十上百个会话,批量导出脚本能帮你省下一个完整的下午。
最后提醒一句:批量导出的数据基本属于个人使用范畴,用作备份和迁移没有问题,但如果你要把导出的内容拿去二次分发或者做商业化用途,请一定好好看一遍平台的服务条款。尊重数据边界,工具才能长长久久地用下去。