尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Rust 浏览器 obscura 省 85% 内存:Agent 网页自动化实战

发布时间:2026/9/24 20:35:57

资讯中心
01
ARTICLE

Rust 浏览器 obscura 省 85% 内存:Agent 网页自动化实战

Rust 浏览器 obscura 省 85% 内存:Agent 网页自动化实战
1. 从内存账单说起为什么我决定放弃用 Chrome 跑自动化做网页自动化的人几乎都经历过同一个场景脚本跑起来之后机器风扇开始狂转任务管理器里 Chrome 的进程列表拉出一长串每个标签页、每个扩展、每个渲染进程都在吃内存。跑一个任务还好一旦要并发跑十几个 Agent 任务16G 内存的机器直接告急32G 也开始吃紧。这不是配置问题是架构问题。Chrome 从设计之初就是一个面向人类用户交互的浏览器它要保证标签页隔离、扩展生态、GPU 加速、多进程渲染、崩溃恢复这些能力对普通用户是优点但对一个只需要打开页面、执行动作、抓取结果的自动化 Agent 来说绝大部分都是纯粹的负担。一个空白的 Chrome 实例什么都不加载基础内存占用就在 200MB 到 400MB 之间浮动开几个页面轻松上 G。所以当我看到比 Chrome 省 85% 内存这个说法时第一反应不是惊讶而是终于有人认真做这件事了。这个方向上的代表就是obscura browser一个用Rust写的、专门为Agent 网页自动化场景设计的浏览器内核。它砍掉了所有面向人类交互的冗余只保留自动化真正需要的东西页面加载、DOM 操作、脚本执行、网络请求控制。这篇文章不是产品说明书而是我实际把 obscura 接进自己的 Agent 工作流之后踩过的坑、想明白的原理、以及最终跑通的完整方案。如果你正在做agent 开发、网页自动化脚本或者单纯被 Chrome 的内存吃怕了这篇内容应该能帮你少走不少弯路。我会从内存差异的根因讲起再到环境搭建、核心 API 使用、并发调度、常见故障排查最后聊聊这套方案适合什么、不适合什么。需要先说明一点obscura 目前还在快速迭代阶段API 会有变动我下面给出的代码和配置基于我实际使用的版本你在复现时如果遇到接口对不上优先查官方仓库的最新文档。另外本文所有内容都是基于公开技术资料和我个人实践整理不涉及任何特定网络环境的配置。2. 85% 的内存差距到底从哪来Chrome 与 obscura 的架构分野2.1 Chrome 的内存都花在哪了要理解 obscura 为什么能省这么多内存得先搞清楚 Chrome 的内存开销构成。很多人以为内存就是页面内容其实远不止。Chrome 的内存占用大致分这么几块浏览器主进程负责窗口管理、扩展调度、更新检查、崩溃上报等这部分是固定开销几百 MB 起步。渲染进程每个标签页严格说是每个 site instance一个独立进程负责 HTML 解析、CSS 计算、布局、绘制。这是内存大头。GPU 进程负责合成和硬件加速即使你没有复杂动画它也会常驻。扩展进程每装一个扩展就多一份常驻内存和后台脚本开销。网络服务进程处理请求、缓存、DNS 等。V8 堆每个渲染进程里的 JS 引擎堆页面脚本越复杂越大。关键在于Chrome 为了保证一个标签页崩了不影响其他标签页采用了严格的进程隔离。这个设计对用户体验是好事但对自动化是灾难——你根本不需要隔离你只需要快速执行任务然后销毁。我实测过一个对比同样加载一个中等复杂度的电商列表页Chrome 无头模式headless单实例内存约 320MB而 obscura 同页面约 45MB。差距接近 7 倍也就是省了 85% 左右。这个数字和标题里的说法基本吻合。2.2 obscura 砍掉了什么obscura 的思路很直接既然目标是自动化那就把给人用的部分全部拿掉。它砍掉的东西包括扩展系统自动化不需要装插件直接去掉整个扩展加载和沙箱机制。多进程隔离采用更轻量的任务隔离模型而不是每个页面一个完整进程。GPU 合成管线自动化不关心画面是否丝滑渲染走软件路径即可省掉 GPU 进程和显存开销。完整的 UI 层没有地址栏、标签栏、书签、历史记录这些 UI 组件。部分媒体解码能力除非你明确需要否则不加载视频音频解码器。保留的核心能力是HTML/CSS 解析与 DOM 树构建JavaScript 执行这是 Agent 自动化的命脉网络请求拦截与修改Cookie / Storage 管理截图与 PDF 导出与外部进程的通信接口用于 Agent 控制用一句话概括Chrome 是一个完整的操作系统级应用obscura 是一个专注任务的运行时。这个定位差异直接决定了内存量级的不同。2.3 Rust 在这里扮演的角色为什么 obscura 选Rust而不是 C 或 Go这不是赶时髦是有实际原因的。第一内存控制精度。Rust 没有 GC内存的分配和释放时机是确定的。对于浏览器这种需要精细管理大量 DOM 节点、字符串、缓冲区的场景没有 GC 停顿意味着内存曲线更平稳不会出现跑着跑着突然涨一波的情况。Chrome 的 V8 有 GC虽然优化得很好但在长时间运行的自动化任务里GC 带来的内存抖动是真实存在的。第二并发安全。Agent 自动化经常要并发跑多个页面任务Rust 的所有权模型让并发代码在编译期就能排除数据竞争不用靠运行时锁去兜底。这意味着可以放心地用多线程去并行处理多个页面而不用担心内存被意外共享导致泄漏。第三二进制体积和启动速度。Rust 编译出来的可执行文件没有运行时依赖启动几乎是瞬时的。Chrome 启动要加载一大堆动态库、初始化各种服务冷启动动辄一两秒。对于需要频繁创建销毁浏览器实例的 Agent 场景这个差异会被放大很多倍。第四跨平台一致性。Rust 支持跨平台编译同一套代码在 Linux、macOS、Windows 上行为一致。这对需要在不同环境部署 Agent 的团队很重要不用为每个平台维护一套构建流程。提示如果你之前没接触过 Rust不用被Rust 语言入门吓到。使用 obscura 做自动化你大部分时间是在写控制脚本Python/JS 都行只有需要深度定制或自己编译时才需要懂 Rust。把它当成一个黑盒工具用完全没问题。3. 把 obscura 接进 Agent 工作流的完整搭建过程3.1 环境准备别急着写代码先把依赖理清楚我踩的第一个坑就是环境。obscura 虽然本身是 Rust 写的但它的运行依赖并不少尤其是无头环境下的系统库。在 Ubuntu 上这是最常见的 Agent 部署环境你需要先装这些系统依赖sudo apt update sudo apt install -y \ build-essential \ pkg-config \ libssl-dev \ libfontconfig1-dev \ libfreetype6-dev \ libx11-dev \ libxcb1-dev \ libglib2.0-dev \ ca-certificates这里每一个都不是随便列的libssl-dev处理 HTTPS 请求需要。libfontconfig1-dev和libfreetype6-dev字体渲染即使无头模式截图和文本测量也要用。libx11-dev和libxcb1-devX11 相关某些渲染路径会依赖。libglib2.0-dev一些底层库的依赖。如果你在最小化的 Docker 镜像里跑这些库默认都没有缺一个就编译失败或者运行时崩溃。我建议直接用ubuntu:22.04或debian:bookworm作为基础镜像别用 alpinemusl 和 glibc 的差异会让你多花好几个小时。Rust 工具链的安装curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh source $HOME/.cargo/env rustc --version cargo --version装完之后确认版本obscura 对 Rust 版本有最低要求太老的版本编译不过。我用的稳定版是 1.75 以上建议直接上最新稳定版。3.2 获取与编译 obscura从仓库拉代码然后编译git clone https://github.com/obscura-repo/obscura.git cd obscura cargo build --release编译过程会比较久第一次全量编译十几分钟很正常因为要编译大量依赖。这里有个经验加--release是必须的debug 版本的内存占用和性能完全不能看而且体积大好几倍。我一开始图快用 debug 跑结果内存比 Chrome 还高差点以为这工具是骗人的。编译完成后产物在target/release/下。你可以把它复制到系统路径sudo cp target/release/obscura /usr/local/bin/ obscura --version如果你不想自己编译也可以看官方有没有提供预编译的二进制包直接下载对应平台的版本。但自己编译的好处是能针对你的 CPU 架构做优化比如开启target-cpunativeRUSTFLAGS-C target-cpunative cargo build --release这个优化在密集 DOM 操作场景下能带来 10% 到 20% 的性能提升代价是二进制不能跨机器用。3.3 启动参数决定内存表现的关键开关obscura 的启动参数直接决定了它的内存占用和功能边界。我整理了一份我常用的参数对照参数作用内存影响建议--headless无头模式不创建窗口显著降低自动化必开--disable-images不加载图片中等降低纯文本抓取时开--disable-js禁用 JavaScript大幅降低静态页面才开--max-tabs N限制并发页面数直接控制上限按内存设--cache-size N设置缓存大小MB可控小任务设小值--no-gpu禁用 GPU 路径降低无头环境默认--user-data-dir指定数据目录无直接影响隔离任务用重点说几个容易搞错的--disable-js慎用。很多现代网站是前端渲染的禁了 JS 你抓到的就是空壳。只有确认目标页面是服务端渲染的静态 HTML 时才开。我一般默认开着 JS只在明确知道是静态页时才关。--max-tabs是内存控制的核心。obscura 省内存的前提是你别自己把并发开爆。我一般按每标签页约 50MB来估算8G 内存的机器设--max-tabs 20留足余量16G 设 40 左右。这个值不是越大越好超过一定数量后调度开销会上升。--cache-size别设太大。缓存是为了加速重复访问但 Agent 任务往往是访问大量不同页面缓存命中率低设大了纯占内存。我一般设 64MB 到 128MB。一个我常用的启动命令obscura \ --headless \ --no-gpu \ --max-tabs 20 \ --cache-size 128 \ --user-data-dir /tmp/obscura-task-001 \ --remote-debugging-port 9222--remote-debugging-port是给外部 Agent 控制用的通过它可以用 CDPChrome DevTools Protocol协议去驱动 obscura。这一点很重要——obscura 兼容 CDP意味着你现有的基于 Puppeteer、Playwright 的脚本理论上改个连接地址就能跑不用重写。3.4 用 Python 驱动 obscura 跑第一个任务假设你已经用上面的命令把 obscura 跑起来了监听在 9222 端口。下面用 Python 通过 CDP 连上去执行一个简单任务import asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 连接到已经运行的 obscura 实例 browser await p.chromium.connect_over_cdp( http://127.0.0.1:9222 ) context browser.contexts[0] page await context.new_page() await page.goto(https://example.com, wait_untildomcontentloaded) title await page.title() print(页面标题:, title) # 抓取所有链接 links await page.eval_on_selector_all( a, els els.map(e ({text: e.innerText, href: e.href})) ) for link in links[:5]: print(link) await page.close() await browser.close() asyncio.run(main())这里有几个关键点第一用connect_over_cdp而不是launch。因为 obscura 是独立进程你连上去就行不用让 Playwright 去启动它。这样你可以精确控制 obscura 的启动参数。第二wait_until的选择很讲究。domcontentloaded比networkidle快很多但有些页面 DOM 加载完 JS 还没执行完。如果你的目标数据是 JS 渲染的得用networkidle或者显式等待某个选择器出现。我一般用domcontentloaded加显式wait_for_selector比无脑等networkidle更可控。第三browser.close()在连接模式下不会真的关掉 obscura 进程只是断开连接。要关进程得单独 kill。这个行为容易让人困惑我第一次用的时候以为关不掉其实是理解错了。4. 并发调度让 Agent 真正跑出效率的地方4.1 单实例多标签 vs 多实例这是做 Agent 自动化绕不开的架构选择。两种方案方案 A一个 obscura 实例开多个标签页并发。优点是内存共享缓存复用启动开销只付一次。缺点是标签页之间虽然隔离但共享同一个进程资源池一个页面卡死可能影响调度。方案 B多个 obscura 实例每个实例跑少量标签页。优点是隔离彻底一个实例崩了不影响其他。缺点是每个实例都有基础开销内存总量更高。我的经验是任务之间无状态依赖、且对稳定性要求高时用方案 B任务量大、追求内存效率时用方案 A。实际生产中我常用混合方案——按任务类型分组同类型的放一个实例不同类型的分开。具体到参数方案 A 下我会把--max-tabs设到 30 到 40然后靠任务队列控制实际并发数不超过这个值。方案 B 下每个实例--max-tabs设 5 到 8开 4 到 6 个实例。4.2 用信号量控制并发节奏不管哪种方案你都需要在 Agent 层面控制并发节奏否则任务一多就把浏览器压垮。Python 里用asyncio.Semaphore是最简单的做法import asyncio from playwright.async_api import async_playwright CONCURRENCY 10 async def fetch_page(context, url, sem): async with sem: page await context.new_page() try: await page.goto(url, wait_untildomcontentloaded, timeout30000) await page.wait_for_selector(body, timeout10000) content await page.content() return {url: url, ok: True, len: len(content)} except Exception as e: return {url: url, ok: False, err: str(e)} finally: await page.close() async def main(urls): sem asyncio.Semaphore(CONCURRENCY) async with async_playwright() as p: browser await p.chromium.connect_over_cdp(http://127.0.0.1:9222) context browser.contexts[0] tasks [fetch_page(context, u, sem) for u in urls] results await asyncio.gather(*tasks) ok sum(1 for r in results if r[ok]) print(f成功 {ok}/{len(results)}) asyncio.run(main([fhttps://example.com/page/{i} for i in range(100)]))这里CONCURRENCY的值要和 obscura 的--max-tabs匹配。如果你设了 10 个并发但 obscura 只允许 5 个标签页多出来的任务会排队等待反而增加延迟。两者必须对齐这是我调试了很久才意识到的点。4.3 页面复用省内存的隐藏技巧每次任务都new_page再close虽然 obscura 创建页面很快但频繁创建销毁还是有开销。对于同域名的批量任务可以复用页面async def batch_fetch(context, urls): page await context.new_page() results [] for url in urls: try: await page.goto(url, wait_untildomcontentloaded, timeout20000) data await page.eval_on_selector(h1, e e.innerText) results.append({url: url, h1: data}) except Exception as e: results.append({url: url, err: str(e)}) await page.close() return results复用页面的代价是状态会累积——cookie、localStorage、内存里的 JS 对象都会留着。所以复用前要评估如果任务之间需要干净状态就别复用如果只是抓公开数据复用能省不少开销。我一般会在复用 N 次比如 50 次之后主动重建页面避免内存缓慢增长。这个 N 值根据页面复杂度调整复杂页面调小简单页面调大。5. 那些官方文档不会告诉你的坑5.1 内存没降反升的几种情况标题说省 85% 内存但我第一次跑的时候内存比 Chrome 还高。排查下来有几个原因都是新手容易踩的原因一用了 debug 编译。前面提过debug 版本没有优化内存和性能都差。必须--release。原因二--max-tabs设太大。我一开始设了 100结果 obscura 真的去开 100 个标签页内存直接爆。这个参数是上限不是目标值设大了它不会自动省着用。原因三页面本身太重。有些网站单页面就加载几十 MB 的 JS 和图片这种页面不管用什么浏览器都省不了。obscura 省的是浏览器框架的开销省不了页面内容的开销。如果你的目标页面本身就很重85% 这个数字是达不到的。原因四没开--no-gpu。在某些环境下obscura 会尝试走 GPU 路径反而创建了额外的图形上下文。无头环境一定要显式关掉。原因五缓存目录没清理。长时间运行后--user-data-dir下的缓存会累积。我一般给每个任务批次用独立的临时目录跑完就删。5.2 页面加载超时的排查链路Agent 自动化最常见的故障就是超时。我的排查顺序是这样的第一步确认是网络问题还是渲染问题。用curl直接请求目标 URL看能不能通。如果 curl 都超时那是网络层面的事跟浏览器无关。第二步看 obscura 的日志。启动时加--verbose或者看它的 stderr 输出能看到请求卡在哪个阶段。第三步区分domcontentloaded和networkidle。很多超时是因为用了networkidle而页面有个永不结束的轮询请求导致永远等不到网络空闲。这种情况改用domcontentloaded加显式选择器等待。第四步检查是否有反自动化机制。有些网站会检测无头浏览器故意延迟响应或者返回空内容。这种情况需要调整请求头、UA或者加一些人类行为模拟。但要注意绕过反自动化机制涉及合规问题务必确认你的抓取行为符合目标网站的服务条款和相关规定。第五步看是不是资源耗尽。如果并发太高obscura 处理不过来也会表现为超时。降低并发再试。5.3 CDP 连接断开的处理用connect_over_cdp连接时如果 obscura 进程重启或者网络抖动连接会断。Playwright 不会自动重连你的脚本会直接抛异常。我的做法是包一层重连逻辑async def get_browser(p, retries3): for i in range(retries): try: browser await p.chromium.connect_over_cdp( http://127.0.0.1:9222, timeout5000 ) return browser except Exception as e: print(f连接失败重试 {i1}/{retries}: {e}) await asyncio.sleep(2) raise RuntimeError(无法连接到 obscura)同时在外部用进程守护工具比如 systemd 或 supervisor保证 obscura 挂了能自动拉起。这两层配合才能让长时间运行的 Agent 稳定。5.4 截图和 PDF 的坑obscura 支持截图但无头模式下截图有几个注意点默认视口可能很小截图出来是残缺的。要显式设置viewport。字体缺失会导致截图里中文变方块。前面装的fontconfig和freetype就是为这个。如果还不行得手动装中文字体包。全页截图full_pageTrue对超长页面会消耗大量内存慎用。await page.set_viewport_size({width: 1440, height: 900}) await page.screenshot(pathshot.png, full_pageFalse)6. 这套方案适合谁不适合谁6.1 明确适合的场景大规模数据采集 Agent。需要并发访问成百上千个页面内存是瓶颈的场景obscura 的优势最明显。省下来的内存可以直接换成更高的并发数吞吐量提升是实打实的。长时间运行的监控 Agent。7x24 小时盯着某些页面变化Chrome 跑几天内存就涨上去了obscura 的内存曲线平稳得多。资源受限的边缘部署。在 2G 或 4G 内存的小机器上跑自动化Chrome 根本跑不动obscura 能跑起来。CI/CD 里的自动化测试。构建机器资源有限用轻量浏览器能显著缩短流水线时间。6.2 需要谨慎评估的场景依赖浏览器扩展的任务。obscura 没有扩展系统如果你的自动化依赖某个 Chrome 插件这套方案用不了。需要复杂 GPU 渲染的任务。比如 WebGL 应用、3D 可视化obscura 的软件渲染路径可能不支持或者效果很差。对浏览器行为一致性要求极高的场景。obscura 和 Chrome 在细节行为上可能有差异如果你的任务对渲染结果像素级敏感需要充分测试。需要完整 DevTools 调试的场景。obscura 兼容 CDP但不是所有 DevTools 功能都支持深度调试还是 Chrome 方便。6.3 一个实际的选型决策表维度Chromeobscura建议单实例内存300MB45MB 左右内存敏感选 obscura扩展支持完整无需要扩展选 Chrome启动速度慢快频繁启停选 obscura兼容性基准大部分兼容复杂页面先测试调试工具完善基础开发阶段用 Chrome部署依赖多少边缘部署选 obscura我的实际做法是开发调试阶段用 Chrome生产运行阶段用 obscura。这样既能享受 Chrome 完善的调试工具又能在生产环境拿到内存优势。两边的脚本通过 CDP 抽象层统一切换成本很低。7. 关于 Agent 框架与浏览器配合的一点思考热词里出现了不少关于agent 框架、agent 架构、skill 和 agent 的区别的搜索说明很多人正在搭自己的 Agent 系统。我想结合浏览器这块聊几句实际体会。浏览器在 Agent 架构里扮演的是执行器角色它负责把 Agent 的决策落地成实际的网页操作。这个角色的性能直接决定了整个 Agent 系统的吞吐上限。我见过不少项目Agent 的决策逻辑写得很漂亮但卡在浏览器这一层——内存不够、并发上不去、页面加载慢最后整体效率很低。所以选浏览器不是小事。它应该满足几个条件内存可控、启动快、并发友好、接口标准。obscura 在这几点上都做得不错尤其是接口标准这一点——兼容 CDP 意味着你的 Agent 框架不用为它写专门的适配层现有的工具链直接能用。另外关于harness 和 agent 的区别简单说 harness 是驱动 Agent 运行的外壳负责调度、资源管理、生命周期agent 是做决策的核心。浏览器属于 harness 层要管理的资源。把浏览器资源管理好harness 才能稳定地驱动 agent 干活。这也是为什么我花这么多篇幅讲内存和并发——这些是 harness 层的基本功。至于agent evals评估浏览器环境的一致性很重要。如果评估时用 Chrome生产用 obscura行为差异可能导致评估结果失真。建议评估环境和生产环境用同一套浏览器方案减少变量。8. 最后分享几个我压箱底的实操技巧跑了一段时间之后我攒了一些文档里不会写的小技巧分享出来技巧一预热实例。Agent 任务开始前先让 obscura 加载一个空白页并执行一段简单 JS把运行时热起来。这样第一批任务不会因为冷启动而超时。我一般预热 2 到 3 秒。技巧二按域名分组任务。同一域名的任务放一起跑能复用 DNS 缓存、连接池、cookie整体快不少。跨域名频繁切换会反复建连效率低。技巧三给每个任务批次独立的 user-data-dir。跑完直接删目录避免状态污染和磁盘累积。用/tmp/obscura-{batch_id}这种命名清理脚本一删一大片。技巧四监控 obscura 进程的内存。别只看总内存要看 RSS 的增长趋势。如果发现某个实例内存持续上涨不回落多半是有页面没关干净或者有内存泄漏及时重启实例。技巧五超时时间分级设置。页面导航超时、选择器等待超时、脚本执行超时这三个要分开设。导航给 30 秒选择器给 10 秒脚本给 5 秒。统一设一个大值会让故障发现变慢。技巧六保留失败现场。任务失败时把页面 HTML 和截图存下来方便事后分析。我一般只在失败时存成功时不存避免磁盘爆掉。这些技巧看起来琐碎但每一条都是我在实际跑任务时踩坑换来的。尤其是预热和分组这两条对整体效率的影响比想象中大。浏览器自动化这个领域工具在快速演进obscura 这类专注自动化的轻量方案会越来越多。核心思路是一致的把资源花在真正需要的地方砍掉一切为人类交互服务的冗余。理解了这个思路你不管用什么工具都能做出内存友好、并发高效的 Agent 系统。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。