不是让 AI 随便写:我如何用 Specs 驱动做出一个浏览器扩展 MVP

NathanLVZS   |   Sunday 24 May 2026


Category:  AI  Web


这篇文章记录“小红书笔记剪藏 / RedNote Clipper”浏览器插件从需求沟通、规格拆解、实现迭代到发布收口的过程。比起展示一个小工具本身,我更想沉淀的是这背后的开发方式:不是让 AI 随便写,而是用 Specs 驱动 AI 协作,把一个想法推进成可以发布的 MVP。

浏览器插件地址:

开场

过去一段时间,我做了一个简单的浏览器扩展,叫“小红书笔记剪藏 / RedNote Clipper”。

它解决的是一个很具体的自用需求:我在小红书上看到一篇有价值的笔记时,希望可以顺手把它保存到本地,而不是只丢进站内收藏夹。站内收藏夹的问题是难以管理、阅读体验差,也不方便继续做 AI 加工。导出的内容最好不是一张截图,也不是一段复制出来的文本,而是一个可以继续整理、搜索、归档的本地资料包。

当前版本支持导出当前打开的单篇笔记,保存标题、正文、作者、发布时间、标签、互动统计、图片或视频媒体,也支持手动选择评论一起导出。导出结果包含 note.jsonnote.mdnote.htmlmedia/ 目录。

从结果看,它像是一个小工具。但对我来说,这个项目更值得记录的是开发方式:它不是让 AI 随便写,也不是靠一句 prompt 直接生成一个可发布产品,而是一个 Specs 驱动的 AI 协作开发过程。

为什么不是纯 Vibe Coding

现在很多人会把 AI 辅助开发概括成 Vibe Coding。这个词很容易让人误解,好像开发者只要描述一个大概感觉,剩下的就交给 AI 自由发挥。

这次项目不是这样。需求不是边写边漂移出来的,方向也不是靠实现过程里的灵感随机调整出来的。相反,在正式实现之前,我和 AI 反复沟通,把需求、边界、非目标、数据模型、任务拆解和验收方式都沉淀到了 specs 里。

真正的工作方式更接近:

  1. 先把模糊想法规格化。
  2. 再按 specs 推进最小闭环。
  3. 实现中遇到真实问题后,回到设计、测试和文档里收口。
  4. 最后把问题复盘成下一个项目可以复用的方法论。

所以,如果一定要给这个过程起一个名字,我更愿意叫它:

Specs 驱动的 AI 协作开发。

AI 参与了需求澄清、方案拆解、代码实现、测试补充、文档维护和发布材料整理,但项目的控制系统不是 AI 的一次性生成能力,而是持续更新的规格、边界和验收标准。

先把需求规格化

项目最早的需求其实很朴素:浏览小红书时,看到一篇有价值的笔记,可以轻量地保存到本地。

这个需求如果不规格化,很容易发散成一个过大的产品:

  • 要不要批量导出收藏夹?
  • 要不要自动抓取作者主页?
  • 要不要做云端同步?
  • 要不要做在线管理后台?
  • 要不要把评论全量展开?
  • 要不要做成知识库系统?

这些方向都可以想象,但它们不是 V1 该做的事。

所以 specs 里先确定了产品目标和非目标。V1 只做“当前打开的单篇笔记”,只在用户明确触发时导出,只保存到本地,不做批量采集、不做无人值守、不做云端同步,也不主动抓取用户没有浏览到的内容。

这个边界非常重要。它让项目从一开始就不是“采集效率最大化”的工具,而是一个“浏览时顺手收藏并导出”的个人整理工具。

同时,导出模型也被提前定义清楚。V1 的导出包包含:

<export-root>/
  note.json
  note.md
  note.html
  media/

其中:

  • note.json 负责结构化保真。
  • note.md 负责直接阅读、检索和导入知识库。
  • note.html 负责离线打开和更好的阅读呈现。
  • media/ 负责保存图片、视频、封面和评论图片。

这一步的价值是,后续写代码时不需要反复争论“到底要导出什么”。实现可以调整,但目标产物是明确的。

V1 边界比功能清单更重要

这个项目最重要的工程判断之一,是不断把边界写清楚。

V1 支持:

  • 当前单篇笔记导出。
  • 用户主动点击触发。
  • 页面内轻量悬浮入口。
  • 手动选择当前页面已出现的评论。
  • 本地生成 note.jsonnote.mdnote.html 和媒体文件。
  • 图片、视频、视频封面、Live 图封面和评论图片的相对路径引用。

