ClawSkills logoClawSkills

stealthy-auto-browse

通过 CreepJS、BrowserScan、Pixelscan 和 Cloudflare 验证的浏览器自动化 — 零 CDP 暴露、操作系统级输入、持久指纹。当标准 b 时使用

介绍

# stealthy-auto-browse

一个在 Docker 中运行的隐身浏览器。它使用 Camoufox(一个定制的 Firefox 分支)而不是 Chromium,因此零 Chrome DevTools Protocol (CDP) 信号可供机器人检测器发现。鼠标和键盘输入通过 PyAutoGUI 在操作系统级别进行——浏览器本身不知道它正在被自动化,这意味着行为分析也无法检测到它。

## 为什么存在

标准的浏览器自动化(Playwright + Chromium, Puppeteer, Selenium)会暴露 CDP 信号,机器人检测服务(Cloudflare, DataDome, PerimeterX, Akamai)会立即捕捉到这些信号。即使使用隐身插件,CDP 协议仍然存在且可被检测。此技能通过使用 Firefox(完全没有 CDP)并在操作系统级别而不是通过浏览器的自动化 API 生成输入事件,彻底消除了这一点。

## 何时使用此技能

- 网站有机器人检测(Cloudflare 质询页面、DataDome、PerimeterX、Akamai) - 网站阻止无头浏览器或提供验证码(CAPTCHA) - 您需要一个不会被封禁的登录会话 - 另一个浏览器技能收到 403 错误或空/被阻止的响应 - 您正在抓取一个积极对抗自动化的网站

## 何时不使用此技能

- 没有机器人防护的简单获取——使用 `curl` 或 `WebFetch` - 不在乎自动化的网站——使用常规浏览器技能,设置起来更快 - 您只需要静态 HTML——使用 `curl`

## 设置

**1. 启动容器:**

```bash docker run -d -p 8080:8080 -p 5900:5900 psyb0t/stealthy-auto-browse ```

端口 8080 是 HTTP API。端口 5900 是一个 noVNC Web 查看器,您可以在其中实时观看浏览器。

**2. 设置环境变量:**

```bash export STEALTHY_AUTO_BROWSE_URL=http://localhost:8080 ```

或者通过 OpenClaw 配置(`~/.openclaw/openclaw.json`):

```json { "skills": { "entries": { "stealthy-auto-browse": { "env": { "STEALTHY_AUTO_BROWSE_URL": "http://localhost:8080" } } } } } ```

**3. 验证:** 当浏览器准备就绪时,`curl $STEALTHY_AUTO_BROWSE_URL/health` 返回 `ok`。

## 工作原理

容器运行一个虚拟 X 显示(Xvfb,分辨率 1920x1080)、Camoufox 浏览器和一个 HTTP API 服务器。您向 API 发送 JSON 命令并取回 JSON 响应。所有命令都发送到 `POST $STEALTHY_AUTO_BROWSE_URL/`,格式为 `{"action": "<name>", ...params}`。

每个响应都具有此形状:

```json { "success": true, "timestamp": 1234567890.123, "data": { ... }, "error": "only present when success is false" } ```

`data` 字段的内容因操作而异——下面有针对每个操作的文档。

## 理解两种输入模式

这是最重要的概念。有两种与页面交互的方式:

### 系统输入(不可检测)

操作:`system_click`, `mouse_move`, `mouse_click`, `system_type`, `send_key`, `scroll`

这些使用 PyAutoGUI 生成真实的操作系统级别的鼠标移动和击键。浏览器将这些接收为真正的用户输入——任何网站的 JavaScript 都无法将它们与真人区分开来。**为了隐身,请使用这些。**

系统输入使用 **视口坐标**(浏览器内容区域内的 x, y 像素位置)。从 `get_interactive_elements` 获取这些坐标。

### Playwright 输入(可检测)

操作:`click`, `fill`, `type`

这些使用 Playwright 的 DOM 自动化通过 CSS 选择器或 XPath 与元素交互。它们更快且更可靠(无需坐标计算),但它们通过浏览器的自动化层注入事件。复杂的行为分析可能会检测到这些时间模式。**当速度比隐身更重要,或者当您有选择器但没有坐标时,请使用这些。**

### 何时使用哪种

- **关键隐身的网站**(Cloudflare、登录表单、任何具有机器人检测功能的网站):始终使用系统输入。 - 网站没有积极对抗您的 **简单抓取**:Playwright 输入没问题且更容易。 - **填写表单**:使用 `system_click` 聚焦字段,然后使用 `system_type` 输入文本。这是不可检测的。使用 `fill` 更快但是可检测的。 - **点击按钮**:如果您有来自 `get_interactive_elements` 的坐标,请使用 `system_click`。如果您只有 CSS 选择器,请使用 `click`。

