Browserless 与自托管浏览器:服务负责什么
托管浏览器通过 CDP 交付一个会话。它不交付稳定的浏览器身份。这里说明服务边界,以及文档中明确写出的限制。
BotBrowser Team
托管浏览器是运行浏览器并交给你一个连接的服务,整个决策取决于它掌控浏览器的哪些部分。BotBrowser 提供已声明的配置档案,使页面可读取的各项表面在多次启动之间保持一致,它不能替代托管机队、会话代理或并发配额。服务交出的不只是一个套接字:会话状态、操作系统仿真和加固路由各覆盖一层,而它们都不覆盖的那一层,才决定新会话是否看起来和上一次相同。
摘要
- 托管浏览器通过 CDP 或 WebSocket 给你一个正在运行的浏览器;自动化仍由你编写,面对反机器人控制的结果也仍由你承担。
- 托管抓取服务是另一种产品:它返回数据,不返回会话。
- Browserless 文档说明两种持久化机制:会话 API 保存
cookies、localStorage与 cache,已认证配置档案则增加 IndexedDB 与两个稳定请求头。 - 它的操作系统仿真只在特定路由上作为每会话查询参数生效,不是存储的档案;时区与语言仍由你的代码或代理决定,只有 Android persona 才会设定移动端视口。
browserless vs puppeteer是类别错误:一个是服务,另一个是你用来连接的库。- 计费方式为并发乘以时间,因此与自托管的交叉点取决于利用率,而不是请求量。
- 当身份必须跨会话、跨构建、跨机器存续时,自托管浏览器是正确选择;当任务不需要身份时,托管浏览器是正确选择。
目录
托管浏览器是什么,不是什么
服务在自己的基础设施上运行浏览器,并通过你的代码已经使用的协议把它开放出来。Browserless 将其称为 Browser-as-a-Service:把 Puppeteer 或 Playwright 连到一个 WebSocket endpoint,保留现有脚本,唯一改动是连接字符串。同一账号还开放无状态的 REST endpoints,例如 /content、/scrape 与 /screenshot,一次 HTTP 请求执行一个浏览器任务并返回结果。
这个划分首先要分清,因为两部分的经济性与失败方式都不同。 包装浏览器的 agent 框架会多出第三层,托管浏览器与 agent 层如何分工 就是把同一个问题上升一层。
BaaS endpoint -> wss://.../chromium?token=... session you drive; you own the code
REST endpoint -> https://.../content?url=... one request, one result, no session
self-hosted -> chrome --user-data-dir=... process on your machine
托管部分去掉的是运维表面:进程池、崩溃重启、版本安装与容量。它没有去掉的是决策层。视口、请求头、导航时机与重试策略仍由你选择,目标站点是否接受该会话也仍由你查明。托管浏览器只是浏览器运行的另一个位置,并不改变它能否通过这一结论。 当页面由多个运行时来源拼接时也会出现同样的划分,混用 API 与渲染数据的店铺 在一个具体案例里展示了这一点。
这一领域的第三种产品容易被与前两者混淆。网页解锁服务或抓取 API 同样在服务端运行浏览器,但它返回提取结果而不是会话。如果你需要的是一个可驱动的会话,这种产品形态就不对,无论它运行得多好;反过来也一样:在无状态 endpoint 之上自动化逐页流程,意味着每次调用都要重建会话状态。
这种架构还有一个实际好处:本地调试工具仍然可用。WebSocket endpoint 接受与本地浏览器相同的开发工具协议消息,因此本地记录 CDP 流量的脚本也能记录远端浏览器的流量,在一处捕获的 trace 可以在另一处重放。无法迁移的是浏览器本身:本地安装的扩展、本地字体集或本地 GPU 路径只存在于安装的位置,远端会话只有服务商放进去的那些。
三种架构并列
运维上真正重要的是:谁运行浏览器、谁承担面对反机器人控制的结果,以及调用代码拿回什么。
| 托管抓取服务 | 托管浏览器 | 自托管浏览器 | |
|---|---|---|---|
| 你收到什么 | 已完成的数据 | 通过 CDP/WebSocket 的会话 | 由你启动的进程 |
| 谁运行浏览器 | 服务商 | 服务商 | 你 |
| 谁处理拦截 | 服务商 | 你 | 你 |
| 跨会话的身份 | 不暴露 | 状态,而非指纹 | 配置档案声明的内容 |
| 浏览器版本控制 | 服务商决定 | 服务商的版本矩阵 | 由你固定 |
| 你排查的故障 | 数据质量 | 选择器、时序、请求头 | 进程、档案、构建 |
请仔细读第三行,因为这行改变了采购决策。托管浏览器移动的是机器,不是问题:Browserless 文档中的加固路由被描述为会修改浏览器行为,因此不是默认选项。如果目标站点拒绝了你的会话,这个会话仍然要由你来诊断。
第四行才是两个托管产品真正拉开差距的地方,也是自托管与两者都不同的地方。抓取服务把身份藏在管线内部;托管浏览器暴露状态,这是更窄的东西;自托管浏览器暴露整个浏览器,三种方案中只有它把指纹当作你有意设定的属性,而不是继承来的属性。
版本这一行值得举例,因为团队往往很晚才发现。按自己的节奏升级机队的服务商,会在脚本毫无变化的情况下更换渲染器,周一通过的布局断言周三可能失败,而任何提交都解释不了原因。自托管也不会让这一风险消失,它把风险转成由你排期的决策,这是另一类并且通常更可取的问题。Chrome for Testing 的版本固定正是把这件事变成决策而不是意外的机制。
会话状态不是浏览器身份
Browserless 文档给出两种在连接之间保留信息的方式,两者都值得细读,因为它们列出的内容本身就是重点。第一种是在脚本中途调用的 CDP 命令 Browserless.reconnect,它把正在运行的浏览器标记为可重连,让服务端在超时窗口内保持进程不关闭;文档指出它仅适用于 Puppeteer,因为需要 browser.disconnect(),而 Playwright 没有这个调用。第二种是 REST 会话 API,它为每个会话分配隔离的用户数据目录,并在浏览器完整重启之间保留数据数天。
standard session -> process kept alive; window in seconds to minutes; Puppeteer only
persisting state -> isolated user-data-dir per session; survives process restarts
会话 API 持久化 cookies、localStorage 与 cache,会话概览页列举的正是这些。已认证配置档案功能走得更远,它列出 cookies、localStorage 与 IndexedDB,并以文档给出的理由排除 sessionStorage:重放它会把过期状态注入 OAuth 重定向与 CSRF 流程。就目的而言这项排除是正确的工程决策,而它划出的边界才是重点:恢复的是登录状态和服务商选择保留的请求头,而不是产生它的整个浏览器。
请求头正是让上一段说法站得住的部分。从 Browserless 会话保存的档案还会记住该浏览器的 User-Agent,加载它的会话会呈现同一个;创建档案的会话与加载该档案的 Brave 会话发送稳定的 Accept-Language,其他 Brave 会话则保留 Brave 内置的变化。因此档案并非只有存储,准确的说法是它带一个已保存的登录加服务商文档里的两个请求头,而不是它完全不带身份。它仍然不带的是计算指纹所需的其余全部内容。
操作系统仿真参数从另一个方向显示了同样的分层。emulationOs 设定一组相互一致的系统派生信号:user agent、client hints、navigator.platform、WebGL 渲染器、hardwareConcurrency 与媒体设备列表。文档随后列出它不改变的内容,这份清单才是重点:时区、语言与 Accept-Language 跟随代理,而对桌面 persona 来说屏幕尺寸与视口仍属客户端。该参数在加固路由上生效,在解锁与抓取 REST API 上生效,包括 /unblock、/smart-scrape 及其带服务商前缀的变体,也在 BQL 路由与 agent 路由上生效,而在普通 /chromium 或 /chrome endpoint 上会被忽略。
emulationOs=windows -> changes: UA, client hints, navigator.platform, WebGL renderer
-> keeps: timezone, locale, Accept-Language, viewport, WebRTC
emulationOs=android -> also sets a high-DPI mobile screen and viewport
Android 取值正是让桌面规则变精确的例外。emulationOs=android 确实会在一套相互一致的移动 persona 中设定屏幕尺寸与像素比,并可用 emulatedDevice 进一步收窄;桌面 persona 则把两者都交给 defaultViewport 或 setViewport。脱离这个例外去读变更清单,会得出关于移动测试的错误结论。
这些都不是缺陷。每小时要跑数千个会话的服务不可能为每个会话提供定制二进制,文档写明的边界也如实说明了控制到哪里为止。实际后果更窄也更有用:如果你的要求是同一身份明天仍然出现,一个已保存的登录加两个稳定请求头还不够,其余表面必须来自别处。
对测试多账号或多区域行为的人来说还有第二个后果。恢复登录的档案非常适合续用单个账号,却完全不适合证明两个账号可以区分,因为区分它们的那部分并不是它恢复的存储。验证这一点需要两个环境:声明表面不同,其余完全相同,而这是保存的 cookie 无法表达的比较。
一个并不成立的对比
围绕这个产品的两个联想词是 browserless vs puppeteer 与 browserless vs playwright,两者都不是对等比较。Puppeteer 与 Playwright 是驱动浏览器的库,Browserless 是浏览器运行的场所。问谁胜出,就像问汽车是否胜过公路。
答案是组合,而且它按库的不同有一个文档写明的差异。使用 Puppeteer 时标准重连流程可用,因为存在 browser.disconnect()。使用 Playwright 时不可用,所以文档把 Playwright 用户引向 REST 会话 API。如果你的技术栈是 Playwright,就按这种形态规划;如果是 Puppeteer,两种形态都可用。
这种不对称在服务边界之外也有代价。依赖断开运行中浏览器的重连流程,假设客户端能区分正常断开与套接字中断,而 Playwright 脚本在套接字错误后重试,与 Puppeteer 脚本主动断开并不是同一件事。在选择持久化机制之前,值得写下脚本究竟在响应哪个事件:显式断开、超时,还是传输错误。只有第一种是生命周期决策,另外两种是故障,把一种当成另一种,会得到比测试预期存活更久的会话。
与自托管的比较是真实的,也是取舍最清楚的一处。自托管意味着你承担进程池、重启、版本升级与容量规划。用显式用户数据目录启动浏览器,并让该目录在多次启动之间保持稳定,才能使身份可复现;Puppeteer 启动与档案的陷阱说明了让档案保持权威的四个启动决策。在服务器上这项工作不会消失,只是转移了:headless 服务器配置是显示、二进制与恢复循环的检查清单,这些本来由托管 endpoint 替你吸收。
账单到底为什么收费
两边都能在不引用任何报价的情况下算出来,因为形态不同。托管浏览器按并发与会话时长收费,自托管浏览器按机器以及维持机器运行的工程小时收费。
managed monthly ~= peak_concurrency x hours_used x unit_price
self-hosted cost ~= machines x amortised_hours + ops_hours_per_month x loaded_rate
因此交叉点是利用率问题,上面的公式给出需要测量的三个值:peak_concurrency、hours_used 与 unit_price。如果峰值并发高而均值低,按单位计费跟随均值并占优,因为你没有为闲置机器付费。如果负载稳定、并发适中,一台可闲置的机器只计费一次,第二项占主导,这正是自托管占优的情形。公开的套餐上限直接描述第一项,从含两个并发浏览器、会话上限两分钟的免费档,到一百个并发浏览器、上限六十分钟,用它就能算出自己负载的托管侧成本,无需向任何人索取报价。
同一套算法还有两项。重启在两边都不是免费的:托管 endpoint 把它藏起来,于是它表现为延迟尾部而不是账单条目,自托管进程池则把它显示为必须预留的容量。另一项是存储,因为持久化档案会占用无状态请求不占用的额度,按身份保存大量已认证状态的工作负载,即使某天不打开浏览器也要为这份状态付费。
还有两项成本容易被漏掉。第一是身份工作:如果工作负载需要跨会话稳定的浏览器,这项要求尚未出现在任何一栏,而在托管会话内满足它,仍然意味着你要自己携带档案。第二是会话上限:合法运行时间超过套餐会话上限的任务会变成另一个任务,被拆成可恢复的片段,而无论谁托管浏览器,这种重新设计都是工作量。
两种形态都不是默认答案。只需一个不需要登录的夜间导出,在托管 endpoint 上更便宜也更简单;需要在三月和四月看起来都一样的一批长期身份,在你掌控的机器上更便宜也更简单。
实践结论
按层选择,而不是按品牌选择。如果任务是一个独立请求,无状态抓取 API 是最短路径。如果任务需要真实会话但不需要持久身份,托管浏览器去掉了运维表面,其公开的会话上限就是设计时需要遵守的约束。如果任务需要同一个浏览器在下周仍可被认作同一个,这项要求位于状态层之下,必须在你能设定二进制、字体与启动标志的地方承担。
两项检查让决策变得具体。第一,写下要求是状态还是身份;Browserless 文档明确说明其档案恢复 cookies、localStorage、IndexedDB、User-Agent 与 Accept-Language,也明确说明 emulationOs 不改动桌面 persona 的时区、语言与视口。第二,计算峰值与均值并发,因为决定哪份账单增长更快的是这个比例,而不是月度请求量。
当托管 endpoint 上的会话失败时,诊断仍属于客户端:哪些选择器、哪些请求头、哪个导航时机、哪个构建。关于安装了哪个构建以及如何固定,请读Chrome for Testing 的版本固定;关于页面能观察到会话本身的哪些信号,请读headless 与 headed 的信号对比。
来源
本文属于指南: 浏览器部署与发布:运维决策地图