V1 不支持:

  • 批量导出多个笔记。
  • 自动遍历作者主页、搜索页或列表页。
  • 自动展开全部评论。
  • 后台持续采集。
  • 云端同步。
  • 绕过平台没有暴露的数据边界。
  • 承诺所有媒体都能拿到理论最高画质。

边界不是为了少做功能,而是为了保证每个已做的功能能形成闭环。

比如评论选择。如果目标是“把所有评论都保存下来”,实现会立刻变成自动展开、翻页、加载、排序、去重和风控问题。但 V1 的目标是“用户手动选择当前页面已出现的评论”。这个边界让交互、数据模型和导出逻辑都变得可控。

再比如媒体。项目不承诺突破平台没有稳定暴露的资源,只在页面状态、DOM 和用户真实浏览过程中可获得的范围内做稳定导出。这不是能力不足的托辞,而是产品边界的一部分。

按 Specs 实现主链路

规格明确之后,代码结构也比较自然地收敛成四层:

  • content:负责页面识别、悬浮面板、评论选择、笔记与评论的 DOM/状态抽取。
  • bridge:负责 content script 和 background 之间的消息协议。
  • background:负责导出任务编排、媒体下载、评论图片缓存命中和导出阶段增强。
  • core:负责纯逻辑,包括导出模型、命名、Markdown/HTML 渲染、媒体 URL 归一化。

核心数据流是:

  1. 内容脚本识别当前页面是否为单篇笔记页。
  2. 用户点击导出后,内容脚本读取页面状态和 DOM,生成 ExtractedNotePayload
  3. 内容脚本通过消息协议把 payload 发给 background。
  4. background 补充评论图片缓存命中,再调用导出组装逻辑生成文本文件和媒体任务。
  5. background 使用浏览器下载能力把 note.jsonnote.mdnote.html 和媒体文件写入本地。
  6. 导出阶段校验实际落盘路径,确保文档引用和本地文件一致。

这里 AI 的价值很明显。它可以快速根据 specs 拆模块、生成候选实现、补齐测试、整理文档。但它不能替代最核心的判断:哪些逻辑应该放在 content,哪些应该放在 background,哪些应该沉到 core 作为纯函数。

如果没有 specs,AI 很容易把功能写成“哪里方便写哪里”。有了 specs,协作方式就变成了:围绕明确边界,让 AI 帮你更快到达可验证的实现。

问题驱动的迭代

这个项目最值得复盘的地方,不是主链路第一次跑通,而是后续真实问题如何推动设计收口。

很多问题一开始看起来只是 bug,但复盘后会发现,它们其实暴露的是某个边界、模型或验收方式还不够清楚。

导出产物本身就是最高优先级的验收对象

当项目进入“能导出”之后,问题重心很快从“按钮能不能点”转向“导出的结果是否可信”。

真实反馈里出现过这样的情况:

  • media/ 里实际保存的是 image-01.webp
  • note.jsonnote.md 里引用的是 image-01.jpg

这不是小显示问题,而是导出包的一致性问题。只要路径不一致,用户就无法把这个资料包稳定放进 Obsidian、静态站点或其他知识库系统里。

这件事推动了一个重要原则:

对导出型产品来说,最终产物本身就是最高优先级的验收对象。

所以后续不再只看“有没有触发下载”,而是要检查 note.jsonnote.mdnote.htmlmedia/ 是否互相一致。

对应实现也做了收口:图片类媒体先转成 data: URL 再写入声明路径,普通视频和 Live 动态视频保持直接下载,但必须校验浏览器实际落盘路径,避免文档里写的是一套路径,文件系统里保存的是另一套路径。

评论抽取要优先保证语义干净

评论区的问题也很典型。

真实页面里,评论文本可能混入“展开 / 收起”等交互文案;作者昵称旁可能混入“作者”徽标;楼中楼评论的时间可能和正文、地区、回复提示混在同一个区域;图片里还可能有表情、贴纸和装饰图。

如果只是粗暴读取可见文本和图片,短期看能拿到内容,长期看会污染结构化数据。

这推动了几个实现原则:

  • 评论正文要过滤交互文案。
  • 作者名要去掉徽标、标签和装饰节点。
  • 时间提取不能靠宽松全文扫描,要优先识别明确的时间节点和短时间格式。
  • 评论图片要区分真正配图和表情贴纸。
  • 用户手选评论的导出顺序要尊重点击顺序,而不是强行恢复 DOM 顺序。

这些问题修完以后,代码只是表层变化,更重要的是方法论变了:评论区不是一个纯文本容器,而是一个视觉结构复杂、语义边界容易污染的区域。抽取逻辑必须优先保证字段语义干净。

正文格式也是内容的一部分