## 工作流

这是与页面交互的典型顺序:

1. **导航**:`goto` 加载 URL 2. **阅读页面**:`get_text` 返回所有可见文本——通常足以理解页面 3. **如果文本不清楚**:`get_html` 给您完整的 DOM 结构 4. **如果仍然困惑**:截图(`GET /screenshot/browser?whLargest=512`) 5. **查找交互元素**:`get_interactive_elements` 返回所有按钮、链接、输入及其 x,y 坐标 6. **交互**:`system_click` 点击,`system_type` 输入,`send_key` 用于 Enter/Tab/Escape 7. **等待结果**:使用 `wait_for_element` 或 `wait_for_text` 而不是休眠 8. **验证**:再次 `get_text` 以确认页面按预期更改

## 操作参考

### 导航

#### goto

导航到 URL。这是您加载页面的方式。

```json {"action": "goto", "url": "https://example.com"} {"action": "goto", "url": "https://example.com", "wait_until": "networkidle"} ```

**参数:** - `url`(必需):要导航到的 URL。 - `wait_until`(可选,默认 `"domcontentloaded"`):何时认为页面已加载。选项:`"domcontentloaded"`(DOM 已解析,快),`"load"`(所有资源已加载),`"networkidle"`(500ms 内无网络活动,最慢但最完整)。

**响应数据:** `{"url": "https://example.com/", "title": "Example Domain"}`

**注意:** 如果页面加载器匹配该 URL(请参阅页面加载器部分),则执行加载器的步骤而不是默认导航。发生这种情况时,响应将包含 `"loader": "loader name"`。

#### refresh

重新加载当前页面。

```json {"action": "refresh"} {"action": "refresh", "wait_until": "networkidle"} ```

**参数:** - `wait_until`(可选,默认 `"domcontentloaded"`):与 `goto` 相同的选项。

**响应数据:** `{"url": "https://example.com/current-page", "title": "Current Page"}`

### 系统输入(不可检测)

#### system_click

将鼠标移动到视口坐标,具有类人曲线(随机抖动,缓动加速度),然后点击。这是隐秘点击事物的主要方式。

```json {"action": "system_click", "x": 500, "y": 300} {"action": "system_click", "x": 500, "y": 300, "duration": 0.5} ```

**参数:** - `x`, `y`(必需):视口坐标——从 `get_interactive_elements` 获取。 - `duration`(可选):鼠标移动持续的时间(秒)。如果省略,则使用 0.2-0.6s 之间的随机持续时间以增加真实感。

**响应数据:** `{"system_clicked": {"x": 500, "y": 300}}`

**它与 `mouse_click` 的区别:** `system_click` 始终先移动鼠标(平滑的类人路径),然后点击。`mouse_click` 可以在没有平滑移动的情况下直接在某个位置点击,或者点击鼠标当前所在的任何位置。

#### mouse_move

将鼠标移动到视口坐标,具有类人移动(抖动,缓动曲线)但**不**点击。使用此功能悬停在元素上(以触发悬停菜单、工具提示)或模拟操作之间的自然鼠标行为。

```json {"action": "mouse_move", "x": 500, "y": 300} {"action": "mouse_move", "x": 500, "y": 300, "duration": 0.4} ```

**参数:** - `x`, `y`(必需):视口坐标。 - `duration`(可选):移动时间(秒)。如果省略,则随机 0.2-0.6s。

**响应数据:** `{"moved_to": {"x": 500, "y": 300}}`

#### mouse_click

在某个位置或当前鼠标位置点击。与 `system_click` 不同,这**不**先进行平滑鼠标移动——这是通过 PyAutoGUI 的直接点击。

```json {"action": "mouse_click"} {"action": "mouse_click", "x": 500, "y": 300} ```

**参数:** - `x`, `y`(可选):如果提供,则直接在该视口位置点击。如果省略,则点击鼠标当前所在的任何位置。

**响应数据:** `{"clicked_at": {"x": 500, "y": 300}}` 或 `{"clicked_at": "current"}`

**何时使用:** 在 `mouse_move` 之后,当您想将移动和点击分成两步时。或者当鼠标已经定位好而您只需要点击时。

#### system_type

通过真实的操作系统击键逐个字符地输入文本。每次击键都有一个随机延迟(在间隔附近抖动)以模仿人类打字速度。完全不可检测。

```json {"action": "system_type", "text": "hello world"} {"action": "system_type", "text": "hello world", "interval": 0.12} ```

**参数:** - `text`(必需):要输入的文本。必须先点击/聚焦一个输入字段。 - `interval`(可选,默认 `0.08`):击键之间的基本延迟(秒)。实际延迟在此值附近随机 +-30ms。

**响应数据:** `{"typed_len": 11}`

