# Webfetch Plus

> 使用 Browser 抓取普通 WebFetch 失败的网页；支持 --stealth、通过 `bin/wfp-human.sh` 人工处理反爬。当用户明确提到 webfetch-plus、或 WebFetch 失败、或页面有验证码/WAF 时使用

- Skill: `gitbyeos/webfetch-plus` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add gitbyeos/webfetch-plus`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gitbyeos/webfetch-plus/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: gitbyeos (https://skillmd.com/u/gitbyeos)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gitbyeos/webfetch-plus

---


# WebFetch Plus

## 执行原则

- 把当前 skill 路径设置给 `WFP_PATH`
- 任何命令执行，必须先 `cd "$WFP_PATH"`
- 普通抓取可用管道，跳过安全提示
- 管道输入按空白字符拆分参数；URL 和参数值不得包含空格
- 脚本按 `runtime/node/package.json` 自动安装 CloakBrowser 与 Playwright；首次安装会使用进程锁，其他调用会等待完成
- **Agent / 非 TTY 环境不要运行人工验证流程**；脚本会从 `/tmp/wfp-states/*.json` 自动选择匹配目标域名的最新会话，找不到时才报告需要交互验证
- 默认 state 路径的唯一权威是 runtime 的 `getDefaultStatePath()`：`/tmp/wfp-states/<hostname-clean>.json`（`hostname-clean` 为 URL hostname 中非字母数字和连字符替换为 `-` 的结果）。不要在调用方自行猜测或另行实现该命名。

## 外部副作用

- 访问传入的 HTTP(S) URL，并启动本地浏览器；`--stealth` 首次使用可能下载 Chromium
- 正文默认写入 `/tmp/wfp-tasks/`
- 失败证据始终写入 `/tmp/wfp-evidence/`，不受 `--output-dir` 影响
- `--save-state` 会写入 Playwright `storageState` JSON（cookies / localStorage）；文件可能含登录态，勿提交到 git
- `--out` 和 `--output-dir` 会写入调用方指定路径；不要让多个调用共用同一 `--out` 路径

## 抓取网页

管道传入 URL 和参数：

```bash
echo "https://example.com" | bash "bin/wfp.sh"
```

也可直接传参：

```bash
bash bin/wfp.sh "https://example.com" --wait 3000
```

脚本在 stdout 输出正文文件路径。

### 动态页面超时

动态页面长期保持连接时，`networkidle` 可能连续导航超时。此时改用 `--wait-until domcontentloaded`，并按页面实际加载时间提高 `--timeout`；例如：

```bash
bash bin/wfp.sh 'https://example.com' --wait-until domcontentloaded --timeout 90000
```

## 验证码、登录与会话复用

遇到人机验证或登录时，在交互 TTY 中运行启动器，完成验证后按回车。启动器会以 headed + stealth 模式保存该目标的默认会话；未显式传入 `--wait-until` 时使用 `domcontentloaded`，显式值优先：

```bash
bash bin/wfp-human.sh 'https://example.com/protected-page'
```

后续普通抓取会复用会话：`--state` 优先；未提供时，脚本从 `/tmp/wfp-states/*.json` 中验证 cookie 域名或 localStorage origin 与目标域名匹配，并采用最新文件。会话过期再次被拦时，重新运行启动器即可覆盖默认 state。

风控拦截且没有可复用会话时，stderr 会输出一条带实际目标 URL 的可复制启动器命令。自动任务和 Agent 不运行启动器，只使用已有或自动发现的会话。非 TTY 的 macOS 调用会自动打开 Terminal。

## 参数

| 参数 | 说明 |
|------|------|
| `--out <path>` | 直接写入指定文件；同一路径不可并发使用 |
| `--output-dir <path>` | 将唯一正文文件直接写入该目录 |
| `--visible` | 显式打开浏览器窗口（headed）；默认隐式 headless |
| `--state <path>` | 显式加载 Playwright storageState JSON（优先于自动发现；文件必须已存在） |
| `--save-state <path>` | 抓取成功后保存 storageState JSON |
| `--wait <ms>` | 页面加载后额外等待时间 |
| `--timeout <ms>` | 页面导航超时时间，默认 45000 |
| `--wait-until <load|domcontentloaded|networkidle>` | 页面加载完成条件，默认 `networkidle` |
| `--retries <1-3>` | 最多尝试次数，默认 3；首次发现 WAF 时后续自动 `--stealth` |
| `--selector <css>` | 只抽取指定 CSS 选择器内容 |
| `--format <markdown|text|html>` | 输出格式，默认 `markdown`；需要链接时用 `html` |
| `--stealth` | CloakBrowser 定制 Chromium 增强反 WAF |

人工验证不属于公开 `wfp.sh` 参数。仅通过 `bash bin/wfp-human.sh '<url>' [fetch options]` 启动；启动器会强制 headed 与 stealth，并保存默认 state。它拒绝 `--state`、`--save-state`、`--out`、`--visible` 等受管理参数。

## 执行流程

```text
传入 URL →（优先 --state，否则自动匹配本地会话）→ 抓取
  → 若 WAF：自动 stealth 重试 → 仍失败则提示 wfp-human.sh 启动器
  → 启动器内部启用人工验证 → 等人回车 → reload 目标 URL → 保存 state
  → 成功则可选 --save-state → stdout 输出正文路径
```

**成功判定**：stdout 输出唯一正文路径
- 空标题 + 极短正文 + 大 HTML / 可见挑战文案 → 失败
- 已有真实标题且正文够长时，不因 HTML 中残留挑战相关 CSS 或文案误判失败

**失败处理契约**：
1. 非零退出；stderr 输出 failure summary 路径，evidence 的 `suggestion` 保留同一建议
2. 首次 WAF → 剩余重试自动 `--stealth`
3. stealth 仍失败且没有匹配的 `/tmp/wfp-states/*.json` 会话 → 最终 stderr 只输出一条带实际目标 URL 的完整可复制启动器命令：`bash bin/wfp-human.sh '<url>'`
4. 人工挑战同页可多次回车；挑战未消失会提示再试，不会立刻关页重开三轮

**通用规则**：
- 默认 headless；启动器内部人工挑战强制 headed，且不隐藏启动窗口
- 启动器不自动解滑块
- 无人值守任务只用 `--state`，不要运行启动器

