ClawSkills logoClawSkills

Browserbase

使用 stagehand CLI 创建和部署浏览器自动化函数的完整指南

介绍

# Browser Automation & Functions Skill

使用 `stagehand` CLI 创建和部署浏览器自动化功能的完整指南。

## 使用场景

- 用户想要自动化网站任务 - 用户需要从网站抓取数据 - 用户想要创建 Browserbase Function - 用户想要部署自动化任务以便按计划或通过 Webhook 运行

## 前置条件

### 设置凭证

```bash stagehand fn auth status # Check if configured stagehand fn auth login # If needed - get credentials from https://browserbase.com/settings ```

## 完整工作流程

### 步骤 1:交互式探索网站

启动本地浏览器会话以了解网站结构:

```bash stagehand session create --local stagehand goto https://example.com stagehand snapshot # Get DOM structure with refs stagehand screenshot -o page.png # Visual inspection ```

手动测试交互: ```bash stagehand click @0-5 stagehand fill @0-6 "value" stagehand eval "document.querySelector('.price').textContent" stagehand session end # When done exploring ```

### 步骤 2:初始化功能项目

```bash stagehand fn init my-automation cd my-automation ```

创建内容: - `package.json` - 依赖项 - `.env` - 凭证(来自 `~/.stagehand/config.json`) - `index.ts` - 功能模板 - `tsconfig.json` - TypeScript 配置

### 步骤 3:⚠️ 立即修复 package.json

**严重 BUG**:`stagehand fn init` 生成的 `package.json` 不完整,会导致部署失败并提示 "No functions were built."

**必需的修复** - 在进行其他操作之前更新 `package.json`:

```json { "name": "my-automation", "version": "1.0.0", "description": "My automation description", "main": "index.js", "type": "module", "packageManager": "[email protected]", "scripts": { "dev": "pnpm bb dev index.ts", "publish": "pnpm bb publish index.ts" }, "dependencies": { "@browserbasehq/sdk-functions": "^0.0.5", "playwright-core": "^1.58.0" }, "devDependencies": { "@types/node": "^25.0.10", "typescript": "^5.9.3" } } ```

**相比生成文件的关键更改:** - ✅ 添加 `description` 和 `main` 字段 - ✅ 添加 `packageManager` 字段 - ✅ 将 `"latest"` 更改为固定版本,如 `"^0.0.5"` - ✅ 添加包含 TypeScript 和类型的 `devDependencies`

然后安装: ```bash pnpm install ```

### 步骤 4:编写自动化代码

编辑 `index.ts`:

```typescript import { defineFn } from "@browserbasehq/sdk-functions"; import { chromium } from "playwright-core";

defineFn("my-automation", async (context) => { const { session, params } = context; console.log("Connecting to browser session:", session.id);

const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;

// Your automation here await page.goto("https://example.com"); await page.waitForLoadState("domcontentloaded");

// Extract data const data = await page.evaluate(() => { // Complex extraction logic return Array.from(document.querySelectorAll('.item')).map(el => ({ title: el.querySelector('.title')?.textContent, value: el.querySelector('.value')?.textContent, })); });

// Return results (must be JSON-serializable) return { success: true, count: data.length, data, timestamp: new Date().toISOString(), }; }); ```

**关键概念:** - `context.session` - 浏览器会话信息(id, connectUrl) - `context.params` - 调用时的输入参数 - 返回 JSON 可序列化的数据 - 最长执行时间为 15 分钟

### 步骤 5:本地测试

启动开发服务器: ```bash pnpm bb dev index.ts ```

服务器运行在 `http://127.0.0.1:14113`

使用 curl 调用: ```bash curl -X POST http://127.0.0.1:14113/v1/functions/my-automation/invoke \ -H "Content-Type: application/json" \ -d '{"params": {"url": "https://example.com"}}' ```

开发服务器会在文件更改时自动重新加载。检查终端日志。

### 步骤 6:部署到 Browserbase

```bash pnpm bb publish index.ts # or: stagehand fn publish index.ts ```

**预期输出:** ``` ✓ Build completed successfully Build ID: xxx-xxx-xxx Function ID: yyy-yyy-yyy ← Save this! ```

**如果看到 "No functions were built"** → 您的 package.json 不完整(参见步骤 3)。

### 步骤 7:测试生产环境

```bash stagehand fn invoke <function-id> -p '{"param": "value"}' ```

或通过 API: ```bash curl -X POST https://api.browserbase.com/v1/functions/<function-id>/invoke \ -H "Content-Type: application/json" \ -H "x-bb-api-key: $BROWSERBASE_API_KEY" \ -d '{"params": {}}' ```

## 完整工作示例:Hacker News 抓取器

```typescript import { defineFn } from "@browserbasehq/sdk-functions"; import { chromium } from "playwright-core";

defineFn("hn-scraper", async (context) => { const { session } = context; console.log("Connecting to browser session:", session.id);

const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;

await page.goto("https://news.ycombinator.com"); await page.waitForLoadState("domcontentloaded");

// Extract top 10 stories const stories = await page.evaluate(() => { const storyRows = Array.from(document.querySelectorAll('.athing')).slice(0, 10);

return storyRows.map((row) => { const titleLine = row.querySelector('.titleline a'); const subtext = row.nextElementSibling?.querySelector('.subtext'); const commentsLink = Array.from(subtext?.querySelectorAll('a') || []).pop();

return { rank: row.querySelector('.rank')?.textContent?.replace('.', '') || '', title: titleLine?.textContent || '', url: titleLine?.getAttribute('href') || '', points: subtext?.querySelector('.score')?.textContent?.replace(' points', '') || '0', author: subtext?.querySelector('.hnuser')?.textContent || '', time: subtext?.querySelector('.age')?.textContent || '', comments: commentsLink?.textContent?.replace(/\u00a0comments?/, '').trim() || '0', id: row.id, }; }); });

return { success: true, count: stories.length, stories, timestamp: new Date().toISOString(), }; }); ```