**重要:** 在调用 `system_type` 之前,您必须先点击输入字段(使用 `system_click` 或 `click`)。此操作输入到当前聚焦的任何内容。

#### send_key

通过操作系统级别的输入发送单个键盘键或组合键。使用此功能按 Enter 提交表单,按 Tab 在字段之间移动,按 Escape 关闭对话框,或任何组合键,如 Ctrl+A, Ctrl+C 等。

```json {"action": "send_key", "key": "enter"} {"action": "send_key", "key": "tab"} {"action": "send_key", "key": "escape"} {"action": "send_key", "key": "ctrl+a"} {"action": "send_key", "key": "ctrl+shift+t"} ```

**参数:** - `key`(必需):键名或带 `+` 分隔符的组合键。键名遵循 PyAutoGUI 命名:`enter`, `tab`, `escape`, `backspace`, `delete`, `up`, `down`, `left`, `right`, `home`, `end`, `pageup`, `pagedown`, `f1`-`f12`, `ctrl`, `alt`, `shift`, `space` 等。

**响应数据:** `{"send_key": "enter"}`

#### scroll

使用鼠标滚轮滚动页面。生成真实的操作系统级别滚动事件。

```json {"action": "scroll", "amount": -3} {"action": "scroll", "amount": 5, "x": 500, "y": 300} ```

**参数:** - `amount`(可选,默认 `-3`):滚动量。**负数 = 向下滚动**,正数 = 向上滚动。每个单位大约是鼠标滚轮的一次“点击”。 - `x`, `y`(可选):如果提供,先将鼠标移动到这些视口坐标,然后滚动。对于在特定可滚动元素内滚动而不是整个页面很有用。

**响应数据:** `{"scrolled": -3}`

### Playwright 输入(可检测)

这些更快、更方便,但使用 Playwright 的 DOM 事件注入,这可以被复杂的行为分析检测到。

#### click

通过 CSS 选择器或 XPath 点击元素。Playwright 在 DOM 中查找元素,如果需要则将其滚动到视图中,然后调度点击事件。

```json {"action": "click", "selector": "#submit-btn"} {"action": "click", "selector": "button.primary"} {"action": "click", "selector": "xpath=//button[@id='submit-btn']"} ```

**参数:** - `selector`(必需):CSS 选择器或 XPath(前缀为 `xpath=`)。

**响应数据:** `{"clicked": "#submit-btn"}`

**何时使用而不是 system_click:** 当您有选择器但不想费心获取坐标时。当元素可能四处移动且坐标不可靠时。当隐身不重要时。

#### fill"}

通过选择器填充输入字段。首先清除任何现有内容,然后设置值。这是填充表单的最快方法,但可以检测到,因为它不会生成单独的击键事件。

```json {"action": "fill", "selector": "input[name='email']", "value": "[email protected]"} ```

**参数:** - `selector` (必需):输入元素的 CSS 选择器或 XPath。 - `value` (必需):要填充的文本。

**响应数据:** `{"filled": "input[name='email']"}` # stealthy-auto-browse

通过 Playwright(而非操作系统)逐个字符地向元素中输入文本。每次击键都有可配置的延迟。这是介于 `fill`(瞬间完成但明显是自动化的)和 `system_type`(操作系统级别,无法检测)之间的折中方案。其打字模式比 `fill` 更逼真,但仍然通过 Playwright 的事件系统实现。

```json {"action": "type", "selector": "#search", "text": "query", "delay": 0.05} ```

**参数:** - `selector` (必需):元素的 CSS 选择器或 XPath。 - `text` (必需):要输入的文本。 - `delay` (可选,默认 `0.05`):击键之间的延迟(秒)。

**响应数据:** `{"typed": "#search"}`

### Screenshots

截图是 GET 请求(不是 POST 操作)。

#### GET /screenshot/browser

将浏览器视口捕获为 PNG 图像。这是用户看到的页面样子。

```bash curl -s "$STEALTHY_AUTO_BROWSE_URL/screenshot/browser?whLargest=512" -o screenshot.png ```

**务必调整截图大小** 以避免生成过大的图像。调整大小的查询参数(均为可选):

| Parameter | What it does | |-----------|-------------| | `whLargest=512` | 缩放以使最大尺寸为 512px,保持宽高比。**默认使用此选项。** | | `width=800` | 缩放至 800px 宽,保持宽高比 | | `height=300` | 缩放至 300px 高,保持宽高比 | | `width=400&height=400` | 强制精确的 400x400 尺寸 |

#### GET /screenshot/desktop

使用 `scrot` 捕获整个虚拟桌面(包括窗口镶边、任务栏等)。调整大小参数同上。当需要查看浏览器视口之外的内容时很有用。

```bash curl -s "$STEALTHY_AUTO_BROWSE_URL/screenshot/desktop?whLargest=512" -o desktop.png ```