正文提取也经历了类似收口。

一开始如果只关心“文本有没有拿到”,把空白压成单个空格似乎也能接受。但导出结果要直接阅读时,段落和换行本身就是内容的一部分。

真实导出里出现过正文被压成一整段的问题。修复之后,正文提取优先从 DOM 获取,保留块级元素和 <br> 带来的换行结构;结构化状态里的 desc 只作为兜底。

这件事沉淀出的原则是:

用于归档阅读的内容,不只要文本完整,也要保留基本阅读结构。

后续 note.html 的设计也沿着这个原则展开:它不是复刻小红书网页,而是一个静态、离线、适合归档阅读的文档页。

评论图片高清不是无条件承诺

评论图片是另一个典型边界问题。

当前比较稳定的现实是:页面上默认可见的通常是预览图;如果用户打开过评论大图,浏览过程中可以缓存到更高清的资源。于是正式链路采用的是:

  • 默认导出页面可见缩略图。
  • 用户浏览过评论大图时,通过本地缓存优先导出高清图。
  • 导出结果中保留 resolutionSource,说明图片来源。

这里没有去做“根据缩略图 URL 猜原图”的激进方案。因为这类方案可能短期有效,但不稳定,也容易偏离产品边界。

这个问题沉淀出的原则是:

当平台没有稳定暴露某类资源时,产品应该明确边界,而不是用不可靠猜测伪装成能力。

真实环境验证要克制,但不能完全缺席

这类浏览器扩展最自然的实现方式,其实是让 AI 直接打开真实页面:观察 DOM、读取页面状态、注入扩展、触发导出,再根据真实结果不断修正实现。

如果没有平台约束,这会是效率最高的闭环。因为页面结构、懒加载、SPA 路由、媒体字段、评论区 DOM 都不是靠静态想象能完整推出来的,真实页面反馈越快,实现越不容易偏。

但小红书这类站点有风控约束,自动化访问、重复打开、脚本请求、批量验证都可能偏离正常用户行为。项目级规则也明确要求,默认不要让 Agent 自动请求、抓取或访问真实小红书页面进行验证。

所以验证策略被拆成几层:

  • 构建验证:确认扩展产物能生成。
  • 本地逻辑验证:用离线样本、测试夹具和导出模型验证核心逻辑。
  • 真实页注入验证:只在用户明确提供真实链接并主动配合时进行,且优先做诊断,不做批量访问。

这也推动了 verify:live 这类脚本变成工程资产。它不是用来“自动抓页面”的,而是用来诊断扩展是否加载、权限是否正常、content script 是否执行、页面识别和 UI 挂载是否成功。

这个边界对后续项目很有参考价值:真实环境反馈很重要,但不能把所有验证都压到真实站点上。离线样本、导出目录和可复跑测试应该承担更多回归职责;真实页面验证则应该尽量克制、可诊断、由用户明确触发。

缺少自动真实验证时,调研可以帮助破局

真实页面自动验证受限之后,AI 在实现过程中更容易绕圈。

有些卡点并不是代码本身复杂,而是缺少足够真实的外部事实。例如:页面状态里到底有哪些媒体字段,图片 URL 的不同来源有什么差异,Live 图动态资源通常暴露在哪里,评论图片高清资源是否有稳定入口。

如果这时只让 AI 在本地代码里反复猜,很容易出现两种问题:

  • 对不存在的字段写过度兼容逻辑。
  • 对真实页面已有的模式视而不见,迟迟找不到正确抽取入口。

这个阶段,网络搜索和公开资料调研就变得有价值。它不能替代真实样本,也不能直接变成最终方案,但可以提供参考方向:别人如何理解页面状态、哪些字段更常见、哪些资源链接只是缩略图、哪些所谓“原图方案”并不稳定。

项目里的图片公开方案调研就是这个作用。它没有直接决定实现,但帮助我们把问题分成几类:

  • 笔记主图可以优先关注页面状态中的 imageList
  • DOM 中的可见图片更适合作为回退,而不是主来源。
  • 评论图片高清资源不能只靠缩略图 URL 猜测。
  • Live 图只有在状态字段暴露动态资源时才能稳定导出。

这件事沉淀出的原则是:

当真实自动验证受限时,不要让 AI 在本地闭门造车;应该用公开资料、用户样本、离线夹具和最小真实诊断共同缩小搜索空间。

调研不是为了找一个可以照抄的答案,而是为了减少无效试错,让实现重新回到可验证的问题上。

把问题沉淀成方法论

这个项目里,问题修复不是终点。更重要的是把问题放回项目资产里。

