快速摘要: Stagehand 是一个将 AI 能力与传统代码精确控制相融合的开源浏览器自动化框架,目前在 GitHub 上已获得超过 20K Star。 它的核心理念非常务实——确定的部分用代码控制,不确定的部分交给 AI 处理,通过act()、extract()、observe()三个原子级 API 覆盖绝大多数自动化场景。框架内置了自愈机制和操作缓存,能在网页改版后自动修复脚本,同时大幅降低重复执行时的 LLM 调用开销。如果你正在被传统自动化脚本的维护成本折磨,或者对纯 AI Agent 的不可控性心存顾虑,Stagehand 提供的"人机协同"路线值得深入了解。往下看,莫潇羽@无忆研习社 将为你做更详细的原理拆解和上手指南。
一、浏览器自动化的老问题:脆弱、复杂、难以维护
做过爬虫或自动化测试的朋友,大概都经历过这样的场景:周五下班前脚本还跑得好好的,周一早上一看全报错了——原来是目标网站在周末悄悄更新了页面结构。一个 div 的 class 换了名字,一个按钮从页面左边挪到了右边,甚至只是加了一层嵌套,原本精心编写的选择器就全部失效了。
这不是个例,而是传统浏览器自动化工具(比如 Selenium、Playwright、Puppeteer)面临的结构性问题。这些工具的工作方式是:开发者通过 CSS 选择器、XPath 表达式或者元素 ID 精确定位页面上的某个元素,然后对它执行点击、输入、滚动等操作。这种方式在页面结构稳定的时候非常可靠,但问题在于——现代 Web 应用的页面结构变化实在太频繁了。
莫潇羽@无忆研习社 总结了一下,传统自动化脚本容易出问题的原因,通常集中在以下几个方面:
- 页面结构复杂且嵌套层级深,选择器一旦写得过于具体就很容易因微小改动而失效
- 前端团队频繁迭代 UI,class 名称、DOM 层级随时可能变化
- 不同用户角色、不同账号状态看到的页面布局可能完全不同
- 动态渲染的内容(比如 SPA 单页应用中异步加载的元素)增加了元素定位的不确定性
- 部分页面采用 Shadow DOM 或 iframe 嵌套,传统选择器很难穿透
这些问题的本质,其实是"不确定性"。你没办法提前预知目标网站明天会变成什么样子,也没办法用一套固定的选择器永远匹配到同一个元素。
那换一种思路,把一切都交给 AI Agent 来做行不行?比如直接给一个指令"帮我登录这个网站然后把数据下载下来",让 AI 自己去看页面、找按钮、点击操作。这条路线近几年确实有不少项目在探索,但实际用下来会发现另一个问题:纯 AI Agent 的行为不够可预测。它可能在页面上乱点一气,也可能因为大语言模型的"幻觉"而执行完全错误的操作。在开发调试阶段这些问题还好处理,但如果是要在生产环境长期运行的自动化任务,这种不确定性就不太能接受了。
所以行业真正需要的,不是在"纯代码"和"纯 AI"之间二选一,而是一种能把两者优势结合起来的中间方案。
二、Stagehand 是什么:AI 与代码的混合控制框架
Stagehand 是由 Browserbase 团队开发并开源的一个浏览器自动化框架。项目采用 MIT 许可证发布在 GitHub 上(仓库地址:https://github.com/browserbase/stagehand),截至本文撰写时,已经获得了超过 20.8K 个 Star 和 1.3K 个 Fork,在浏览器自动化领域的关注度相当高。
Stagehand 的定位非常明确:它不是要用 AI 替代所有的代码逻辑,也不是只做一个简单的 Playwright 封装。它走的是一条"人机协同"的中间路线——把 AI 当作一个可调用的智能函数嵌入到你的自动化脚本中,让开发者自己决定哪些步骤用精确的代码控制,哪些步骤交给 AI 来处理。
用一句话概括就是:哪里确定用代码,哪里不确定交给 AI。
这个设计理念在实际使用中的体现是:你可以在同一个脚本里,前一行用传统的 Playwright 代码 page.goto("https://example.com") 导航到指定页面(因为目标 URL 是确定的),下一行就切换为 stagehand.act("点击登录按钮") 让 AI 来找到并点击登录按钮(因为按钮的选择器可能随时变化)。两种方式可以无缝混合使用,不存在任何切换成本。
莫潇羽在深入研究这个项目后认为,Stagehand 的这种设计思路之所以值得关注,是因为它抓住了自动化工程中一个非常本质的矛盾:确定性与灵活性的平衡。在真实业务场景中,一个自动化流程里既有完全确定的步骤(比如访问固定的 URL、填写固定格式的数据),也有充满不确定性的步骤(比如在一个你不熟悉的第三方网站上找到某个按钮)。Stagehand 允许你针对不同的步骤选择最合适的处理方式,而不是强迫你全都用同一种方案。
Stagehand 的技术演进历程
了解 Stagehand 的发展历程有助于更好地理解它的设计决策。
Stagehand 最初是作为 Playwright 之上的一个 AI 增强层来构建的。早期版本直接扩展了 Playwright 的 Page 对象,在其基础上添加了 act()、extract()、observe() 三个 AI 驱动的方法。这意味着你可以继续使用所有原生的 Playwright 方法,同时获得 AI 能力的加持。这种"渐进式增强"的策略让 Stagehand 在早期迅速获得了大量采用——开发者可以在现有的 Playwright 项目中直接引入 Stagehand,不需要重写任何代码。
但随着用户规模的扩大和使用场景的深入,团队发现 Playwright 的"测试优先"设计理念与 Stagehand 的"自动化优先"需求之间开始出现一些摩擦。比如 Playwright 内置的可操作性检查(actionability checks)和自动等待(auto-waiting)机制对于测试场景非常有用,但在追求效率的自动化场景中会带来不必要的延迟。更关键的是,Stagehand 需要深度使用 Chrome DevTools Protocol(CDP)来获取无障碍树、DOM 快照、网络事件等丰富的上下文信息以供 AI 模型分析,而 Playwright 的抽象层在某些方面限制了这种底层访问能力。
于是,Stagehand v3 做了一个重大的架构决策:移除对 Playwright 的核心依赖,转而直接基于 CDP 协议构建。这不是说不再支持 Playwright——你仍然可以在 Stagehand 中使用 Playwright 作为浏览器驱动——而是说 Stagehand 的核心引擎不再绑定于任何特定的浏览器驱动框架。v3 引入了一个模块化的驱动系统,支持 Playwright、Puppeteer 或任何基于 CDP 的驱动程序。
这个架构变更带来了显著的性能提升。根据官方的基准测试数据,Stagehand v3 在涉及深层嵌套 iframe 和 Shadow DOM 的测试场景中,整体操作速度比 v2 版本提升了 44% 以上。同时,新的上下文构建器(Context Builder)能够更智能地筛选发送给 AI 模型的信息,减少了不必要的 token 消耗。
三、三大核心 API 详解:act、extract、observe
Stagehand 将复杂的浏览器操作抽象为三个极其简洁的原子级方法。这种 API 设计非常符合实际使用中的思维直觉——你要么是想"做点什么",要么是想"看看有什么",要么是想"拿到些什么"。下面逐一详细拆解。
3.1 act() — 动作执行:告别选择器的时代
act() 是 Stagehand 中使用频率最高的方法,它的作用是执行一个浏览器操作——点击按钮、输入文字、滚动页面、选择下拉选项等等。与传统方式最大的区别在于:你不需要写任何选择器,只需要用自然语言描述你想做什么。
先来看一个对比。在传统的 Playwright 脚本中,如果你想点击一个登录按钮,代码通常长这样:
// 传统 Playwright 方式
// 需要依赖特定的选择器,一旦 ID 或 class 变化就会失效
await page.click('#login-btn-v2');
// 或者用更复杂的选择器
await page.click('xpath=//button[contains(@class, "primary") and contains(text(), "Submit")]');
这里面有一个隐含的假设:#login-btn-v2 这个 ID 或者那个复杂的 XPath 表达式会一直有效。但现实中,前端工程师可能在下一次迭代中把按钮的 ID 改成 #btn-login,或者把文案从"Submit"改成"确认提交",你的脚本就崩了。
用 Stagehand 的 act() 来做同样的事情:
// Stagehand 方式
// 用自然语言描述意图,AI 来匹配元素
await stagehand.act("Click the login button");
就这么简单。不管登录按钮的 ID 是什么、class 叫什么名字、甚至按钮上的文字是"Login"还是"登录"还是"Sign In",只要页面上存在一个语义上符合"登录按钮"描述的元素,AI 就能找到并点击它。
act() 的底层工作原理
当你调用 act("Click the login button") 时,Stagehand 在底层执行了以下步骤:
第一步,页面上下文采集。Stagehand 通过 CDP 协议获取当前页面的多维度信息。在早期版本中,这主要依赖于原始 DOM 的解析;但从新版本开始,Stagehand 默认使用 Chrome 无障碍树(Accessibility Tree) 作为主要的页面理解来源。无障碍树是浏览器为屏幕阅读器等辅助工具构建的一套结构化页面描述,它过滤掉了大量的 CSS 样式和布局噪音,保留了页面元素的语义信息(比如"这是一个按钮"、"这是一个输入框"、"这是一个链接"),非常适合作为 AI 理解页面的输入。
第二步,上下文优化。v3 版本引入的新上下文构建器会对采集到的信息进行智能筛选和压缩,只将与当前操作相关的部分发送给 LLM。这一步非常关键——如果把整个页面的 DOM 都丢给 AI 模型,不仅浪费 token(意味着更高的调用成本和更长的响应时间),还可能因为信息过多而导致模型判断失误。
第三步,LLM 推理。经过优化的页面上下文连同用户的自然语言指令一起发送给大语言模型(Stagehand 支持多种模型,包括 OpenAI 的 GPT 系列和 Anthropic 的 Claude 系列等)。模型分析后返回一个具体的操作方案——比如"点击坐标 (235, 480) 处的元素"或者"点击 ID 为 xxx 的元素"。
第四步,操作执行。Stagehand 将 LLM 返回的操作方案转化为底层的 CDP 命令,发送给浏览器执行。
这个流程中有一个很重要的设计:与纯 AI Agent 不同,Stagehand 允许你在执行前预览 AI 将要采取的操作。这意味着在开发和调试阶段,你可以先看看 AI 打算做什么,确认没问题后再让它执行。这大大降低了"AI 乱点"的风险。
act() 的实战用法示例
// 基础用法:执行单个操作
await stagehand.act("Click the login button");
// 输入文本
await stagehand.act("Type 'hello@example.com' into the email field");
// 处理下拉菜单
await stagehand.act("Select 'California' from the state dropdown");
// 滚动操作
await stagehand.act("Scroll down to the pricing section");
// 复杂操作描述
await stagehand.act("Close the cookie consent popup if it appears");
莫潇羽@无忆研习社 这里要提醒一点:act() 的设计理念是执行单个原子操作。如果你需要完成一个包含多个步骤的复合任务(比如"登录账户然后导航到设置页面"),应该将其拆分为多个 act() 调用,或者使用后面会介绍的 agent() 方法。
3.2 extract() — 数据提取:结构化的信息采集
如果说 act() 是 Stagehand 的"手",那 extract() 就是它的"眼"和"脑"。在我看来,extract() 是 Stagehand 最具实用价值的功能——它让数据抓取变得前所未有地简单。
传统的网页数据提取是怎么做的?通常是这样一个流程:先用浏览器开发者工具检查目标数据所在的 DOM 结构,然后编写对应的 CSS 选择器或 XPath 来定位元素,再用正则表达式或字符串处理来清洗提取到的文本。碰到结构复杂的页面(比如商品列表页,每个商品卡片的 HTML 结构可能都不完全一致),这个过程会非常繁琐,而且写出来的代码维护成本极高。
Stagehand 的 extract() 彻底改变了这个工作模式。你只需要做两件事:定义你想要的数据结构,然后告诉 AI 去提取。
import { z } from "zod";
// 第一步:用 Zod Schema 定义你想要的数据结构
const productSchema = z.object({
productName: z.string().describe("The name of the product"),
price: z.number().describe("The price in USD"),
rating: z.number().describe("The user rating out of 5"),
reviewCount: z.number().describe("The total number of reviews"),
availability: z.string().describe("In stock or out of stock status"),
});
// 第二步:调用 extract,AI 自动从页面中提取并结构化数据
const productData = await stagehand.extract(
"Extract the main product details from this page",
productSchema
);
// 输出的就是一个干净的、符合你定义的 Schema 的 JSON 对象
console.log(productData);
// {
// productName: "Wireless Bluetooth Headphones",
// price: 79.99,
// rating: 4.5,
// reviewCount: 2847,
// availability: "In Stock"
// }
这里有几个值得关注的细节:
Schema 驱动的类型安全。extract() 使用 Zod(一个 TypeScript 优先的数据校验库)来定义数据结构。这意味着提取出来的数据不仅有类型保障,而且如果 AI 返回的数据不符合你定义的 Schema,会直接报错而不是给你一个残缺或错误的对象。这在构建数据管道时非常重要——你不希望脏数据悄悄地流入下游系统。
跨布局的理解能力。传统的选择器方案严重依赖于页面的具体 HTML 结构——如果商品信息是用 <table> 展示的,你需要写一套提取逻辑;换成 <div> 卡片布局,又要写另一套。但 extract() 完全不关心底层的 HTML 结构,AI 理解的是页面的语义,而不是 DOM 的结构。无论数据是用表格、卡片、列表还是其他任何方式展示的,只要人眼能看到这些信息,AI 就能提取出来。
自动数据清洗。提取出的数据会自动按照 Schema 中定义的类型进行转换。比如你在 Schema 中把 price 定义为 z.number(),即使页面上显示的是 "$79.99"(一个带美元符号的字符串),AI 也会自动将其转换为数字 79.99。
提取复杂数据结构的示例
extract() 不仅能处理简单的键值对,还能处理嵌套的复杂数据结构:
// 提取一个包含多条记录的列表
const searchResultsSchema = z.object({
results: z.array(z.object({
title: z.string().describe("The title of the search result"),
url: z.string().describe("The URL of the result"),
snippet: z.string().describe("The description snippet"),
})).describe("All search results on the current page"),
totalResults: z.string().describe("The total number of results shown"),
currentPage: z.number().describe("The current page number"),
});
const searchData = await stagehand.extract(
"Extract all search results and pagination info",
searchResultsSchema
);
对于从事数据采集工作的团队来说,extract() 的价值不言而喻——它把以前需要几十行甚至上百行的解析代码,压缩到了几行 Schema 定义。更重要的是,这些 Schema 定义不会因为页面改版而失效,因为它描述的是"你想要什么数据",而不是"数据在页面的哪个位置"。
3.3 observe() — 观察与推理:让脚本拥有"判断力"
observe() 是三个核心方法中最容易被忽视的一个,但它在构建智能化自动化流程中的作用不可替代。
observe() 的功能可以这样理解:它让你的脚本能够"看"一眼当前页面,理解页面上有什么,然后把这些信息返回给你,让你(或者后续的代码逻辑)来决定下一步该做什么。
// 观察页面上有哪些可交互的元素
const elements = await stagehand.observe(
"Find all links related to 'Help Center'"
);
console.log(elements);
// 返回一个数组,包含每个匹配元素的描述、选择器等信息
// [
// { description: "Help Center link in the footer", selector: "..." },
// { description: "Help Center button in the sidebar", selector: "..." },
// ]
observe() 的典型使用场景包括:
动态决策。当你的脚本需要根据页面的实际状态来决定下一步操作时,observe() 就派上用场了。比如一个电商平台的自动化流程,你可能需要先"看看"当前页面是商品列表页还是单个商品详情页,然后根据判断结果走不同的处理分支。
// 先观察页面状态
const pageState = await stagehand.observe(
"Is there a login form or a user dashboard on this page?"
);
// 根据观察结果决定下一步
if (pageState.some(el => el.description.includes("login form"))) {
await stagehand.act("Fill in the email with test@example.com");
await stagehand.act("Fill in the password with mypassword");
await stagehand.act("Click the login button");
} else {
// 已经登录,直接进行后续操作
await stagehand.act("Navigate to settings");
}
元素发现。当你需要知道页面上某类元素有多少个、分别在哪里时,observe() 比盲目地调用 act() 要安全得多。你可以先观察,拿到元素列表后,再有针对性地操作。
调试辅助。在开发阶段,你可以用 observe() 来快速了解一个陌生页面的结构,而不需要手动打开浏览器开发者工具去翻 DOM。
3.4 agent() — 多步骤任务的智能编排
除了上述三个原子级方法,Stagehand 还提供了一个 agent() 方法,用于执行需要多个步骤才能完成的复杂任务。
// 创建一个 Agent 实例
const agent = stagehand.agent();
// 用自然语言描述一个复杂的多步骤任务
await agent.execute("Navigate to the latest pull request on this repository");
agent() 的工作方式类似于将多个 observe() → act() → observe() → act() 的循环自动串联起来。你给它一个高层级的目标,Agent 会自主决定需要执行哪些中间步骤,并逐步完成。
不过,莫潇羽@无忆研习社 需要诚实地说,根据社区反馈和实际测试,agent() 的表现目前还不如三个原子方法那么稳定。在处理简单直观的多步骤任务时,它通常能很好地完成;但面对特别复杂或者路径不明确的任务时,可能会出现卡住或走错方向的情况。所以在生产环境中,更推荐使用原子方法配合你自己的控制逻辑来编排复杂流程,而把 agent() 留给开发调试或者对失败容忍度较高的场景。
Stagehand 的 Agent 模式是模型无关的(model-agnostic),这意味着你可以搭配不同的 LLM 来使用它。根据 Browserbase 团队的测试经验,不同的大语言模型在不同的任务类型上表现各异:Anthropic 的 Claude 在高层级推理和复杂决策方面表现突出,更适合用作 Agent 的"大脑";而 OpenAI 的 GPT 系列在执行具体的浏览器操作指令时更加精准。Stagehand 允许你为不同的操作配置不同的模型,以获得最优的综合表现。
四、两大工程亮点:自愈机制与操作缓存
如果只是把 AI 接入浏览器操作,这件事的技术含量其实并不高——市面上已经有不少类似的方案了。Stagehand 真正让人觉得"这是一个经过深思熟虑的工程产品"的地方,在于它的自愈机制和操作缓存这两个特性。
4.1 Self-Healing 自愈机制:脚本不再"一碰就碎"
传统自动化面临的最大维护噩梦就是"页面一改,脚本全崩"。Stagehand 的自愈机制直接针对这个痛点。
它的工作逻辑是这样的:
当一个自动化脚本在执行过程中遭遇失败——比如之前缓存的选择器对应的元素不存在了——Stagehand 不会像传统工具那样直接抛出错误然后终止执行。取而代之的是,它会自动触发一个"修复"流程:
首先,Stagehand 检测到操作失败,判断失败的可能原因(元素不存在?元素被遮挡?页面结构变化了?)。
然后,它重新调用 AI 来分析当前的页面状态,寻找一条新的操作路径——也许登录按钮的 ID 变了,但按钮还在页面上,只是换了一个名字或换了一个位置。
最后,找到新路径后,Stagehand 执行修复后的操作并继续运行后续流程。
整个过程对开发者来说是透明的——你的脚本代码不需要做任何修改。这就好比你请了一个助手帮你做事,你只需要告诉他"去按一下那个登录按钮",至于按钮是蓝色的还是红色的、在页面左边还是右边,这些细节由助手自己去适应。
自愈功能可以通过配置开启:
const stagehand = new Stagehand({
env: "LOCAL",
selfHeal: true, // 开启自愈模式
});
4.2 Action Caching 操作缓存:又快又省
AI 推理是有成本的——每次调用 LLM 都意味着时间和 token 的消耗。如果你的自动化脚本每天要运行上百次,每次都对同一个页面的同一个操作重新调用 AI 来分析,那累计下来的开销会非常可观。
Stagehand 的操作缓存机制完美解决了这个问题。
当 AI 第一次分析页面并成功执行了某个操作后,Stagehand 会自动将这个操作的"成功路径"缓存起来——包括 AI 的判断结果和对应的元素信息。下次再执行同样的操作时,Stagehand 会直接使用缓存的路径,跳过 LLM 推理,从而实现接近原生 Playwright 的执行速度。
更精妙的是,操作缓存和自愈机制是协同工作的:
第一次执行 → AI 分析页面 → 找到元素 → 执行操作 → 缓存结果
第二次执行 → 使用缓存 → 直接执行(无 AI 调用,极快)
第 N 次执行 → 使用缓存 → 发现元素变了 → 触发自愈 → AI 重新分析 → 找到新元素 → 执行 → 更新缓存
第 N+1 次执行 → 使用更新后的缓存 → 直接执行
这套机制的巧妙之处在于:正常情况下,脚本以极快的速度运行,不依赖 AI;只有当页面发生变化导致缓存失效时,AI 才介入修复。这种"按需调用 AI"的策略,完美平衡了执行速度、运行成本和脚本的韧性。
操作缓存可以通过环境变量或配置项来控制:
const stagehand = new Stagehand({
env: "LOCAL",
cacheDir: "./stagehand-cache", // 指定缓存存储目录
});
也可以通过设置环境变量 ENABLE_CACHING=true 来全局启用。
五、从零开始上手 Stagehand:完整操作指南
了解了原理之后,来看看如何实际上手使用 Stagehand。以下内容以 TypeScript(Node.js 环境)为例,因为 Stagehand 的核心实现是 TypeScript 编写的,这也是功能最完整、社区支持最好的版本。同时 Stagehand 也提供了 Python、Rust、Go、C# 等语言的 SDK,后面会简要介绍。
5.1 环境准备
在开始之前,确保你的开发环境满足以下条件:
- Node.js 18 或更高版本(推荐使用 LTS 版本)
- npm 或 pnpm 包管理器
- 一个 LLM 服务的 API Key(比如 OpenAI 或 Anthropic 的 API Key)
如果你计划使用 Browserbase 的云端浏览器服务(适合生产环境部署),还需要准备 Browserbase 的 API Key 和 Project ID。不过本地开发时,使用本机浏览器就足够了。
5.2 最快的起步方式:一行命令
Stagehand 提供了一个脚手架工具,可以帮你快速创建一个预配置好的项目:
npx create-browser-app
运行这个命令后,CLI 会引导你完成项目创建:选择项目目录、配置 API Key 等。完成后,进入项目目录:
cd my-stagehand-app
cp .env.example .env # 创建环境变量文件
然后编辑 .env 文件,填入你的 API Key:
# .env 文件内容
# 选择你使用的 LLM 提供商,填入对应的 API Key
OPENAI_API_KEY=sk-your-openai-api-key-here
# 或者
ANTHROPIC_API_KEY=sk-ant-your-anthropic-api-key-here
# 如果使用 Browserbase 云端浏览器(本地开发可以不填)
# BROWSERBASE_API_KEY=your-browserbase-api-key
# BROWSERBASE_PROJECT_ID=your-project-id
最后运行示例脚本:
npm start
5.3 手动安装与配置
如果你更喜欢在现有项目中手动集成 Stagehand,或者想更细粒度地控制配置,可以按以下步骤操作。
首先安装 Stagehand 包:
npm install @browserbasehq/stagehand
# 或者用 pnpm
pnpm add @browserbasehq/stagehand
同时安装 Zod(用于定义 extract() 的 Schema):
npm install zod
然后创建你的自动化脚本。以下是一个完整的入门示例:
// index.ts
import "dotenv/config";
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";
async function main() {
// 初始化 Stagehand
// env: "LOCAL" 表示使用本机浏览器
// env: "BROWSERBASE" 表示使用 Browserbase 云端浏览器
const stagehand = new Stagehand({
env: "LOCAL",
});
// 启动浏览器会话
await stagehand.init();
console.log("Stagehand session started");
// 获取页面对象
const page = stagehand.context.pages()[0];
// 导航到目标网页(这步用精确的代码控制)
await page.goto("https://news.ycombinator.com");
// 用 observe() 观察页面上有哪些可操作的元素
const observeResult = await stagehand.observe(
"Find the link to the top story"
);
console.log("Observed elements:", observeResult);
// 用 act() 执行一个操作
await stagehand.act("Click on the first story title");
// 用 extract() 提取当前页面的数据
const articleData = await stagehand.extract(
"Extract the title and any metadata about this page",
z.object({
title: z.string().describe("The title of the article or page"),
source: z.string().describe("The source or domain of the content"),
})
);
console.log("Extracted data:", articleData);
// 关闭会话
await stagehand.close();
}
main().catch(console.error);
5.4 完整配置项详解
Stagehand 的构造函数接受一个配置对象,以下是各配置项的说明:
const stagehand = new Stagehand({
// 运行环境:"LOCAL" 使用本机浏览器,"BROWSERBASE" 使用云端浏览器
env: "LOCAL",
// AI 模型配置
model: {
name: "gpt-4o", // 使用的模型名称
// 也可以使用 "claude-sonnet-4-20250514" 等其他模型
},
// 系统提示词(可选,用于引导 AI 的行为)
systemPrompt: "You are helping to automate a web browsing task...",
// 是否开启自愈模式
selfHeal: true,
// DOM 稳定等待超时时间(毫秒)
domSettleTimeout: 3000,
// 缓存目录
cacheDir: "./stagehand-cache",
// 日志级别:0 = 静默,1 = 基本信息,2 = 详细调试
verbose: 1,
// 本地浏览器启动选项(仅 env: "LOCAL" 时有效)
localBrowserLaunchOptions: {
headless: false, // false = 打开可视化窗口,方便调试
},
});
5.5 一个更接近实际应用的完整示例
下面是一个从 GitHub 仓库页面提取信息的完整示例,演示了如何混合使用代码控制和 AI 操作:
import "dotenv/config";
import { Stagehand } from "@browserbasehq/stagehand";
import { z } from "zod";
async function scrapeGithubRepo() {
const stagehand = new Stagehand({
env: "LOCAL",
verbose: 1,
});
await stagehand.init();
const page = stagehand.context.pages()[0];
// 第一步:精确导航(用代码控制,因为 URL 是确定的)
await page.goto("https://github.com/browserbase/stagehand");
// 第二步:AI 操作(点击一个不确定选择器的按钮)
await stagehand.act("Click on the 'Issues' tab");
// 第三步:AI 提取(获取 Issues 列表的结构化数据)
const issuesSchema = z.object({
issues: z.array(z.object({
title: z.string().describe("The title of the issue"),
author: z.string().describe("The username who opened the issue"),
labels: z.array(z.string()).describe("The labels attached to the issue"),
commentCount: z.number().describe("Number of comments on the issue"),
})).describe("The list of issues visible on the current page"),
openCount: z.string().describe("Total number of open issues"),
});
const issuesData = await stagehand.extract(
"Extract the list of issues shown on this page",
issuesSchema
);
console.log(`Found ${issuesData.issues.length} issue