### Page Inspection

#### get_interactive_elements

扫描页面并返回每个交互元素(按钮、链接、输入框、选择框、文本区域等)及其视口坐标。这是你查找可点击内容及其位置的方法。

```json {"action": "get_interactive_elements"} {"action": "get_interactive_elements", "visible_only": true} ```

**参数:** - `visible_only` (可选,默认 `true`):仅返回当前屏幕上可见的元素。

**响应数据:** ```json { "count": 5, "elements": [ { "i": 0, "tag": "button", "id": "submit-btn", "text": "Submit", "selector": "#submit-btn", "x": 400, "y": 250, "w": 120, "h": 40, "visible": true }, { "i": 1, "tag": "input", "id": null, "text": "", "selector": "input[name='email']", "x": 300, "y": 180, "w": 250, "h": 35, "visible": true } ] } ```

`x`、`y` 是元素的中心点 —— 直接将这些传递给 `system_click`。`selector` 可以与 Playwright 操作(如 `click` 或 `fill`)一起使用。`w`、`h` 提供元素的尺寸。

**这是你了解页面上可以与哪些元素进行交互的主要工具。** 在点击任何内容之前调用此方法。

#### get_text

返回页面主体的所有可见文本内容。文本将被截断为 10,000 个字符。

```json {"action": "get_text"} ```

**响应数据:** `{"text": "Page title Some content here...", "length": 1234}`

这通常是在导航之后首先要调用的方法 —— 它可以告诉你页面上有什么内容,而无需截图。

#### get_html

返回当前页面的完整 HTML 源代码。

```json {"action": "get_html"} ```

**响应数据:** `{"html": "<!DOCTYPE html>...", "length": 45678}`

当 `get_text` 提供的结构不足以理解页面布局,或者你需要在 DOM 中查找特定元素时使用。

#### eval

在页面上下文中执行任意 JavaScript 并返回结果。表达式通过 `page.evaluate()` 求值。

```json {"action": "eval", "expression": "document.title"} {"action": "eval", "expression": "document.querySelectorAll('a').length"} {"action": "eval", "expression": "JSON.stringify(performance.timing)"} ```

**参数:** - `expression` (必需):要计算的 JavaScript 表达式。必须返回可 JSON 序列化的值。

**响应数据:** `{"result": "Example Domain"}` — 结果是该表达式返回的任何内容。

### Wait Conditions

使用这些方法代替 `sleep` 来等待页面内容。它们更可靠,因为它们等待的是特定条件,而不是任意的时间。

#### wait_for_element

等待与 CSS 选择器或 XPath 匹配的元素达到特定状态(可见、隐藏、附加到 DOM、从 DOM 分离)。

```json {"action": "wait_for_element", "selector": "#results", "timeout": 10} {"action": "wait_for_element", "selector": "xpath=//div[@class='loaded']", "timeout": 15} {"action": "wait_for_element", "selector": ".spinner", "state": "hidden", "timeout": 10} ```

**参数:** - `selector` (必需):CSS 选择器或 XPath(使用 `xpath=` 前缀)。 - `state` (可选,默认 `"visible"`):等待的状态。选项:`"visible"`(已渲染且未隐藏)、`"hidden"`(不可见)、`"attached"`(在 DOM 中,无论可见性如何)、`"detached"`(已从 DOM 中移除)。 - `timeout` (可选,默认 `30`):最大等待时间(秒)。如果超时则抛出错误。

**响应数据:** `{"selector": "#results", "state": "visible"}`

#### wait_for_text

等待特定文本出现在页面主体的任意位置。

```json {"action": "wait_for_text", "text": "Search results", "timeout": 10} ```

**参数:** - `text` (必需):要查找的精确文本(对 `document.body.innerText` 进行子字符串匹配)。 - `timeout` (可选,默认 `30`):最大等待时间(秒)。

**响应数据:** `{"text": "Search results", "found": true}`

#### wait_for_url

等待页面 URL 与模式匹配。适用于表单提交或重定向之后。

```json {"action": "wait_for_url", "url": "**/dashboard", "timeout": 10} {"action": "wait_for_url", "url": "https://example.com/success*", "timeout": 15} ```

**参数:** - `url` (必需):要匹配的 URL 模式。支持 `*`(除 `/` 外的任意字符)和 `**`(包括 `/` 在内的任意字符)glob 模式。也可以是用于精确匹配的完整 URL。 - `timeout` (可选,默认 `30`):最大等待时间(秒)。

**响应数据:** `{"url": "https://example.com/dashboard"}`

#### wait_for_network_idle

等待直到 500 毫秒内没有进行中的网络请求。适用于在初始页面加载后动态加载内容的页面。

```json {"action": "wait_for_network_idle", "timeout": 30} ```

