news 2026/9/18 3:47:48

用书签脚本一键导出豆包对话记录:原理与实操

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用书签脚本一键导出豆包对话记录:原理与实操

1. 起因:豆包的对话记录,为什么非要用脚本导出

先交代一下背景。我在豆包里攒了几十段调试代码、写文案、梳理需求的对话,某天想把这些内容整理进本地知识库,结果发现手动一段段复制实在太痛苦了。豆包App端可以逐条选中复制,但在网页端面对一个几十轮的对话,你要么一屏一屏地滚动,要么对着手机屏幕长按选择,碰到代码块还会把格式搅得一团糟。更麻烦的是,如果你想把对话交给其他工具做二次整理,这种手工搬运的效率低到让人崩溃。

我当时的第一个想法是找官方导出入口。翻了一圈设置菜单,都没有提供对话导出功能,只能靠复制粘贴。后来我在逛技术社区的时候看到有人提到"书签脚本"这个思路,瞬间觉得方向对了:书签脚本不需要安装任何客户端,不需要下载软件包,只要在浏览器收藏夹里存一段带javascript:前缀的代码,在豆包对话页面点一下,就能把当前对话内容自动抓取并保存成文件。

这个方案的价值在于,它是纯前端脚本,所有操作都发生在你的浏览器里。对话内容不会被上传到任何第三方服务器,也没有中间环节的数据泄露风险。对于个人知识管理来说,这是最轻量、最可控的一条路。

这篇文章我准备完整记录这套脚本的写法、原理、我在实际使用中踩过的坑,以及导出之后的后续处理。无论你是完全不懂代码的用户,还是想照着改一版自己用的开发者,应该都能从里面找到有用的东西。

2. 书签脚本的底层逻辑:它到底在页面上做了什么

在贴代码之前,我必须先讲清楚书签脚本的原理,否则你遇到问题的时候完全不知道从哪里下手。

2.1 书签脚本到底是什么

书签脚本的英文叫 bookmarklet,本质上就是一段被压缩成一行、以javascript:开头的 JavaScript 代码。你把它存成浏览器书签后,点击它不是打开一个网址,而是让浏览器在当前页面执行这段 JavaScript。

普通的书签存的是https://开头的地址,浏览器负责跳转;书签脚本存的是javascript:协议,浏览器把后面的内容当作程序运行。这意味着它能访问当前页面的 DOM 结构、读取页面上的文本、模拟用户点击,甚至触发下载。换句话说,它拥有浏览器当前标签页的"页面操作权"。

很多人一听"脚本"就紧张,觉得是不是什么黑客工具。其实不是,书签脚本做的事情和你在浏览器开发者工具控制台里敲代码是一样的,只是把代码打包成了一个可以反复点击的按钮。它就是一段你自己写的、运行在你自己浏览器里的代码,权限边界非常明确:只能操作当前打开的页面,拿不到你的密码、聊天记录数据库或者其他无关数据。

2.2 从 DOM 到文件,一条完整的数据链路

书签脚本要完成"导出豆包对话",本质上需要做四件事:

  1. 定位对话容器:在豆包的网页 DOM 里找到存放消息列表的节点。
  2. 提取对话内容:遍历每个消息节点,取出角色标识(用户还是豆包)和文本内容。
  3. 组装导出格式:把内容拼接成便于阅读的 Markdown 或纯文本格式,这一步还可以保留代码块的格式信息。
  4. 触发文件下载:利用浏览器内置的 Blob 和下载机制,把文本内容生成为一个带时间戳的文件。

这个流程听上去简单,实际做的时候有两个核心障碍。第一个是页面结构不稳定,豆包前端一旦改版,原本能选中的节点选择器就可能失效,导致脚本"失灵"。第二个是滚动加载机制,豆包网页端和大多数聊天应用一样,默认只渲染当前视口附近的几十条消息,你得先让页面把历史消息都加载出来,脚本才能全量导出。

2.3 为什么不走接口直接拿数据

可能有人会问:豆包网页端本身肯定有接口能拿历史记录,书签脚本能不能直接调接口?答案是技术上可以试,但我强烈不建议。

第一个原因是接口通常需要鉴权 token,而 token 存放在浏览器的存储或 Cookie 里,直接从书签脚本里拿这些信息再调接口,代码复杂度会急剧上升。第二个原因是接口数据结构、字段名、分页方式都没有公开文档,纯靠抓包逆向维护成本太高。第三个原因其实是最关键的:接口方案会把你的登录态暴露在脚本逻辑里,一旦页面环境被注入恶意代码,风险是实打实的。

所以我的原则很明确:只读页面上的 DOM,不去碰接口。这既是安全边界,也是维护成本最低的方案。页面渲染出来什么,我就导出什么,所见即所得。

3. 完整代码与安装步骤:从空白书签到一键导出

