浏览器扩展+AI Agent:用自然语言生成Userscript的工程实践

发布时间:2026/10/8 15:24:48
浏览器扩展+AI Agent:用自然语言生成Userscript的工程实践 1. 项目缘起与核心思路拆解1.1 这个扩展到底在解决什么问题浏览器扩展生态里有一个长期存在的尴尬用户想要的功能往往非常具体具体到没有哪个开发者愿意专门为它写一个扩展。比如你想在某个内部管理系统里批量导出表格数据、想在某个论坛自动折叠已读帖子、想把某个页面的特定信息抓取下来整理成 CSV——这些需求真实存在但受众太窄没人会为它单独上架一个扩展。Userscripts用户脚本本来是解决这类长尾需求的标准答案。Tampermonkey、Violentmonkey 这类脚本管理器让用户可以在任意页面上注入自定义 JavaScript理论上什么都能做。但问题在于写脚本本身有门槛。你得懂 DOM 操作、得知道怎么处理异步请求、得理解页面加载时序还得自己调试。对于非专业开发者来说这个门槛足以劝退。Usermods 这个项目的核心思路就是把写用户脚本这件事交给一个 coding agent编码智能体来做。你不需要自己写代码只需要用自然语言描述你想要什么扩展内置的 agent 会理解页面结构、生成对应的 userscript、并且直接在浏览器里运行它。这本质上是在 userscript 管理器和 AI 编码助手之间架了一座桥。我第一眼看到这个标题时的判断是这不是又一个AI 帮你写代码的套壳产品因为它的场景非常聚焦——浏览器内的、针对当前页面的、即时生效的脚本生成。这个聚焦点决定了它的技术架构和普通代码助手完全不同。1.2 为什么是扩展 agent这个组合要理解这个设计得先想清楚一个关键问题为什么 agent 要跑在浏览器扩展里而不是做成一个网页应用答案在于上下文。一个 coding agent 要生成能用的 userscript它必须知道目标页面的真实结构——DOM 长什么样、有哪些关键元素、用了什么框架、有没有动态加载。如果做成网页应用用户得手动把页面 HTML 复制粘贴过去这个体验直接崩了。而浏览器扩展天然拥有对当前标签页的完全访问权限agent 可以直接读取页面 DOM、执行探测脚本、甚至在生成后立即注入测试。另一个考量是执行闭环。userscript 的最终归宿是在浏览器里运行。如果生成和运行分离在两个环境里用户还得手动复制脚本、打开脚本管理器、新建脚本、粘贴、保存、刷新页面。这个流程每多一步流失率就高一截。扩展内集成 agent 之后从描述需求到脚本生效可以压缩到一次点击。从工程角度看这个组合也带来了一些必须解决的难题agent 的推理需要调用大模型 API扩展里怎么管理密钥和请求生成的脚本可能有 bug怎么让用户快速迭代页面结构复杂时怎么给 agent 提供足够的上下文又不超出 token 限制这些问题的处理方式基本决定了一个这类产品的成败。1.3 目标用户与典型场景我把这个项目的目标用户分成三类每类的诉求和痛点都不太一样第一类是非技术背景的效率型用户。他们不懂编程但每天要在某些网页上重复做机械操作。比如电商运营要每天从后台导出订单、HR 要从招聘网站批量下载简历、财务要从多个系统里对账。这类用户最看重的是描述一下就能用对脚本质量的要求是能跑就行。第二类是懂一点技术的半专业用户。他们会看代码、能改简单的 JS但不想从零写。这类用户会把 agent 生成的脚本当作起点自己再微调。他们对生成质量要求更高希望 agent 生成的代码结构清晰、有注释、方便修改。第三类是专业开发者。他们其实自己就能写脚本但用 agent 可以省去查文档、试错的时间。这类用户最在意的是 agent 能不能理解复杂需求、能不能处理边界情况、生成的代码是否符合自己的风格。这三类用户对同一个产品的期待差异很大这也是这类工具设计时最纠结的地方——做得太简单满足不了专业用户做得太复杂又吓跑小白。Usermods 选择的方向看起来是偏向第一类和第二类把降低门槛放在首位。2. 核心技术点深度解析2.1 Coding Agent 在浏览器环境里的特殊约束把一个 coding agent 塞进浏览器扩展和把它放在服务器或 CLI 里面临的约束完全不同。我在实际折腾类似架构时踩过不少坑这里把关键差异梳理一下。约束一上下文窗口的稀缺性。一个现代网页的完整 DOM 序列化后动辄几万到几十万字符直接塞给模型是不可能的。所以 agent 必须有一套页面摘要机制——只提取关键信息比如页面标题、主要容器的结构、可交互元素的列表、用到的框架特征等。这个摘要的质量直接决定生成脚本的准确率。我见过的一些实现会先用启发式规则筛选出可能相关的元素再让模型基于这些元素推理。约束二无法执行任意系统命令。服务器端的 coding agent 可以跑测试、装依赖、执行构建。浏览器扩展里的 agent 只能做有限的事读取 DOM、执行注入的 JS、发起网络请求。这意味着 agent 的验证环节必须重新设计——它不能跑单元测试只能通过实际注入脚本、观察执行结果来判断对错。约束三API 密钥的安全管理。扩展要调用大模型 API密钥存哪里是个问题。存在扩展的 storage 里相对安全但用户得自己填。有些产品会走自己的后端代理但这样又引入了服务端成本和隐私顾虑。Usermods 这类工具通常让用户自带密钥BYOK这也是目前比较务实的做法。约束四网络请求的跨域限制。扩展的 background script 可以跨域请求但 content script 受页面同源策略约束。agent 调用模型 API 的请求必须走 background而读取页面信息的操作在 content script 里。这两者之间的消息传递需要仔细设计否则容易出现时序问题。2.2 Userscript 生成的关键技术环节生成一个能用的 userscript远不止让模型写段 JS这么简单。我把整个链路拆成几个关键环节每个环节都有坑。环节一页面结构探测。agent 需要知道目标页面长什么样。常见做法是注入一段探测脚本收集页面上的关键元素信息——标签名、class、id、文本内容、层级关系。但这里有个陷阱很多现代网站用 React、Vue 这类框架DOM 是动态生成的探测时机不对就会拿到空结构。所以探测脚本通常要等页面稳定后再执行或者监听 DOM 变化。环节二需求到选择器的映射。用户说把所有的商品价格抓下来agent 得把这句话翻译成具体的选择器。这需要它理解页面语义——哪个元素是价格、哪个是商品名。如果页面结构规整模型能猜个八九不离十如果结构混乱就得靠多轮交互让用户确认。环节三脚本骨架的生成。一个规范的 userscript 有固定的元数据块// UserScript那一段包含名称、匹配规则、运行时机等。agent 必须正确生成这些元数据否则脚本管理器不会正确加载。运行时机run-at尤其关键——document-start、document-end、document-idle三个选项对应完全不同的执行环境选错了脚本就废了。环节四异步与等待处理。页面元素可能延迟加载脚本必须处理元素还没出现的情况。成熟的写法是用MutationObserver监听 DOM 变化或者用轮询等待元素出现。agent 生成的脚本如果直接document.querySelector然后操作遇到动态页面就会报错。这是新手脚本最常见的 bug 来源。环节五样式注入与隔离。如果脚本要修改页面样式得考虑 CSS 优先级和隔离问题。直接改元素 style 可能被页面原有样式覆盖用!important又可能影响其他部分。更稳妥的做法是注入独立的 style 标签用足够具体的选择器。2.3 扩展架构的模块划分一个能跑 agent 的浏览器扩展架构上通常分成这么几块我按数据流顺序说Popup / Side Panel交互层。用户在这里输入需求、查看生成结果、点击运行。这一层要处理输入、展示 agent 的思考过程如果暴露的话、提供脚本预览和编辑入口。设计上最大的挑战是agent 生成需要时间用户等待期间要有反馈不能让人以为卡死了。Background Service Worker调度层。负责调用模型 API、管理会话状态、协调 content script。这里要注意 Manifest V3 对 service worker 的生命周期限制——它可能随时被浏览器回收所以状态不能只存在内存里得持久化到 storage。Content Script执行层。注入到目标页面负责读取 DOM、执行生成的脚本、把结果回传给 background。这一层和页面的隔离性要处理好避免和页面原有脚本冲突。Storage持久层。存用户配置、API 密钥、历史生成的脚本、用户偏好等。用chrome.storage.local还是sync取决于是否需要跨设备同步。这四层之间的消息传递是这类扩展最容易出 bug 的地方。我建议所有跨层通信都定义清晰的消息协议用 TypeScript 的类型系统约束否则调试起来非常痛苦。3. 实操过程与核心环节实现3.1 从零搭建一个最小可用的原型假设你要自己复现一个类似 Usermods 的原型我按实际开发顺序给你梳理一遍。这里以 Chrome 扩展Manifest V3为例其他浏览器大同小异。第一步搭扩展骨架。先建一个最小扩展包含manifest.json、一个 popup 页面、一个 background service worker、一个 content script。manifest 里要声明activeTab、scripting、storage权限以及目标页面的 host 权限。这里有个经验host 权限不要一上来就申请all_urls先用activeTab配合用户主动触发能减少审核麻烦也更符合最小权限原则。{ manifest_version: 3, name: Usermods Prototype, version: 0.1.0, permissions: [activeTab, scripting, storage], background: { service_worker: background.js }, action: { default_popup: popup.html } }第二步实现页面探测。在 content script 里写一个函数收集当前页面的关键信息。我的做法是提取页面标题、URL、主要 landmark 元素header、main、article 等、所有带 id 或特定 class 的元素、以及可交互元素button、a、input的列表。每个元素记录标签名、选择器路径、文本摘要。这个摘要要控制体积我一般限制在 8000 字符以内。function summarizePage() { const landmarks [...document.querySelectorAll(header, main, article, section, nav, aside)] .slice(0, 20) .map(el ({ tag: el.tagName.toLowerCase(), id: el.id || null, cls: el.className ? String(el.className).slice(0, 80) : null, text: (el.innerText || ).slice(0, 120) })); const interactive [...document.querySelectorAll(button, a, input, select)] .slice(0, 60) .map(el ({ tag: el.tagName.toLowerCase(), text: (el.innerText || el.value || el.placeholder || ).slice(0, 60), id: el.id || null })); return { title: document.title, url: location.href, landmarks, interactive }; }第三步接模型 API。在 background 里封装一个调用函数把页面摘要和用户需求拼成 prompt发给模型。这里的关键是 prompt 设计——要明确告诉模型输出格式比如只输出 JS 代码不要解释要给出 userscript 的元数据模板要强调处理异步和边界情况。我实测下来prompt 里加几个反面例子比如不要用 document.querySelector 直接操作可能不存在的元素能显著降低生成 bug 的概率。第四步注入执行。拿到模型返回的脚本后用chrome.scripting.executeScript注入到目标页面。这里要注意注入的脚本运行在页面的主世界MAIN world还是隔离世界ISOLATED world如果要操作页面 DOM隔离世界就够了如果要调用页面自己的 JS 函数得用主世界。Userscript 通常用隔离世界避免和页面脚本冲突。第五步结果反馈与迭代。脚本执行后把结果成功/失败、控制台输出、DOM 变化回传给 popup 展示。如果失败让用户能补充说明重新生成。这个迭代循环是产品体验的核心做得顺不顺直接决定用户会不会继续用。3.2 Prompt 工程的关键细节这类产品的效果七成取决于 prompt 设计。我把实践中总结的几个要点列出来要点一明确输出契约。在 system prompt 里硬性规定输出格式比如只输出一个完整的 userscript包含元数据块不要任何解释文字。模型很爱加解释不加约束的话返回内容没法直接用。要点二提供元数据模板。把// UserScript块的字段和取值规则写清楚特别是match和run-at。match要根据当前页面 URL 自动生成run-at默认用document-idle除非用户需求明确要求更早执行。要点三强制异步安全。在 prompt 里明确要求所有 DOM 操作前必须等待元素出现使用 MutationObserver 或轮询。这一条能挡掉大量运行时错误。要点四限制 API 使用。明确告诉模型只能用浏览器原生 API不要引入外部库除非用户明确要求。引入 CDN 依赖会让脚本变脆而且有安全风险。要点五错误处理。要求生成的脚本包含 try-catch出错时用console.error输出可读信息。这样用户遇到问题时至少知道哪里错了。3.3 参数选择与性能权衡实际部署时有几个参数需要仔细权衡我给出我的经验值参数推荐值说明页面摘要字符上限6000-8000太小信息不足太大浪费 token 且可能超限模型温度0.2-0.4代码生成需要确定性温度太高会生成不稳定代码最大输出 token2000-4000userscript 一般不会太长留足余量即可请求超时30-60 秒模型响应慢时要给用户反馈不能无限等重试次数2-3 次网络抖动时自动重试但要有上限避免死循环温度这个参数特别值得说。我试过 0.7 的温度生成的脚本虽然有创意但经常用一些奇怪的写法稳定性差。降到 0.3 之后代码风格明显更规范虽然偶尔会保守一点但整体可用性高得多。代码生成任务确定性比多样性重要。4. 常见问题与排查技巧实录4.1 生成脚本不生效的排查路径这是最高频的问题用户描述完需求脚本生成了但页面上什么都没发生。我整理了一套排查顺序按这个走基本能定位到问题。第一查脚本有没有被注入。打开开发者工具的 Console看有没有脚本相关的日志。如果连日志都没有说明注入环节就失败了。常见原因是权限不足或目标页面是特殊页面比如chrome://开头的页面不允许注入。第二查选择器对不对。在 Console 里手动跑一下脚本用的选择器看能不能选到元素。选不到的话要么是页面结构变了要么是模型猜错了。这时候让用户手动指认目标元素把准确的选择器喂给模型重新生成。第三查执行时机对不对。如果选择器在 Console 里能选到但脚本里选不到多半是执行太早元素还没渲染。把run-at改成document-idle或者加等待逻辑。第四查有没有报错。看 Console 里的红色错误。常见的错误包括调用了不存在的方法、跨域请求被拦、和页面原有脚本冲突。跨域问题尤其常见脚本里发 fetch 请求时要注意目标接口的 CORS 策略。第五查是不是被页面框架覆盖了。有些页面特别是 SPA会在脚本执行后重新渲染把脚本做的修改冲掉。这种情况要用 MutationObserver 持续监听或者在框架的渲染钩子里操作。4.2 常见问题速查表现象可能原因解决方向脚本完全不执行权限不足/特殊页面检查 host 权限避开受限页面选择器选不到元素结构变化/时机太早重新探测结构延迟执行修改被覆盖SPA 重新渲染用 MutationObserver 持续监听请求失败CORS/接口变更检查接口策略改用 background 代理脚本报错生成代码有 bug看 Console 错误补充说明重新生成页面变卡脚本死循环/高频监听检查循环和监听器加节流和其他扩展冲突全局变量污染用 IIFE 包裹避免全局变量4.3 几个我踩过的坑坑一不要相信模型对页面结构的想象。早期我图省事只把页面 URL 和用户需求发给模型让它自己猜结构。结果生成的脚本十有八九选择器是错的。后来老老实实做页面探测把真实结构喂进去成功率立刻上来了。模型再强也猜不到你那个内部系统的 class 名叫什么。坑二match写太宽会误伤。有次生成的脚本match写成了*://*/*结果在用户所有页面上都执行了把别的网站搞乱了。后来我在 prompt 里强制要求match必须基于当前页面 URL 精确生成并且生成后要展示给用户确认。坑三API 密钥别硬编码。见过一些开源实现把密钥写在代码里这是大忌。一定要让用户自己填存在chrome.storage.local里并且明确告知用户密钥的用途和存储位置。坑四给用户看生成过程。一开始我直接展示最终脚本用户看不懂也不知道对不对。后来改成展示 agent 的思考摘要——它识别到了哪些元素、打算怎么操作、为什么这么选。用户能看懂这个信任感和成功率都上来了。坑五脚本要能编辑。再好的 agent 也有生成不对的时候。给用户一个脚本编辑框让他们能手动改几行比重新描述需求快得多。这个功能看起来简单但极大提升了产品的实用性。5. 这类工具的边界与延伸思考5.1 它做不了什么把话说清楚这类工具不是万能的。有几类需求它天然处理不好。需要登录态和复杂鉴权的操作。如果脚本要调用需要 OAuth 或复杂 token 的接口agent 很难自动处理得用户手动配置。涉及大量数据处理的场景。比如要抓取上万个页面、做复杂的数据清洗浏览器环境不适合干这个应该用服务端脚本。对稳定性要求极高的生产环境。agent 生成的脚本质量参差不齐用在关键业务流程上有风险。这类场景还是得专业开发者写。需要长期维护的脚本。页面结构一变脚本就可能失效。agent 生成的脚本如果没有良好的错误处理和结构注释维护起来很痛苦。5.2 可以延伸的方向从 Usermods 这个思路出发有几个方向值得探索。脚本市场与共享。用户生成的脚本如果能一键分享、被别人复用价值会放大很多。但这里要处理安全和隐私问题——别人分享的脚本可能偷数据。脚本的自动修复。页面结构变化导致脚本失效时让 agent 自动重新探测、修复脚本。这个闭环如果做通实用性会大幅提升。多步骤工作流。单个脚本能做的事有限如果能编排多个脚本形成工作流比如抓数据→清洗→导出能覆盖更复杂的场景。本地模型支持。现在都依赖云端 API如果支持本地小模型隐私和成本问题都能缓解。不过本地模型的能力目前还撑不起这个场景得再等等。5.3 我个人的使用体会折腾这类工具一段时间后我最大的感受是agent 的价值不在于替代开发者而在于把写脚本这件事的门槛从会编程降到会描述。这个降维打击覆盖的人群是巨大的。我身边很多运营、产品、市场同事他们有大量重复性的网页操作需求但从来没想过可以写脚本解决——因为不会。有了这类工具他们至少能迈出第一步。但另一面agent 生成的脚本质量确实不稳定。我的经验是把它当作高级代码补全而不是全自动开发。用户描述需求 → agent 生成初稿 → 用户测试 → 反馈问题 → agent 修改这个循环走两三遍基本能得到可用的脚本。指望一次生成就完美目前还不现实。最后分享一个实用技巧描述需求时尽量具体到在哪个位置、对什么元素、做什么操作、期望什么结果。比如不要说帮我整理这个页面而要说把页面右侧列表里所有价格大于 100 的商品名称提取出来去重后按字母排序输出到控制台。描述越具体agent 生成越准。这个技巧我在用任何 coding agent 时都适用值得记住。