**参数:** - `timeout` (可选,默认 `30`):最大等待时间(秒)。

**响应数据:** `{"idle": true}`

### Tab Management

浏览器可以打开多个标签页。一次有一个“活动”标签页 —— 所有操作都在活动标签页上执行。

#### list_tabs

返回所有打开的标签页及其 URL 以及哪一个处于活动状态。

```json {"action": "list_tabs"} ```

**响应数据:** ```json { "count": 2, "tabs": [ {"index": 0, "url": "https://example.com/", "active": false}, {"index": 1, "url": "https://other.com/", "active": true} ] } ```

#### new_tab

打开一个新的浏览器标签页。可以选择将其导航到某个 URL。新标签页将成为活动标签页。

```json {"action": "new_tab"} {"action": "new_tab", "url": "https://example.com"} ```

**参数:** - `url` (可选):在新标签页中导航到的 URL。 - `wait_until` (可选,默认 `"domcontentloaded"`):与 `goto` 相同。

**响应数据:** `{"index": 1, "url": "https://example.com/"}`

#### switch_tab

通过索引(从 0 开始)切换活动标签页。所有后续操作都将在此标签页上执行。

```json {"action": "switch_tab", "index": 0} ```

**参数:** - `index` (必需):来自 `list_tabs` 的标签页索引。

**响应数据:** `{"index": 0, "url": "https://example.com/"}`

#### close_tab

关闭一个标签页。关闭后,最后一个剩余的标签页将成为活动标签页。

```json {"action": "close_tab"} {"action": "close_tab", "index": 1} ```

**参数:** - `index` (可选):要关闭的标签页索引。如果省略,则关闭当前活动标签页。

**响应数据:** `{"closed": true, "remaining": 1}`

### Dialog Handling

浏览器有模态对话框(alert、confirm、prompt)。默认情况下,对话框会自动接受(点击 OK)。如果你需要关闭对话框或为 prompt 提供文本,请使用 `handle_dialog`。

#### handle_dialog

如果要关闭对话框或提供 prompt 文本,**请在触发对话框的操作之前调用**。如果不调用此方法,对话框将被自动接受(点击 OK)。

```json {"action": "handle_dialog", "accept": true} {"action": "handle_dialog", "accept": false} {"action": "handle_dialog", "accept": true, "text": "my response"} ```

**参数:** - `accept` (可选,默认 `true`):`true` 点击 OK/Accept,`false` 点击 Cancel/Dismiss。 - `text` (可选):prompt 对话框的响应文本。对于 alert/confirm 忽略。

**响应数据:** `{"configured": {"accept": true, "text": null}}`

**示例 — 处理确认对话框:** ```bash # Step 1: Tell the browser to accept the next dialog curl -X POST $API -H 'Content-Type: application/json' -d '{"action": "handle_dialog", "accept": true}' # Step 2: Now click the button that triggers the confirm curl -X POST $API -H 'Content-Type: application/json' -d '{"action": "system_click", "x": 300, "y": 200}' ```

#### get_last_dialog

返回有关最近出现的对话框的信息。

```json {"action": "get_last_dialog"} ```

**响应数据:** ```json { "dialog": { "type": "confirm", "message": "Are you sure you want to delete this?", "default_value": "", "buttons": ["ok", "cancel"] } } ```

如果尚未出现对话框,则返回 `{"dialog": null}`。`type` 字段是以下之一:`"alert"`、`"confirm"`、`"prompt"`、`"beforeunload"`。

### Cookies

#### get_cookies

返回浏览器上下文的所有 cookie,或特定 URL 的 cookie。

```json {"action": "get_cookies"} {"action": "get_cookies", "urls": ["https://example.com"]} ```

**参数:** - `urls` (可选):用于过滤 cookie 的 URL 数组。如果省略,则返回所有 cookie。

**响应数据:** ```json { "count": 3, "cookies": [ {"name": "session", "value": "abc123", "domain": ".example.com", "path": "/", "httpOnly": true, "secure": true, ...} ] } ```

#### set_cookie

在浏览器上下文中设置 cookie。

```json {"action": "set_cookie", "name": "session", "value": "abc123", "url": "https://example.com"} {"action": "set_cookie", "name": "pref", "value": "dark", "domain": ".example.com", "path": "/", "httpOnly": false, "secure": true} ```

**参数:** 任何标准 cookie 字段 —— `name`、`value`、`url`、`domain`、`path`、`httpOnly`、`secure`、`sameSite`、`expires`。至少需要 `name`、`value` 以及 `url` 或 `domain` 之一。

**响应数据:** `{"set": "session"}`

#### delete_cookies

从浏览器上下文中清除所有 cookie。

```json {"action": "delete_cookies"} ```

**响应数据:** `{"cleared": true}`

### Storage