大致有几种沉淀方式:

  • 已完成并稳定的能力,写回验收清单。
  • 已确认但未彻底解决的问题,进入未解决问题清单。
  • 与某次迭代强相关的过程经验,写进 journal。
  • 影响产品边界或技术边界的内容,更新 specs 或 architecture。
  • 真实出现过的问题,尽量补成离线回归测试。
  • 面向上架和传播的表达,进入 release 文档。

比如媒体路径一致性问题,最后不只是修了下载逻辑,还被写进了任务清单和验收要求。

比如 Live 图动态资源,如果页面状态里有 imageList[*].livePhoto + stream.h264,当前链路可以稳定导出;如果只剩 DOM 里的 blob:,就不能假装已经解决,而是进入未解决问题清单,等待新的样本和单独设计。

这种做法的好处是,项目不会只依赖开发者记忆。每一个真实问题都尽量转化为某种可复用资产:测试、文档、设计约束或后续任务。

AI 的实际价值

经过这个项目,我对 AI 协作开发的感受更清楚了一点。

AI 最有价值的地方不是“替我决定做什么”,而是:

  • 把模糊需求快速拆成文档结构。
  • 根据 specs 生成多个实现候选。
  • 在已有代码风格下做局部实现。
  • 帮忙检查上下游影响。
  • 把真实问题转成测试样例。
  • 把过程经验整理成 journal、release 或 specs。
  • 在长链路任务里保持上下文连续。

但 AI 不应该替代这些事情:

  • 产品边界判断。
  • 风险边界判断。
  • 哪些能力进入 V1,哪些明确不做。
  • 某个问题是抽取层、导出层、缓存层还是验证层的问题。
  • 最终产物是否符合用户真实使用场景。

换句话说,AI 可以让实现速度更快,但不能让工程判断变得可有可无。

如果没有 specs,AI 越快,项目越容易发散。如果有 specs,AI 的速度才会变成生产力,而不是噪声。

从功能到发布

另一个容易被低估的阶段,是从“功能能用”到“可以发布”。

这个项目后期不只是继续写代码,还补了:

  • note.html 静态阅读页。
  • Chrome / Edge 商店文案。
  • 隐私政策草稿。
  • 审核答复模板。
  • 发布说明模板。
  • 素材验收清单。
  • 小红书宣传笔记策略和正文草稿。

这些工作看起来不如写功能有技术感,但它们是产品化的一部分。

尤其是隐私和能力边界表达,必须和真实实现一致。文案里不能宣称批量导出、自动采集、全量备份收藏夹,也不能暗示绕过限制。能说的就是当前真实支持的能力:用户主动导出当前单篇笔记,结果保存在本地,不上传到开发者服务器。

这也再次说明 specs 的价值。发布材料不是凭空写出来的,而是从产品边界、数据模型和实现能力里自然长出来的。

可复用流程

如果把这次经验抽象成下一个项目可以复用的流程,我会这样总结:

  1. 需求沟通 先和 AI 反复问清楚真实场景、用户动作、输入输出和非目标。

  2. Specs 固化 把需求、设计、任务、验收、风险分别写下来,不让实现阶段承担所有决策。

  3. 最小闭环 只实现能证明价值的主链路,避免一开始做重型系统。

  4. 问题分层 遇到 bug 先判断属于抽取、导出、缓存、渲染、下载、验证还是发布表达问题。

  5. 离线回归 真实出现过的问题,尽量转成离线样本和可复跑测试。

  6. 文档回写 修复后更新 specs、journal、验收清单或未解决问题清单,避免经验只留在聊天记录里。

  7. 发布收口 功能稳定后,同步收口隐私、商店文案、使用说明、素材和审核答复。

这个流程的核心不是文档越多越好,而是让每份文档承担明确职责。Specs 控制方向,tests 控制回归,journal 沉淀经验,release 承接发布。

结尾

这次做“小红书笔记剪藏”,我最大的收获不是“AI 帮我写了多少代码”,而是更清楚地看到了 AI 协作开发的控制点。

AI 可以加快沟通、拆解、实现和复盘,但前提是项目有清晰的规格、边界和验收对象。否则,所谓 Vibe Coding 很容易变成“功能不断冒出来,但系统越来越难收口”。

对我来说,更可持续的方式是 Specs 驱动:

先把需求说清楚,再让 AI 加速实现;实现中暴露的问题,继续反向沉淀成设计、测试、文档和方法论。

这不是最炫的 AI 编程方式,但它更接近一个真实项目从想法走到可发布 MVP 的过程。

上一篇: PAC代理网络下Python网络请求
下一篇: 这个可以有