下面直接给出一份我目前正在用的脚本。它经过了豆包网页端多个版本的验证,能够把对话内容导出为 Markdown 文件。

3.1 安装过程

先说明操作步骤,代码在后面,你不需要理解每一行也能用。

  1. 打开浏览器,按Ctrl+D创建一个任意书签,书签名称随便填,比如"导出豆包对话"。
  2. 把书签的网址(URL)清空,替换成下面这段代码,保存。
  3. 打开豆包网页版,进入你想导出的那个对话页面。
  4. 确保页面已经滚动加载出了你需要的全部历史消息(这一点后面会细讲)。
  5. 点击刚保存的书签,浏览器会自动下载一个 Markdown 文件,文件名格式类似doubao-chat-20250120-233011.md

整个操作不需要安装任何插件,也不需要打开开发者工具,是我试过的方案里最顺手的一种。

3.2 脚本源码

javascript:(function(){ // 防止重复注入,如果页面上已经有导出按钮则提示 if (document.getElementById('db-export-tip')) { alert('脚本已经在运行中,请稍候'); return; } // 兼容可能的用户脚本环境 var MSG_SELECTOR = '[class*="chat"] [class*="message"], [class*="conversation"] [class*="item"]'; var nodes = document.querySelectorAll(MSG_SELECTOR); if (!nodes.length) { alert('没有找到对话节点,请确认当前页面是对话详情页'); return; } var lines = []; var seen = new Set(); nodes.forEach(function(node){ var text = (node.innerText || '').trim(); if (!text || text.length < 1) return; if (seen.has(text)) return; seen.add(text); lines.push(text); }); var content = '# 豆包对话导出\n\n' + lines.join('\n\n---\n\n'); var blob = new Blob([content], {type: 'text/markdown;charset=utf-8'}); var url = URL.createObjectURL(blob); var a = document.createElement('a'); var ts = new Date(); function pad(n){ return n < 10 ? '0' + n : '' + n; } var filename = 'doubao-chat-' + ts.getFullYear() + pad(ts.getMonth()+1) + pad(ts.getDate()) + '-' + pad(ts.getHours()) + pad(ts.getMinutes()) + pad(ts.getSeconds()) + '.md'; a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(function(){ URL.revokeObjectURL(url); }, 1000); })();

这段代码的思路是通用的,不是用来绕过任何服务的行为,只是把你屏幕上已经明文显示出来的内容整理成本地文件。

3.3 代码逐段说明

选择器部分是整段脚本最需要关注的地方。豆包的聊天页面里,消息节点通常会有一些和chatmessage相关的 class 名称。我用了一个属性选择器[class*="chat"] [class*="message"]来模糊匹配,这样即使样式类名带着随机后缀也能命中。如果你发现点击书签后没有反应,八成是这里没匹配上,后面我会专门讲怎么排查。

去重逻辑是因为 DOM 节点在滚动复用的时候,有些页面会把已渲染的消息保留在文档流里,遍历时可能重复读取。我用一个Set保存已经出现过的文本,重复内容直接跳过。

Blob 加 a 标签下载是浏览器端生成文件的标准做法。先创建一个二进制大对象,内容就是我们拼好的 Markdown 文本,然后创建一个隐藏的<a>元素,给它的download属性设置文件名,模拟一次点击,浏览器就会自动把文件下载下来。

代码里还有一个细节:URL.revokeObjectURL(url)放在 1 秒后执行。这一步是为了释放浏览器内存,如果不释放,长时间频繁导出会积累内存占用。虽然单次影响不大,但作为习惯还是写上比较好。

3.4 不同浏览器下的小差别

我最常用的是 Chrome 和 Edge,这套脚本在两个浏览器上都稳定运行。Chrome 对自动下载有权限控制,如果第一次点击书签没有弹出下载,去设置里查看一下"下载内容"的权限设置。Firefox 对javascript:书签的支持也一直很好,但 Safari 上偶尔会出现download属性被忽略的情况,表现为不下载文件,而是新开一个页面展示文本,遇到这种情况直接手动保存页面即可。

4. 排查记录:我踩过的坑和对应的修正方案

脚本写出来能用只是第一步。我用了大半年,前前后后遇到过四类问题,每个都让我折腾了一阵子。这里按排查链路写出来,给你省时间。

4.1 问题一:点击书签后提示"没有找到对话节点"

这个是最常见的问题。起因通常是豆包前端改版,聊天消息节点的 class 命名规则变了,导致选择器匹配不到任何元素。

我的排查思路是:先打开浏览器开发者工具(F12),在"Elements"面板里选中任意一条对话气泡,看它的 class 和父级结构,然后重新调整选择器。这个方法对完全不懂前端的用户也友好,你只要会右键"检查"就能做。

实际操作中,我一般会搭配一个懒人方案:在消息区的顶层节点上多抓几层。比如用document.querySelector('main')找到主体区域,再通过innerText直接获取文本。这种做法的缺点是角色标识(用户/豆包)会丢失,但至少能保证内容不丢,适合应急。

4.2 问题二:导出的文件只有屏幕上的内容,历史消息不全

豆包网页端是滚动加载的聊天界面。你打开一个长对话时,页面上只渲染最近的一部分消息,往上滚动的时候,前端会动态加载更早的内容。如果脚本在页面还没加载完历史消息时就执行,导出的自然只有当前已渲染出来的部分。

这个坑绕不开,但处理起来很简单:点击书签之前,先手动把对话滚动到最顶部,再一路滚回底部。这个过程会触发浏览器把全部历史消息加载进 DOM。

如果你在电脑前端上操作,还可以用一个小技巧:在开发者工具的 Console 里执行下面这行命令,让页面自动滚到底部:

var scroller = document.querySelector('[class*="chat"], [class*="conversation"], main'); var timer = setInterval(function(){ if (scroller) scroller.scrollTop = scroller.scrollHeight; }, 300); // 等滚到底之后,在控制台执行 clearInterval(timer) 停止

如果你不愿意开控制台,手动滚动大概也就几秒钟时间,多数情况下完全够用。

4.3 问题三:保存书签时,javascript:代码被浏览器吞掉几个字符

这属于浏览器的"好心办坏事"。创建书签时,有些浏览器会尝试优化或者截断极长的 URL。如果你的书签脚本太长,粘贴保存后可能被截断,导致脚本语法错误或者执行失败。

解决办法有两个。一是把脚本拆短,尽量用简写变量名、去掉不必要的注释。二是利用浏览器的"多步拼接":先把代码保存到本地文本文件里,需要时从文件里复制到书签栏,如果真的被截断,就再粘贴一次。另外,现在大多数浏览器都已经不会主动修改用户粘贴进书签栏的 URL 了,但为了保险起见,保存后重新打开书签编辑框看一眼全文,总归没错。

4.4 问题四:导出内容里混入了大量无关文本,比如推荐内容或侧边栏标题

这是因为我的选择器范围太宽,把页面上属于推荐模块、侧边栏的文本也包进来了。当豆包页面改版时,这种"误伤"非常容易发生。

精修思路是缩小选择器范围:先看准一条对话气泡,把它的老祖宗节点找出来,用相对稳定的祖先节点做限定。比如如果消息列表的外层容器 class 里有一个稳定的[class*="chat-list"],那选择器就写成[class*="chat-list"] [class*="message"],这样推荐内容就进不来了。

我在实际使用中还加了一个"先预览再下载"的拦截逻辑,但书签脚本没法弹出一个带滚动条的预览框,所以我把逻辑改成了:先弹窗提示抓到了多少条消息,并打印前几条内容到控制台,确认无误后再真正执行下载。改起来也很简单,把脚本里var content =之后的下载部分替换成console.log(lines.slice(0, 5))调试,跑通了再改回来。

5. 导出之后:如何把对话整理成知识库素材

脚本导出的是 Markdown 文件,但原始格式不会太精致,毕竟每个消息节点之间只用了分隔线。我会在导出之后做一轮整理,把零散对话变成能放进知识库、能交给其他工具处理的素材。

5.1 给每条消息加上角色前缀和序号

豆包网页端 DOM 里能区分用户和助手消息,但我的脚本为了兼容性,选择直接抓文本片段,所以导出的内容在一开始是"没有角色标注"的。整理阶段的第一件事就是手动补角色信息。

如果是几十条以内的对话,我通常直接在文本编辑器里过一遍,给每条消息前加上**我****豆包**。如果是上百条的长对话,我会改用正则批量处理,方法也很简单:在每个段落前根据关键词匹配加前缀。因为豆包回复里经常出现"我来帮你""可以的"这类内容,不一定能稳定区分,所以批量处理完后还是需要人工抽查。

5.2 转换格式:从 Markdown 到 PDF 或 Word

如果你需要分享给同事,Markdown 文件显然不够友好。我常用的转 PDF 方法是:用 Typora 打开 Markdown 文件,选择"文件-导出-PDF"。Typora 会把代码块、标题格式渲染得比较干净。没有 Typora 的话,用 VS Code 配合 Markdown PDF 插件也可以,效果差不太多。

还有一个更省事的办法:直接通过浏览器打印功能。用任何支持 Markdown 预览的插件打开文件,按Ctrl+P,目标打印机选"另存为 PDF",页面设置选好边距就能输出 PDF。这里的核心技巧是记得勾选"背景图形"选项,否则代码块的灰色底色会丢失。

5.3 批量合并多条对话

我实际场景里经常需要把同一个主题的多个对话合并成一篇文档。手工拼接容易乱,我先把所有要合并的 Markdown 文件放在同一个目录下,然后用一个简单的 Node.js 脚本按文件名顺序合并:

const fs = require('fs'); const path = require('path'); const dir = './chats'; const files = fs.readdirSync(dir).filter(f => f.endsWith('.md')).sort(); let result = files.map(f => { const content = fs.readFileSync(path.join(dir, f), 'utf8'); const title = f.replace('.md', ''); return `# ${title}\n\n${content}`; }).join('\n\n---\n\n'); fs.writeFileSync('./merged.md', result, 'utf8');

这种做法的好处是,合并后每一段对话都有独立的一级标题,目录结构清楚,后续整理检索都方便。

5.4 进入知识库前的关键一步:清洗与去重

导出内容里通常存在两类冗余:一类是对话轮次里的寒暄废话,比如"好的""收到""明白了";另一类是同一句话因为系统渲染原因在 DOM 里出现了两次。清洗时我会先用正则删除完全重复的行,再手工扫一眼,把没有信息量的短句删掉。这一步虽然费时间,但直接决定知识库的检索质量,跳过的话后面用起来会非常糟心。

6. 边界与安全:书签脚本能做什么、不能做什么

最后说点很多人容易误解的边界问题。

书签脚本确实很强大,但它的控制范围仅限于你当前打开的标签页。它读不到浏览器其他标签页的内容,碰不到你保存在浏览器密码管理器里的账号密码,更不会对豆包服务器造成什么额外请求。从安全角度看,它的权限比一个浏览器扩展还要小,扩展可以通过 manifest 申请跨域权限,而书签脚本根本没有这个机会。

但反过来也要提醒一点:不要在你的个人电脑上随便运行陌生人提供的书签脚本。虽然书签脚本只能在当前页面运行,但恶意脚本完全可以读取当前页面上显示的所有内容,包括你在某个后台页面里输入的临时内容,然后把它们偷偷发到一个钓鱼服务器。我这个脚本从头到尾没有发起任何网络上传动作,唯一涉及网络的只有下载文件这一个本地操作。你如果看到别人的书签脚本,第一反应应该是看它里面有没有fetchXMLHttpRequestnavigator.sendBeacon这类发请求的 API,一旦出现,就要警惕它是否在偷偷上传数据。

关于导出内容的合规问题,这里多提一句。书签脚本导出的内容,是你自己账号下、你自己主动发起的对话记录。这些内容的所有权和处置权重在你,但传播和二次使用时依然要注意边界——涉及个人隐私的信息不要随意发到公开渠道,涉及工作内容的要遵守公司的信息安全规定。脚本工具本身是中性的,关键看你怎么用。

再回到技术层面,这套"书签脚本导出页面内容"的思路,不只适用于豆包。你把它稍作改动,完全可以用于导出其他网页上的表格、文章列表、聊天记录。核心方法论是通用的:确认目标页面结构,设计合适的选择器,读取可见文本,组装成文件格式,触发下载。掌握这一套之后,你其实就拥有了一双"网页内容剪贴板"的手,遇到重复性的复制粘贴工作都可以想一想,能不能用一个小书签来解决。

说起我自己用下来的体会,书签脚本最大的优势不是技术含量,而是"轻"。没有安装门槛,没有权限申请,没有后台进程,将来不用了直接删掉书签就消失得无影无踪。对于像我这种经常需要在网页端整理内容的人来说,它是投入产出比极高的小工具。你如果也被手工复制对话搞得不耐烦,不妨按这篇文章的步骤试一把,五分钟就能跑通第一版,后面再按自己的需求慢慢打磨。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 3:46:28

如果 Rene 只做 newsletter 挑论文,TaoToken Key 该放在哪一步

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 3:44:09

Deformable DETR可变形注意力:端到端目标检测实战与调优

1. 从 DETR 到 Deformable DETR&#xff1a;这个项目究竟在解决什么问题Deformable DETR 是我这两年做检测落地时回头率最高的一个结构。它属于 Transformers 在视觉检测方向的一条重要分支——把注意力机制从"一视同仁地看全图"改成"每个查询只在少数关键位置上…

作者头像 李华
网站建设 2026/9/18 3:42:29

OA与SAP RFC接口对接实战:从报销场景看财务凭证同步

OA和SAP的RFC接口对接&#xff0c;我前前后后做了好几个项目&#xff0c;从最早的财务凭证同步&#xff0c;到后来的人力组织架构集成&#xff0c;再到这次员工报销&#xff0c;算是把这条链路摸了一遍。这篇就把报销场景下&#xff0c;OA通过RFC调用SAP接口的完整落地过程写出…

作者头像 李华
网站建设 2026/9/18 3:41:20

Anaconda3-5.2.0:Python 3.6兼容性锚点与Windows老旧环境部署指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华