访问页面的 localStorage 和 sessionStorage。这些是针对每个源的 —— 你必须在正确的页面上才能访问存储。

#### get_storage

以键值对象的形式返回 localStorage 或 sessionStorage 中的所有项。

```json {"action": "get_storage", "type": "local"} {"action": "get_storage", "type": "session"} ```

**参数:** - `type` (可选,默认 `"local"`):`"local"` 表示 localStorage,`"session"` 表示 sessionStorage。

**响应数据:** `{"items": {"theme": "dark", "lang": "en"}, "type": "local"}`

#### set_storage

在 localStorage 或 sessionStorage 中设置单个键值对。

```json {"action": "set_storage", "type": "local", "key": "theme", "value": "dark"} ```

**参数:** - `type` (可选,默认 `"local"`):`"local"` 或 `"session"`。 - `key` (必需):存储键。 - `value` (必需):存储值(字符串)。

**响应数据:** `{"set": "theme", "type": "local"}`

#### clear_storage

清除 localStorage 或 sessionStorage 中的所有项。

```json {"action": "clear_storage", "type": "local"} {"action": "clear_storage", "type": "session"} ```

**响应数据:** `{"cleared": "local"}`

### Downloads

浏览器会自动跟踪由页面交互(点击下载链接、返回文件的表单提交等)触发的文件下载。

#### get_last_download

返回有关最近下载文件的信息。

```json {"action": "get_last_download"} ```

**响应数据:** ```json { "download": { "url": "https://example.com/file.pdf", "filename": "file.pdf", "path": "/tmp/playwright-downloads/abc123/file.pdf" } } ```

如果尚未下载任何内容,则返回 `{"download": null}`。`path` 是容器内保存文件的本地路径。`filename` 是服务器建议的下载名称。

### Uploads

#### upload_file

在不打开操作系统文件选择器的情况下,以编程方式在 `<input type="file">` 元素上设置文件。文件必须存在于容器内部 —— 如有必要,请使用 `docker cp` 复制文件。

```json {"action": "upload_file", "selector": "#file-input", "file_path": "/tmp/document.pdf"} ```

**参数:** - `selector` (必填): 文件输入元素的 CSS 选择器。 - `file_path` (必填): 容器内文件的绝对路径。

**响应数据:** `{"selector": "#file-input", "file": "document.pdf", "size": 12345}`

**注意:** 设置文件后,您仍需提交表单(点击提交按钮)才能使上传实际生效。

### 网络日志

捕获页面发出的所有 HTTP 请求和响应。用于调试、查找页面调用的 API 端点,或验证某些资源是否已加载。

#### enable_network_log

开始记录活动页面的所有 HTTP 请求和响应。

```json {"action": "enable_network_log"} ```

**响应数据:** `{"enabled": true}`

#### disable_network_log

停止记录网络活动。已捕获的条目将保留。

```json {"action": "disable_network_log"} ```

**响应数据:** `{"enabled": false}`

#### get_network_log

返回自启用日志记录(或上次清除)以来捕获的所有网络条目。

```json {"action": "get_network_log"} ```

**响应数据:** ```json { "count": 4, "log": [ {"type": "request", "url": "https://api.example.com/data", "method": "GET", "resource_type": "fetch", "timestamp": 1234567890.123}, {"type": "response", "url": "https://api.example.com/data", "status": 200, "timestamp": 1234567890.456}, {"type": "request", "url": "https://cdn.example.com/style.css", "method": "GET", "resource_type": "stylesheet", "timestamp": 1234567890.789}, {"type": "response", "url": "https://cdn.example.com/style.css", "status": 200, "timestamp": 1234567890.999} ] } ```

每个条目要么是 `"request"`(请求),要么是 `"response"`(响应)。请求包含 `method`(方法)和 `resource_type`(资源类型,如 fetch、document、stylesheet、script、image 等)。响应包含 `status`(状态)代码。

#### clear_network_log

删除所有已捕获的网络条目,但如果日志记录处于开启状态,则保持开启。

```json {"action": "clear_network_log"} ```

**响应数据:** `{"cleared": true}`

### 滚动

#### scroll_to_bottom

使用 JavaScript `window.scrollBy()` 从上到下滚动整个页面。每次滚动一个视口高度,滚动之间有固定的延迟。到达底部(滚动位置停止变化)后,它会滚动回顶部。用于触发延迟加载的内容。

```json {"action": "scroll_to_bottom"} {"action": "scroll_to_bottom", "delay": 0.6} ```

**参数:** - `delay` (可选,默认 `0.4`): 每个滚动步骤之间等待的秒数。

**响应数据:** `{"scrolled": "bottom"}`

#### scroll_to_bottom_humanized

与 `scroll_to_bottom` 相同,但使用真实的操作系统级鼠标滚轮滚动(通过 PyAutoGUI),并带有随机的滚动量和抖动的延迟,看起来像人类的滚动操作。行为分析无法检测。

