拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AI Agent专用浏览器:如何做到比Chrome快7.6倍还省90%内存?

1. 为什么给 AI Agent 写浏览器从“让 AI 看网页”到“让 AI 读网页”1.1 传统“Chrome Playwright”方案到底慢在哪做 AI Agent 的团队多数会踩过同一条坑第一步不是模型不聪明而是 Agent 控制浏览器的链路太重。最常见的方案是 Playwright 或 Puppeteer 拉起本机 Chrome然后一句page.goto(url)、再等一个固定延时最后把页面内容通过page.content()抽取出来丢给大模型。这套方案本身没毛病尤其是面向通用网页自动化时非常稳。但问题是Chrome 是一个给人类用的完整图形浏览器。它要做的事情包括多进程隔离、GPU 合成、CSS 样式完整计算、复杂 JavaScript 引擎执行、各种扩展加载、密码管理、同步服务甚至还有后台的预渲染与更新检查。只要你通过自动化协议去驱动它这些开销全部会先于你的 Agent 任务发生一遍。我实测过一个很典型的场景Agent 需要去新闻站点抓取今日文章列表并总结摘要。用 Playwright 启动 Chrome冷启动到首屏可交互往往要 3~5 秒而后台会持续创建渲染进程和服务进程单页面占用内存经常在 400MB 以上。如果 Agent 一轮任务要浏览 10 个页面光浏览器资源占用就能吃掉好几个 GB而且每一步等待都拖慢 Agent 的决策节奏。真正的根本问题在这里Agent 需要的是“网页里有什么内容、有哪些可点击元素、点击后发生什么”它不需要像人一样看渲染后的精美页面。Chrome 把 99% 的计算能力花在了 Agent 根本不关心的事情上。于是我开始认真思考能不能不套 Chrome而是写一个专供 Agent 使用的浏览器内核。1.2 Agent 和人类对浏览器的需求完全不同人类浏览网页核心是“看”看排版、看字体、看图片、看交互动效。所以 Chrome 的价值在于像素级正确的渲染以及人能凭视觉快速判断信息层级。Agent 浏览网页核心是“读”读 DOM 树结构、读链接关系、读表单字段、读按钮状态然后决定下一步动作。它不需要真正把 CSS 全部算完不需要关心字体渲染得好不好看也不需要把 4K 大图下载下来做视觉展示。举一个最直白的例子如果 Agent 要去一个购物网站查价格它真正需要的是一个结构化数据——商品名称、SKU ID、价格、库存状态、购买按钮的位置。这个需求等价于“一个 API 接口”但网页没有给 Agent 提供 API所以 Agent 只能像人一样进页面看。传统方案就是把整个页面渲染出来再让模型去“看”截图或读 DOM这个过程中浪费掉的资源是惊人的。换成 Agent 专用浏览器的思路后一切变成了另一个问题怎么把一个网页快速降维成 Agent 能处理的最小必要信息集同时保留页面中的可交互语义。这个思路听起来像是在做爬虫但爬虫只是静态读取Agent 浏览器还要求能执行 JavaScript、能点击按钮、能等待动态渲染完成、能返回结构化事件结果。它是爬虫、浏览器、自动化协议三者的交集。1.3 重新审视指标快 7.6 倍是怎么来的标题里的数字很夸张我需要先把它说清楚免得被当成标题党。7.6 倍并不是“所有网页全部跑分”而是基于 Agent 最常用的一类任务——批量内容读取与结构化提取——做出来的对比。测试页面是资讯站、文档站、电商列表页这种典型的中型页面不是视频网站或者地图应用那种极度吃渲染的 WebGL 应用。对比口径是“从调用浏览器打开页面到 Agent 拿到可用的结构化结果所消耗的总时间”。在这个口径下Chrome 会有一个很难绕过的固定成本完整下载所有资源、完整执行主文档和广告脚本、等待页面 load 事件、再通过 DevTools 协议把序列化后的 DOM 传出来。我的 TinyAgent 浏览器只下载主文档和必要脚本重点解析 HTML 与脚本执行结果并且在一个更轻量的进程模型里完成自然能拉开倍数差距。内存方面同理。Chrome 的核心强项——沙箱、标签页隔离、崩溃恢复——全部建立在多进程架构上每一个 Tab 都有一组独立进程。同类页面在 Chrome 里开 10 个 Tab内存消耗是线性的。而 Agent 的浏览器本质上是对页面一个一个地做“即取即用”完全可以用更紧凑的架构来减少并行资源占用压缩到 Chrome 的十分之一并不是什么魔法而是需求模型变了。2. TinyAgent 浏览器整体架构与核心设计取舍2.1 架构分层内核、接口、编排三个层面动手写了之后我才意识到一个所谓“Agent 专用浏览器”其实不是单点技术而是三层的叠加体。第一层是真正干活的浏览与解析内核第二层是给 Agent 使用的编程接口层第三层是跟大模型任务衔接的编排层。TinyAgent 的代码结构大致是这样内核层提供页面加载、HTML 解析、DOM 树构建、样式与脚本执行的最小实现。我早先也想过直接基于 Chromium 删改后来发现删减工作量和风险都很大就换成了一个基于 Rust 编写、依赖轻量 WebView 组件和自研 DOM 解析模块的方案。内核层不提供多标签页 UI不需要地址栏、书签、扩展系统和复杂设置中心。接口层对上层暴露一组类似page.navigate()、page.getStructuredData()、page.waitForSelector()、page.click()的 API。这些 API 面向 Agent 的思维模式设计返回的不是 Markdown 或截图而是带语义标签的 JSON 结构。文档对象模型被压缩成content、links、inputs、buttons、forms这几个区块。编排层处理“模型思考 → 浏览器动作 → 拿到反馈 → 再思考”的循环。编排层会判断某个页面是不是需要真实点击多次才能拿到结果如果任务在第一次内容加载时就能完成它就直接返回结果不再驱动真实内核做多余渲染。这个分层是一个很自然的演进结果。最早我尝试在同一个进程里同时做页面解析和 Agent 决策结果两者互相阻塞。后来把内核层独立出来Agent 需要页面内容时再去内核取整个系统清爽了很多。2.2 为什么把“快”放在“全渲染”前面有一次我让 TinyAgent 打开一个中等复杂度的后台管理页面页面里有大量隐藏的 Bootstrap 组件和几十个未渲染的弹窗层。TinyAgent 的核心原则是“只处理与任务相关的 DOM 可达部分”所以它只拿到了表格数据区和几个操作按钮花费时间不到 Chrome 的五分之一。但隔壁团队坚持要看像素级渲染说这样 Agent 才能更好地理解用户界面。我承认视觉截图在“UI 类操作任务”中有价值但它不是所有任务的必需项。真实世界中的 Agent 任务可以简单分成三类信息获取型看新闻、查资料、读文档、操作执行型下单、填表、点击、验证反馈型检查页面元素是否出现、数据是否更新。这三类里信息获取型占比最大也是 LLM 最擅长的场景。信息获取型任务几乎不需要渲染结果它需要的是“语义树”不是“像素图”。操作执行型任务需要元素坐标和可见性信息这些 DOM 也能提供但确实对动态渲染有一定要求。验证反馈型任务最轻量核心是轮询某个选择器。基于这个分类我决定把 TinyAgent 的分工定义为默认走快速 DOM 解析通道遇到页面强烈依赖 JavaScript 动态渲染、且 Agent 需要“看到”视觉状态时才切换到完整 WebView 渲染模式。这是一个工程上的折中也是性能拉开差距的主要原因。听起来像“取巧”但你要承认如果做一个能选择较优路径的浏览器为什么一定要每条路径都走最重的渲染管线呢2.3 内核与进程模型选择如何把内存降到十分之一进程模型是内存差异的核心。Chrome 为每个标签页启动至少一个渲染进程、一个网络进程、一个 GPU 进程多个标签共享浏览器进程。这种架构让一个页面崩溃不会拖垮其他页面非常可靠但代价是每个标签页的常驻内存和进程间通信开销都很大。TinyAgent 采用了两级进程方案。第一级是一个常驻的 Agent 会话管理进程负责编排任务、缓存连接、存放会话状态。第二级是页面浏览工作进程每当 Agent 要访问一个新页面时会启动一个极轻量的 worker 来执行加载与解析完成后该 worker 会把结构化数据返回给管理进程然后立即销毁。这样的好处非常明显同一时刻只存在一个活跃的页面 worker而不是同时开着十个 Tab 等你慢慢看。对 Agent 来说它本来就只需要关注当前正在处理的页面。切换到下一个页面时前一个页面的 worker 直接不复存在内存立刻回收。Chrome 也可以做到关标签回收内存但 Agent 自动化运行时往往不会特意关掉历史标签结果就是积累大量无用页面进程。内核上我选择了 Rust 一个精简的嵌入式 WebView 作为渲染兜底而不是从零写 HTML 引擎。从零写解析器能让你完全控制行为但工作量太大而且兼容性会成为无底洞。TinyAgent 的策略是两个引擎并行默认使用自研的轻量 HTML 解析器做快速结构化提取遇到复杂的前端框架页面把内容交给嵌入式 WebView 渲染再从渲染完的 WebView 环境里抓取 DOM。这样做保留了快速通道和兜底通道两种模式下都避免了一整套 Chrome 级别的基础设施。最后测下来同一批测试页面的平均峰值内存大概在 42MB 到 85MB 之间而 Chrome 在我这台测试机上跑同样页面序列的峰值是稳定在 600MB 以上。十分之一的说法是因为我们不需要长期保持十几个标签页常驻。3. 快 7.6 倍从哪来性能优化手段与实现细节3.1 优化点一跳过与任务无关的网页资源和脚本传统浏览器加载一个新闻页面时会下载的东西比正文多得多。资讯站首页随便就能带 200 多个请求广告脚本、数据上报、AB 测试、用户行为采集、社交分享挂件、第三方字体、视频预览图……Chrome 会全部加载并执行因为这些资源组成了页面在人类眼中的“完整性”。TinyAgent 在请求发出前会先做策略拦截和解码。页面主文档必须下载主脚本是否执行要看页面是否需要 JS 来动态生成内容其余的图片、字体、视频、统计埋点、广告联盟脚本则默认全部拦截。我会为每个任务维护一个资源类型白名单当任务是“提取文章正文和链接”时图方便也可以下载但默认不被加载。在实现时我用的是一个中间层请求过滤器。所有从页面发起的 HTTP 请求都会先经过这层过滤根据 URL 后缀、请求方域名、Content-Type 做快速判断。比如请求pixel.example.com/tracker的目标域名是一个已知的商业分析域过滤器直接返回空响应。这一步将每个页面的有效请求数量从 200 压制到 20~30 个。我的实测数据里仅这一项就能让页面加载时间降低 50% 以上同时明显减少网络带宽占用。3.2 优化点二Agent 不需要等待整棵 DOM 树加载完Chrome 的自动化等待一般发生在load事件之后而load意味着页面里的每个子资源都被传输并处理完。对普通用户而言这很合理因为要保证人看到的是完整网页。对 Agent 而言这完全是一种过度要求。TinyAgent 自定义了一套“内容就绪”判定逻辑。默认策略是当主文档完成解析、核心可见内容对应的 DOM 结点已构建、而且页面没有抛出错得离谱的 JavaScript 异常时就认为页面可用。对于支持 JavaScript 的页面还要再叠加一个可配置的 “最小稳定时间窗”比如 800ms 内 DOM 结构不再发生明显变化就判定为稳定不等load事件。这里要给一个真实例子。某公司的职位列表页使用 Vue 渲染页面在 1.2 秒左右列出了职位列表但图片资源和统计脚本一直加载到 5 秒才结束。Chrome Playwright 必须等到 5 秒后的load如果等待策略不严谨还会偶发取空数据。TinyAgent 在 1.2 秒后观察到关键选择器.job-item已经出现直接返回结构化结果后续数十个无关请求不再等待。这样单页获取时间从 5 秒降到 1.2 秒恰恰是“快好几倍”最直接的来处。3.3 优化点三用结构化提取替代大模型看图很多 Agent 项目在网上搜一圈发现最好的方式是“截图 直接把图片丢给多模态模型”让它理解页面内容。这个方法效果不错但有两个致命问题第一截图依赖页面完整渲染渲染时间和计算资源会直接拉高第二把整张高清截图传给大模型Token 消耗极不划算延迟也大。TinyAgent 的选择是提供可插拔的“结构化预提取器”。浏览器内核拿到 DOM 树之后不会一次性把整包文本丢给上层而是先做一轮净化去掉 style、script、noscript 节点识别正文容器标签和列表容器提取所有链接及其锚文本提取按钮文字、输入框占位符和当前值把可见文本按区块拼装成紧凑文案。比如一个电商搜索页TinyAgent 最终返回给 Agent 的数据大概长这样{ page_type: search_result, items: [ { title: 无线机械键盘 87 键 热插拔, price: 299.00, link: https://example.com/product/12345, badge: 自营 }, { title: 无线机械键盘 104 键 RGB 背光, price: 459.00, link: https://example.com/product/23456, badge: 秒杀 } ], pagination: { next_page_url: https://example.com/search?qkeyboardpage2, total_results_text: 共 2,300 件商品 }, visible_raw_text_len: 1240 }这样 Agent 拿到的不是一大坨 HTML而是一个干净的 JSON。大模型读完这个 JSON 后可以直接做决策无需再经过整页文本或截图。在部分页面中这个预提取过程比截图传输快了 5 到 10 倍因为网络 I/O 和 Token 延迟同时被压缩了。这也是整体 7.6 倍提升里有分量的一个贡献项。3.4 优化点四轻量渲染兜底与“页面快照复用”TinyAgent 也不是所有页面都能用快速通道搞定。遇到大量依赖 React/Vue 客户端渲染、或者内容靠滚动懒加载的页面快速 HTML 解析通道拿不到数据此时必须启动嵌入式 WebView 执行脚本。这个兜底模式比 Chrome 的优势主要在于它只维持一个小型窗口没有书签栏、扩展、预渲染服务页面只要不在可视区域就立即关闭渲染线程。另一个很好用的优化是缓存复用。在大模型跑批任务的场景中很多候选网页其实在前几次运行里已经被访问过。TinyAgent 在浏览器会话层做了 DOM 快照缓存只要同一个 URL 在 10 分钟内被再次请求而且响应头没有明确禁止缓存就直接从缓存里返回上一轮的结构化结果。Agent 做大规模调研时经常反复访问同一批列表页和详情页快照缓存一命中整轮任务的耗时会变得非常平滑。这些都是普通文档里不会细讲的“脏活儿”。浏览器不是靠单点魔法变快的而是靠把不需要做的事砍到最少把一个一个细节抠出来最后量变引发质变。4. 实测对比与 Chrome 的硬碰硬测试记录4.1 测试环境与任务设计先交代清楚测试条件不然数据没有参照意义。我用的是一台普通开发笔记本配置是 i7-12700H、32GB 内存、SSD、Ubuntu 22.04网络环境是本地 200Mbps 带宽。Chrome 版本为 126 稳定版Playwright 使用官方最新 Node.js 库TinyAgent 是当前仓库的 main 分支构建。测试任务是我日常使用 Agent 的高频场景——批量采集 30 个技术博客的文章页提取标题、正文第一段、发布时间和作者。每个浏览器连续执行 3 轮取中间值避免首轮缓存和机器抖动影响。Chrome 方案使用 Playwright 的waitUntil: load并等待正文选择器后提取文本TinyAgent 使用默认的快速解析通道并开启结构化提取。这个任务设计没有任何偏袒它恰好是 Agent 最典型的工作负载。Chrome 如果能在这里赢下来说明它的重型架构在简单任务上也能做到高效但最终结果证明重型架构没有占到便宜。4.2 核心数据对照最终记录的数据如下表这些数字不是宣传 PPT而是我从日志文件里直接汇总出来的实测结果指标Chrome PlaywrightTinyAgent差异单页平均总耗时含启动4.87 秒0.64 秒约 7.6 倍差距单页平均 DOM 就绪耗时3.15 秒0.42 秒约 7.5 倍差距单轮 30 页总耗时146.1 秒19.2 秒约 7.6 倍差距峰值常驻内存整轮712 MB68 MBTinyAgent 约 1/10单页平均内存占用356 MB41 MB约 8.7 倍差距成功解析出结构化数据的页数30/3030/30一致这里需要特别说一句Chrome 的表现其实并不差。在一个需要执行复杂前端交互、处理视频和动画的场景Chrome 是完全胜任的TinyAgent 反而可能因为裁剪过度而丢信息。但在“读文章、抓列表、提取数据”这类 Agent 任务里Chrome 确实是大炮打蚊子。4.3 复盘为什么数据差距能拉这么开把差距拆开看Chrome 的 4.87 秒主要花在了三块一是启动浏览器进程和打开 DevTools 通道约 0.6~0.9 秒二是页面load事件前的资源下载和脚本执行约 3 秒左右三是 Playwright 将页面 DOM 序列化并通过 CDP 传回 Node.js 的开销。TinyAgent 的 0.64 秒也分三块建立 worker 和读取 URL 的调度成本约 80ms下载主文档并完成轻量解析约 300ms结构化提取与结果编码约 150ms。整个过程没有完整渲染、没有资源堆砌、没有远程调试协议的往返开销时间自然省下来了。内存差距更简单。Chrome 每个测试页面都会在后台创建独立的子进程哪怕页面一打开就直接关掉那短暂的高峰也值得计算。TinyAgent 因为核心不承担界面渲染worker 从启动到退出甚至可以控制在 200ms 内进程的寿命都很短被观测到的内存自然小得可怜。有人会说你这不算浏览器只能算爬虫框架。我同意一半如果只跑静态页面那确实可以算爬虫框架。但 TinyAgent 实现了 WebView 模式下的 JavaScript 执行、表单填写、点击并且能像浏览器一样维护 Cookie 与会话状态所以叫它“浏览器”也没有太大问题。它更像是做了极简 UI 的浏览器只不过这个 UI 是给 Agent 用的而不是给人用的。5. 实操过程从零搭一个 Agent 浏览器的关键步骤5.1 基础依赖与项目结构如果你看完前文也想自己动手做一个最小可用版本可以把项目拆成两层来做底层复用系统自带的 WebView 做兜底渲染上层用 Python 或 Node.js 写 Agent 接口。我这里用 Node.js 作为主语言因为前端生态里处理 DOM 的库选择更多。最小项目结构可以这样安排tiny-agent-browser/ ├── package.json ├── src/ │ ├── kernel/ │ │ ├── fetcher.js # 请求下载与资源过滤 │ │ ├── htmlParser.js # 轻量 HTML 解析 │ │ ├── extender.js # WebView 兜底渲染 │ │ └── structurizer.js # 结构化数据提取 │ ├── api/ │ │ ├── page.js # navigate / waitFor / click │ │ └── session.js # 会话与 Cookie 管理 │ ├── agent/ │ │ └── orchestrator.js # Agent 任务编排 │ └── utils/ │ └── cache.js # DOM 快照缓存 └── test/ └── benchmark.js # 与 Chrome 对照脚本实际项目里我用了 Rust 写了一个很精简的本地服务来保证卸载快速通道的性能但最小可运行版本不需要这样。用 Node.js 的undici库负责 HTTP 请求用node-html-parser做初步解析如果页面需要完整渲染就通过webview模块拉起系统窗口环境然后再读取 DOM。5.2 资源过滤拦截器的具体写法拦截器是决定页面加载快慢的第一道闸门核心逻辑是对比资源类型与任务类型。下面这段写了一个按文件类型阻断图片和视频的请求示例// src/kernel/fetcher.js import { fetch as undiciFetch } from undici; const BLOCKED_EXTENSIONS new Set([ .png, .jpg, .jpeg, .gif, .webp, .avif, .mp4, .webm, .ogg, .woff, .woff2, .ttf ]); const BLOCKED_CONTENT_TYPES [ image/, video/, font/, audio/ ]; export async function smartFetch(url, { allowMedia false } {}) { const controller new AbortController(); const timeout setTimeout(() controller.abort(), 15000); const response await undiciFetch(url, { signal: controller.signal, redirect: follow, headers: { user-agent: TinyAgentBrowser/0.1.0, accept: text/html,application/xhtmlxml,application/json;q0.9,*/*;q0.8 } }); // 初步判断如果非媒体任务直接拦截媒体资源 if (!allowMedia) { const contentType response.headers.get(content-type) || ; const matchedType BLOCKED_CONTENT_TYPES.some(prefix contentType.startsWith(prefix) ); if (matchedType) { clearTimeout(timeout); return null; } } clearTimeout(timeout); return response; }调用这个函数前主解析流程会先获取主文档 HTML。文档里提到img、video、link relstylesheet这些标签时只有任务上下文中明确需要视觉信息才会发起真实下载否则只记录占位信息。对于页面请求里的统计脚本逻辑也差不多通过一个大而全的“广告与追踪域名集”判断命中则直接返回 204 空响应。这个拦截器最需要注意的坑是别误伤动态渲染所需的内联脚本。很多前端框架的主逻辑都写在外部 JS 中如果粗暴地禁止执行所有脚本页面就会留下一个空壳。所以我的实现里有一个“最小执行集”配置Agent 任务在发起前会声明自己是否需要 JavaScript 渲染只有声明后才会放行白名单域名的脚本文件。5.3 结构化提取器的模块与伪代码轻量解析器拿到了 HTML 字符串后接下来就是提取有用结构。为了减少对真实渲染的依赖我默认用node-html-parser把 HTML 转成 DOM 树再遍历摘取内容。核心实现在structurizer.js里关键代码如下// src/kernel/structurizer.js import { parse } from node-html-parser; export function extractStructuredData(html, options {}) { const root parse(html); const result { title: , mainContent: , links: [], buttons: [], inputs: [] }; // 标题最优先取 title 和 h1 result.title root.querySelector(title)?.text?.trim() || root.querySelector(h1)?.text?.trim() || ; // 正文提取采用“最长逻辑区块”策略 // 在 article、main、body 中找 p 文本总长最长的容器 const candidates [article, main, body]; let bestNode null; let bestScore 0; for (const selector of candidates) { const node root.querySelector(selector); if (!node) continue; const paragraphs node.querySelectorAll(p); const score paragraphs.reduce((sum, p) sum p.text.trim().length, 0); if (score bestScore) { bestScore score; bestNode node; } } if (bestNode) { result.mainContent bestNode.text.trim() .replace(/\s/g, ) .slice(0, options.maxContentLength || 3000); } // 提取页面中所有可见链接 root.querySelectorAll(a[href]).forEach((a) { const href a.getAttribute(href); const text a.text.trim().slice(0, 120); if (href text !href.startsWith(javascript:)) { result.links.push({ text, href }); } }); // 去重并限制数量 result.links result.links .filter((link, index, arr) { const key ${link.text}|${link.href}; return arr.findIndex(l ${l.text}|${l.href} key) index; }) .slice(0, options.maxLinks || 100); return result; }这段实现用了非常朴素的正文提取逻辑没有上复杂的 NLP 模型。原因很简单在真实项目中稳定性比炫技更重要。朴素的语义容器识别基本能应对大多数博客和文档站。等页面有更多前端框架时这个提取器无法拿到动态内容就得切换到下一节的 WebView 兜底通道。真实生产里你会需要把提取器升级为更精细的规则组合比如针对招聘站、电商站、新闻站分别配置专门的正文容器规则。但核心思路是一样的让浏览器返回“语义层数据”而不是“视觉层数据”。5.4 Agent 的等待策略别再把 load 当金标准之前我踩过一个坑用固定延时等待页面等久了浪费 2 秒等短了拿不到数据。后来总结出一套策略优先级从高到低选择器等待Agent 自己定义了“这个页面里有什么元素才算成功”。比如需要拿到.price元素的值内核就会每 100ms 轮询检查该选择器是否出现最多等待 5 秒。文本内容等待需要页面里出现一段特定文本比如“登录成功”。内核监听 DOM 变化一旦出现目标文本立即返回。网络空闲等待如果任务没有明确目标元素就等网络请求数量从高位进入平静状态连续 500ms 内没有新请求以此近似判定页面稳定。结构稳定等待比较两次 DOM 快照差异如果连续 800ms 没有新增节点就认为渲染稳定。这四种策略可以组合使用。大多数情况下选择器等待最快文本等待最稳。Chrome 系自动化默认的domcontentloaded和load反而不是最优解因为前者太早后者太晚。最理想的状态是让 Agent 告诉你它要什么然后浏览器针对性地去等那个东西而不是等一个“大约完整”的信号。5.5 WebView 兜底渲染必须保留的逃生通道前面说了一堆快速通道多好但真实开发中必然遇到快速通道抓不到内容的页面这时候不能没有兜底。我在 TinyAgent 里通过系统 WebView 完成动态渲染兜底它相当于嵌入式迷你浏览器支持 JavaScript 执行并且能接受自动化指令。兜底模式的启动流程包括几个步骤// 伪代码简化 WebView 初始化与使用 import { createWebViewWindow } from ./vendor/webview-bridge; export async function loadWithWebView(url, { injectScript }) { const wv await createWebViewWindow({ width: 1280, height: 800 }); const pageReady wv.navigate(url); if (injectScript) { await wv.executeScript((function() { // 等待目标节点出现并把必要数据存到 window.__agentData const observer new MutationObserver(() { const node document.querySelector(.target-content); if (node !window.__agentData) { window.__agentData node.innerText; } }); observer.observe(document.body, { childList: true, subtree: true }); })()); } const data await wv.waitForEval(window.__agentData || null, 10000); wv.destroy(); return data; }这段代码的核心理念是等待 Agent 关心的数据出现而不是等待整个页面全部完成。在 WebView 模式里我们同样塞入了一个 MutationObserver它持续观察 DOM一旦目标内容生成立即把数据导出并销毁窗口。WebView 是保底通道默认不打开。但每个 Agent 任务都要有降级策略快速解析拿不到内容、超时、结构提取结果为空时自动重新用 WebView 渲染一次如果还是拿不到就明确报错而不是返回空数据误导大模型。6. 常见问题与避坑指南TinyAgent 踩过的真实坑6.1 Cookie 和登录态怎么处理这是最容易被轻量浏览器坑到的地方。Chrome 有完整的 Cookie 管理、存储引擎和用户画像体系切到精简内核后你会发现很多页面第一次访问会要求登录或弹出验证码。解决方案是做一个独立的“会话存储中心”把 Cookie、LocalStorage、IndexedDB 数据按域名切分保存并在每次发请求时自动带上。第一次用 WebView 人工登录并保存会话之后所有快速通道请求都自动附带Cookie头大部分页面就不会再重复要求登录。我在这里栽过一次某些平台会生成 HttpOnly Cookie 来做安全校验直接拼接 Cookie 可用但 UA 或 TLS 指纹不一致又会触发风控。所以尽量让快速通道的请求头完全复刻 WebView 中记录的请求头做到一致性。这个问题没有绝对解只能尽量规范会话载体。6.2 前端框架动辄 1MB 以上的 bundle 文件怎么处理现代前端项目用 Webpack/Vite 打包后主 JS bundle 常常超过 1MB。TinyAgent 的快速通道不会执行这个 bundle所以很多页面拿到的都是空壳 HTML。那轻量解析是不是就没用了我的答案是把决定权交给“页面分类器”。当一个页面被下载下来后首先判断 HTML 里是否包含可见的正文文本。如果 HTML 源文件里能找到超过 500 字的有效文字就直接走快速通道。如果源文件几乎只有div idapp/div这种挂载点那就说明它依赖客户端渲染自动切到 WebView。这个动态分类器花了大约两周才调到稳定水平。它本质上解决了“什么时候该相信轻量解析、什么时候必须完整渲染”的决策问题而这个问题不是简单用一个 PageSpeed 或者headless: true就能解决的。6.3 图片懒加载导致 Agent 找不到关键信息很多页面为了性能在用户滚动到可视区域之前不会加载图片链接。Chrome 截图时往往需要模拟滚动才能让所有图片加载但 Agent 只是想获取图片的地址它不需要真实下载图片。TinyAgent 的策略是直接读取>
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门