chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)

- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
This commit is contained in:
admin committed 2026-10-10 23:13:22 +08:00
1 parent 30b46dbd0c
commit c1b5e4d966
735 files changed
+153192 -2415

No files matched your search

@@ -0,0 +1,97 @@
---
name: web-fetch-antibot
description: 网页抓取遇到反爬(JS 渲染壳页、一次性令牌、人机验证页)时的判断与止损方法。当出现以下情形时触发:WebFetch 返回空或 fetch failed、curl 拿到的 HTML 里目标内容一个字符都没有、正文代理返回"unusual traffic / verify you are human"、无头浏览器被踢到 /sorry/ 或 /cdn-cgi/challenge-platform/、用户给出 share.google/aimode/* 或类似"分享链接"。核心 = 三个判据快速归因 + 硬止损规则(同一 URL 换 2 种方式失败即停手,改向用户索取原文),避免在无解目标上烧掉工具预算。
version: 1.0.0
updated_at: 2026-09-19
agent_created: true
---
# web-fetch-antibot — 反爬页面的归因与止损
抓不到网页时,问题通常不在"网络通不通",而在**页面属于哪一类**。本技能给的是**判断顺序**和**何时停手**,不是"再多试几种 UA"。
## 一、先分类,再动手(三层判据)
按顺序做,命中即停在该层、按该层处置。
**判据 A — JS 渲染壳页(最有价值的一条)**
```bash
curl -sL -o page.html -w "%{http_code} %{size_download}\n" <URL>
grep -c "<目标关键词>" page.html # 期望 >0
grep -o "<title>[^<]*</title>" page.html
```
- 关键词计数 = **0** 且 `size_download` 不小(几十 KB 以上)⇒ 壳页,正文由前端 XHR 现拉。
- 附带特征:`<title>` 是通用名(`Google Search`、`Just a moment...`)、无 `AF_initDataCallback` / `__NEXT_DATA__` / `window.__INITIAL_STATE__` 这类结构化内联数据、脚本是混淆过的自解压串。
- 处置:**换 UA / 换 Googlebot UA 无用**(实测过,字节数只差几百,命中数仍为 0)。直接进判据 B。
**判据 B — 无头浏览器能否过**
```bash
agent-browser open <URL> ; agent-browser wait 15000 ; agent-browser get url
```
- 落在 `/sorry/index`、`/cdn-cgi/challenge-platform/`、`/httpservice/retry/enablejs` ⇒ **人机验证或 JS 强制**。
- 一手经验:**headless 基本无解**。`navigator.webdriver`、CDP 注入痕迹、WebGL / 字体指纹在 Google、Cloudflare 侧是一等信号,不是靠 `--headed` 或换 UA 能绕的。
- 注意首启成本:冷启 30–45 s,前台直跑会撞超时 ⇒ 后台跑 + `sleep` 后读文件;收尾务必 `agent-browser close --all`。
**判据 C — 正文代理(r.jina.ai 等)返回什么**
- 返回**验证页原文**(含 `unusual traffic`、`verify you are human`、`About this page`)⇒ 说明是 **IP / 指纹级拦截**,不是渲染问题 ⇒ 代理路线也废。
- 顺带信息:Jina 的返回里会带它自己的出口 IP(实测 `2600:1900:0:4006::f01`,Google Cloud 段)—— 数据中心 IP 本身就是降权因子。
## 二、硬止损规则(本技能的主要价值)
> **同一 URL 换 2 种方式仍未拿到正文 ⇒ 停手,直接向用户索取"粘贴原文"。**
来源:2026-09-19 一个 `share.google/aimode/*` 链接,试了 6 条路全部失败(WebFetch / curl 直连 / curl 经代理 / curl 换 Googlebot UA / r.jina.ai / 无头 Chromium),累计十余次工具调用,最终仍然只能请用户粘贴。**换 UA 和换客户端的边际收益在这个组合面前是 0。**
允许的例外:待抓页是你自己项目的(有凭据 / 有源站),那属于部署问题不是反爬问题,另走排障。
## 三、本机环境陷阱(沙箱内必踩,会伪装成"网络不通")
- 沙箱会注入 `HTTPS_PROXY=127.0.0.1:63854`,那是**沙箱内代理**,对 Google 返回 `502 CONNECT tunnel failed`。
- 沙箱内 `curl https://www.google.com` 返回 `000`,**不代表网络不通**。
- 需要真实外网时:`dangerouslyDisableSandbox: true` + **显式**指定用户自己的代理 `HTTPS_PROXY=http://127.0.0.1:10800`(与 `git config --global http.proxy` 同值)。
- 沙箱内 `PATH` 被 shim 重置 ⇒ 用完整 POSIX 路径 `/e/ProgramData/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin`。
- `agent-browser` 若报 command not found:装到托管 Node 下 —— `npm install -g agent-browser`(用 `E:\ProgramData\.workbuddy\binaries\node\versions\22.22.2-3`);Chrome 本体插件已预先下到 `~/.agent-browser/browsers/`。
## 四、案例锚点:Google AI Mode(Gemini)分享链接
技术链路(逐跳实测):
```
https://share.google/aimode/<id>
302 → https://www.google.com/share.google?q=aimode/<id>
301 → https://www.google.com/search?smstk=<token>&smstidx=3&q=<urlencoded>&udm=50
&csuir=1&aep=34&kgs=<hash>&shem=...&shndl=37&shmd=<hash>&source=sh/x/aim/m1/1
```
四道反爬叠加:
1. **JS 渲染** — 落盘 HTML 92,262 B,目标中文关键词出现 **0** 次,无数据结构化回调。
2. **一次性令牌** — `smstk` / `smstidx` 与会话绑定,重放即失效。
3. **无 JS 兜底 = 元刷新** — `<noscript><meta content="0;url=/httpservice/retry/enablejs?sei=...">`。
4. **边缘人机验证** — 真浏览器被踢到 `/sorry/index?continue=...`。
**唯一能免费拿到的信息**:URL 的 `q=` 参数**原文暴露了提问内容**(本次解出「区块链 二级链技术发展的如何了」)。所以拿到这类链接时,**先把 `q=` 解码出来**,至少能知道用户想问什么;正文再请用户粘贴。
同类站点的同族特征:`udm=50` = Google AI Mode;`smstk` 前缀 = 分享态令牌;Cloudflare 的对应物是 `/cdn-cgi/challenge-platform/` + `cf_clearance` cookie。
## 五、正确交付姿势
抓不到不是失败,**说不清"为什么抓不到"才是失败**。向用户报告时必须给出:已试过哪几条路、每条路的具体返回(状态码 / 字节数 / 命中数)、归因到哪一层、以及"你这边的可用替代动作"(粘贴原文 / 提供导出文件 / 给带凭据的访问方式)。
## 六、复用的工具账本
| 路线 | 适用 | 本案例结果 |
|---|---|---|
| WebFetch | 静态 HTML | fetch failed(空) |
| `curl` 直连 | 静态 HTML / API | 502(沙箱内代理) |
| `curl -x 用户代理` | 静态 HTML | 200 但壳页,命中 0 |
| `curl` + Googlebot UA | 怕被 UA 拦的站 | 200 但壳页,命中 0 |
| `r.jina.ai` 正文代理 | 中等强度 JS 站 | 返回反爬验证页 |
| `agent-browser` 无头 | 真需要渲染 / 交互 | 落到 `/sorry/index` |
顺序建议:**WebFetch → curl 落盘 + 关键词计数 →(判据 A 命中则直接问用户)→ 只在"确实是 JS 渲染但无强验证"时才上无头浏览器。**