```json {"action": "scroll_to_bottom_humanized"} {"action": "scroll_to_bottom_humanized", "min_clicks": 3, "max_clicks": 8, "delay": 0.7} ```

**参数:** - `min_clicks` (可选,默认 `2`): 每个滚动步骤的最小鼠标滚轮点击次数。 - `max_clicks` (可选,默认 `6`): 每个滚动步骤的最大鼠标滚轮点击次数。每次都会在最小值和最大值之间选择一个随机值。 - `delay` (可选,默认 `0.5`): 滚动步骤之间的基本延迟。实际延迟会有 ±30% 的抖动。

**响应数据:** `{"scrolled": "bottom_humanized"}`

### 显示

#### calibrate

重新计算视口坐标(`get_interactive_elements` 返回的内容)与屏幕坐标(PyAutoGUI 使用的内容)之间的映射。浏览器有窗口装饰(标题栏、地址栏),会使视口偏离屏幕原点。

```json {"action": "calibrate"} ```

**响应数据:** `{"window_offset": {"x": 0, "y": 74}}`

**何时调用此函数:** 在进入/退出全屏、调整浏览器窗口大小后,或者如果 `system_click` 坐标似乎有偏差时。偏移量在启动时是自动计算的,因此您很少需要调用此函数。

#### get_resolution

返回虚拟显示分辨率(来自 XVFB_RESOLUTION 环境变量)。

```json {"action": "get_resolution"} ```

**响应数据:** `{"width": 1920, "height": 1080}`

#### enter_fullscreen / exit_fullscreen

切换浏览器全屏模式(隐藏地址栏和窗口装饰)。在全屏模式下,视口占据整个屏幕,因此坐标映射方式不同。

```json {"action": "enter_fullscreen"} {"action": "exit_fullscreen"} ```

**响应数据:** `{"fullscreen": true, "changed": true}` — 如果已经处于请求的状态,则 `changed` 为 `false`。

**重要提示:** 进入/退出全屏后调用 `calibrate` 以更新坐标映射。

### 实用工具

#### ping

健康检查,返回当前页面 URL。用于验证 API 是否正在响应以及浏览器是否存活。

```json {"action": "ping"} ```

**响应数据:** `{"message": "pong", "url": "https://example.com/"}`

#### sleep

暂停执行指定持续时间。在等待页面内容时,优先使用 `wait_for_element` 或 `wait_for_text` —— 仅在固定计时需求时使用 `sleep`。

```json {"action": "sleep", "duration": 2} ```

**参数:** - `duration` (可选,默认 `1`): 休眠的秒数。

**响应数据:** `{"slept": 2}`

#### close

关闭浏览器。此后容器将停止运行。

```json {"action": "close"} ```

**响应数据:** `{"message": "closing"}`

### 状态端点 (GET)

#### GET /state

返回当前浏览器状态。

```bash curl -s "$STEALTHY_AUTO_BROWSE_URL/state" ```

**响应:** ```json { "status": "ready", "url": "https://example.com/", "title": "Example Domain", "window_offset": {"x": 0, "y": 74} } ```

#### GET /health

简单的健康检查。当 API 准备就绪时,以纯文本形式返回 `ok`。

```bash curl -s "$STEALTHY_AUTO_BROWSE_URL/health" ```

## 容器选项

```bash # Custom display resolution docker run -d -p 8080:8080 -e XVFB_RESOLUTION=1280x720 psyb0t/stealthy-auto-browse

# Match timezone to your IP's geographic location (important for stealth — mismatched # timezone is a common bot detection signal) docker run -d -p 8080:8080 -e TZ=Europe/Bucharest psyb0t/stealthy-auto-browse

# Route browser traffic through an HTTP proxy docker run -d -p 8080:8080 -e PROXY_URL=http://user:pass@proxy:8888 psyb0t/stealthy-auto-browse

# Persistent browser profile — cookies, sessions, and fingerprint survive container restarts docker run -d -p 8080:8080 -v ./profile:/userdata psyb0t/stealthy-auto-browse

# Open a URL automatically on startup docker run -d -p 8080:8080 psyb0t/stealthy-auto-browse https://example.com ```

## 页面加载器 (URL 触发自动化)

页面加载器类似于 **Greasemonkey/Tampermonkey 用户脚本**,但是用于 HTTP API。您可以定义一组操作,每当浏览器导航到匹配的 URL 时自动运行。无需在每次访问网站时手动发送一系列命令,而是将其作为 YAML 文件编写一次,由容器处理。

这适用于以下情况:移除 Cookie 弹窗、关闭覆盖层、等待动态内容、在抓取前清理页面,或者您原本每次需要手动进行的任何重复性设置。