## 常见模式

### 参数化抓取 ```typescript defineFn("scrape", async (context) => { const { session, params } = context; const { url, selector } = params; // Accept params from invocation

const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;

await page.goto(url); const data = await page.$eval(selector, els => els.map(el => el.textContent) );

return { url, data }; }); ```

### 身份验证 ```typescript defineFn("auth-action", async (context) => { const { session, params } = context; const { username, password } = params;

const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;

await page.goto("https://example.com/login"); await page.fill('input[name="email"]', username); await page.fill('input[name="password"]', password); await page.click('button[type="submit"]'); await page.waitForURL("**/dashboard");

const data = await page.textContent('.user-data'); return { success: true, data }; }); ```

### 多页面工作流 ```typescript defineFn("multi-page", async (context) => { const { session, params } = context; const browser = await chromium.connectOverCDP(session.connectUrl); const page = browser.contexts()[0]!.pages()[0]!;

const results = []; for (const url of params.urls) { await page.goto(url); await page.waitForLoadState("domcontentloaded");

const title = await page.title(); results.push({ url, title }); }

return { results }; }); ```

## 故障排查

### 🔴 "No functions were built. Please check your entrypoint and function exports."

**这是第 1 号错误!**

**原因:** `stagehand fn init` 生成的 `package.json` 不完整。

**修复:** 1. 更新 `package.json`(见上文步骤 3) 2. 添加所有必需字段:`description`、`main`、`packageManager` 3. 将 `"latest"` 更改为固定版本,如 `"^0.0.5"` 4. 添加包含 TypeScript 和类型的 `devDependencies` 部分 5. 运行 `pnpm install` 6. 再次尝试部署

**快速检查:** 将您的 `package.json` 与代码库中的 `bitcoin-functions/package.json` 进行对比。

### 本地开发服务器无法启动

```bash # Check credentials stagehand fn auth status

# Re-login if needed stagehand fn auth login

# Install SDK globally pnpm add -g @browserbasehq/sdk-functions ```

### 功能在本地有效但部署失败

**常见原因:** 1. 缺少 `devDependencies`(TypeScript 无法编译) 2. 使用了 `"latest"` 而非固定版本 3. `package.json` 中缺少必需字段

**解决方案:** 按照步骤 3 所述修复 package.json。

### 无法从页面提取数据

1. 截图:`stagehand screenshot -o debug.png` 2. 获取快照:`stagehand snapshot` 3. 使用 `page.evaluate()` 记录 DOM 中的内容 4. 检查选择器是否匹配实际的 HTML 结构

### "Invocation timed out"

- 功能最长运行时间为 15 分钟 - 使用特定的等待方式而不是长时间休眠 - 检查页面是否确实正在加载

## 最佳实践

1. ✅ 在 `stagehand fn init` 之后**立即修复 package.json** 2. ✅ **先进行交互式探索** - 使用本地浏览器会话了解网站 3. ✅ **手动测试** - 在编写代码之前验证每个步骤是否有效 4. ✅ **本地测试** - 部署前使用开发服务器 5. ✅ **返回有意义的数据** - 包含时间戳、计数、URL 6. ✅ **优雅地处理错误** - 在有风险的操作周围使用 try/catch 7. ✅ **使用特定的选择器** - 优先使用数据属性而非 CSS 类 8. ✅ **添加日志** - `console.log()` 有助于调试已部署的功能 9. ✅ **验证参数** - 在使用前检查 `params` 10. ✅ **设置合理的超时时间** - 不要永远等待

## 快速检查清单

- [ ] 使用 `stagehand session create --local` 探索网站 - [ ] 手动测试交互 - [ ] 创建项目:`stagehand fn init <name>` - [ ] **立即修复 package.json**(步骤 3) - [ ] 运行 `pnpm install` - [ ] 在 `index.ts` 中编写自动化代码 - [ ] 本地测试:`pnpm bb dev index.ts` - [ ] 使用 curl 验证 - [ ] 部署:`pnpm bb publish index.ts` - [ ] 测试生产环境:`stagehand fn invoke <function-id>` - [ ] 保存功能 ID

## 需要代码修复(面向维护者)

**文件:** `/src/commands/functions.ts` **行号:** 146-158 **函数:** `initFunction()`

将当前的 `packageJson` 对象替换为:

```typescript const packageJson = { name, version: '1.0.0', description: `${name} function`, main: 'index.js', type: 'module', packageManager: '[email protected]', scripts: { dev: 'pnpm bb dev index.ts', publish: 'pnpm bb publish index.ts', }, dependencies: { '@browserbasehq/sdk-functions': '^0.0.5', 'playwright-core': '^1.58.0', }, devDependencies: { '@types/node': '^25.0.10', 'typescript': '^5.9.3', }, }; ```

这将消除所有新项目的 "No functions were built" 错误。

更多产品