### 它们如何工作

1. 您创建定义 URL 模式和步骤列表的 YAML 文件 2. 将这些文件挂载到容器的 `/loaders` 位置 3. 每当 `goto` 导航到匹配加载器模式的 URL 时,加载器的步骤会自动运行,而不是默认导航

**这些步骤与 HTTP API 的操作完全相同。** 您可以通过 `POST /` 发送的每个操作(goto、eval、click、system_click、sleep、scroll、wait_for_element 等)都可以作为加载器步骤使用。相同的名称,相同的参数。

### 设置

```bash docker run -d -p 8080:8080 -p 5900:5900 \ -v ./my-loaders:/loaders \ psyb0t/stealthy-auto-browse ```

### 加载器格式

```yaml name: Human-readable name for this loader match: domain: example.com # Exact hostname match (www. is stripped automatically) path_prefix: /articles # URL path must start with this regex: "article/\d+" # Full URL must match this regex steps: - action: goto # Same actions as the HTTP API url: "${url}" # ${url} is replaced with the original URL wait_until: networkidle - action: eval expression: "document.querySelector('.cookie-banner')?.remove()" - action: wait_for_element selector: "#main-content" timeout: 10 ```

### 匹配规则

所有匹配字段都是**可选的**,但至少需要一个。如果您指定了多个字段,则**所有**字段都必须匹配才能触发加载器:

- **`domain`**: 精确的主机名。比较前会剥离双方的 `www.`,因此 `domain: example.com` 也匹配 `www.example.com`。 - **`path_prefix`**: URL 路径必须以此字符串开头。`path_prefix: /blog` 匹配 `/blog`、`/blog/post-1`、`/blog/archive` 等。 - **`regex`**: 全 URL 将根据此正则表达式进行测试。

### `${url}` 占位符

在步骤中的任何字符串值内,`${url}` 会被传递给 `goto` 的原始 URL 替换。这允许您使用自定义等待设置导航到该 URL,或将其传递给 JavaScript:

```yaml steps: - action: goto url: "${url}" wait_until: networkidle - action: eval expression: "console.log('Loaded:', '${url}')" ```

### 实用示例:清理抓取

假设您正在抓取一个新闻网站,该网站有 Cookie 弹窗、新闻简报模态框和延迟加载的内容。如果没有加载器,您需要在每次 `goto` 后发送 5 个以上的命令。使用加载器:

```yaml # loaders/news_site.yaml name: News Site Cleanup match: domain: news-site.com steps: # Navigate with full network wait so everything loads - action: goto url: "${url}" wait_until: networkidle

# Wait for the main content to be there - action: wait_for_element selector: "article" timeout: 10

# Kill the cookie popup - action: eval expression: "document.querySelector('.cookie-consent')?.remove()"

# Kill the newsletter modal - action: eval expression: "document.querySelector('.newsletter-overlay')?.remove()"

# Scroll to trigger lazy-loaded images - action: scroll_to_bottom delay: 0.3

# Small pause for everything to settle - action: sleep duration: 1 ```

现在,当您 `goto` `news-site.com` 上的任何 URL 时,所有这些都会自动发生。您的响应包括 `"loader": "News Site Cleanup"`,因此您知道它已触发。

### 加载器触发时的响应

```json { "success": true, "data": { "loader": "News Site Cleanup", "steps_executed": 6, "last_result": { "success": true, "timestamp": 1234567890.456, "data": { "slept": 1 } } } } ```

## 预装扩展

浏览器预装了以下扩展:

- **uBlock Origin**: 广告和跟踪器拦截 - **LocalCDN**: 本地提供常见的 CDN 资源以防止跟踪 - **ClearURLs**: 从 URL 中剥离跟踪参数 - **Consent-O-Matic**: 自动处理 Cookie 同意弹窗(点击“全部拒绝”或最小同意)

## 示例:完整登录流程 (不可检测)

```bash API=$STEALTHY_AUTO_BROWSE_URL

# Navigate to login page curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "goto", "url": "https://example.com/login"}'

# See what's on the page curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "get_text"}'

# Find all interactive elements and their coordinates curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "get_interactive_elements"}'

# Click the email field (coordinates from get_interactive_elements) curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "system_click", "x": 400, "y": 200}'

# Type email with human-like keystrokes curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "system_type", "text": "[email protected]"}'

# Tab to password field curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "send_key", "key": "tab"}'

# Type password curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "system_type", "text": "secretpassword"}'

# Press Enter to submit curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "send_key", "key": "enter"}'

# Wait for redirect to dashboard curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "wait_for_url", "url": "**/dashboard", "timeout": 15}'

# Verify we're logged in curl -s -X POST $API -H 'Content-Type: application/json' \ -d '{"action": "get_text

更多产品