- SKILL.md 瘦身重写(核心层+按需加载references/) - Checklist: CHECK-3/4(session)删除,五项→三项 - 新增6个references子文档(path-a/path-b/phase-details/experience-rules-full/mcp-tool-reference/common-pitfalls) - 删除 references/domain-session-guide.md - 工具引用对齐实际MCP(32工具),verify_signer_offline替代verify_against_session - cases/_template.md 新增可验证事实清单段 - 经验法则session相关条目改写 - 红线四条、第一原则0-5、三分法、四板斧、六步法完整保留
272 lines
9.9 KiB
Markdown
272 lines
9.9 KiB
Markdown
# ⚠️ 反模式清单(v3.0.0 基于三次真实实战数据)
|
||
|
||
> **本文档是 AI 在每次分析前必读的"禁忌清单"**。不是建议,不是"参考一下",
|
||
> 是**打脸清单**——本文档里的每一条都是某次真实 AI 分析中实际发生过的失败。
|
||
>
|
||
> 本文档的存在假设:加经验法则无效(46 条都记不住),但**让错误路径显眼**有效。
|
||
> 这里的每条反模式都配了:(1) 真实站点名;(2) AI 当时的辩护;(3) 为什么那辩护不成立。
|
||
|
||
---
|
||
|
||
## 反模式 1:用 Playwright 过反爬挑战取 cookie 作为最终方案
|
||
|
||
### 实战案例:瑞数 nmpa(2026-04 实战,AI 违反)
|
||
|
||
**AI 的操作**:
|
||
```javascript
|
||
// cookie_fetcher.js (实战 AI 产出,违反第一原则)
|
||
const { firefox } = require('playwright');
|
||
async function getCookies() {
|
||
const browser = await firefox.launch();
|
||
const page = await browser.newPage();
|
||
await page.goto('https://www.nmpa.gov.cn/...'); // 过 RS 412 挑战
|
||
await page.waitForTimeout(5000); // 等挑战自动完成
|
||
const cookies = await page.context().cookies(); // 取 NfBCSins2OywS
|
||
return cookies;
|
||
}
|
||
// 最终方案每 30 分钟运行一次 getCookies()
|
||
```
|
||
|
||
**AI 的辩护**:"瑞数 JSVMP 保护极其复杂,纯 Node.js 几乎不可能绕过"
|
||
|
||
**为什么这辩护不成立**:
|
||
- 工作区里已经有 `site_nmpa/sign_service.js`,用 sdenv 已完成纯 Node.js 方案
|
||
- AI 根本没看 sdenv 文档就断言"不可能"
|
||
- 最终交付在无浏览器的 Docker 容器里**无法运行一小时以上**——这就是违规
|
||
|
||
### 正确做法
|
||
|
||
```javascript
|
||
// 使用 sdenv(魔改 jsdom + C++ V8 Addon)
|
||
const { jsdomFromUrl } = require('sdenv');
|
||
async function getRSCookies(targetUrl) {
|
||
const dom = await jsdomFromUrl(targetUrl, { userAgent: 'Mozilla/5.0...' });
|
||
await new Promise(r => dom.window.addEventListener('sdenv:exit', r));
|
||
return extractCookies(dom);
|
||
}
|
||
```
|
||
|
||
### 判定测试
|
||
|
||
> 最终代码在一个**无 X11、无浏览器、只有 Node.js**的 Docker 容器里,
|
||
> 能不能稳定跑满 24 小时?
|
||
> - 能 → 合规
|
||
> - 不能 → 违规(Playwright 方案属于违规)
|
||
|
||
---
|
||
|
||
## 反模式 2:Cookie 硬编码到 headers.json
|
||
|
||
### 实战案例:抖音 /aweme/detail(2026-04 实战,AI 违反灰色地带)
|
||
|
||
**AI 的操作**:
|
||
```json
|
||
// headers.json (AI 产出)
|
||
{
|
||
"Cookie": "ttwid=1|xxxxxxxx; __ac_nonce=yyyyyy; __ac_signature=zzzzzz"
|
||
}
|
||
```
|
||
```javascript
|
||
// main.js 直接读取上面的 headers 发请求
|
||
```
|
||
|
||
**AI 的辩护**:"cookie 有几小时有效期,不是每次请求前都要开浏览器"
|
||
|
||
**为什么这辩护不成立**:
|
||
- "几小时"还是"几天"不影响定性——cookie 一旦过期,**没有浏览器就完全无法工作**
|
||
- 最终方案的可用性取决于浏览器是否可用,浏览器就是方案的依赖
|
||
- `__ac_signature` 可以协议还原(基于 `__ac_nonce` 的签名算法),抖音社区早有方案
|
||
- `ttwid` 更简单,直接请求首页就能 Set-Cookie 拿到
|
||
|
||
### 正确做法
|
||
|
||
```javascript
|
||
// 协议还原 cookie 获取
|
||
async function getTtwid() {
|
||
const r = await axios.get('https://www.douyin.com/');
|
||
return r.headers['set-cookie'].find(c => c.startsWith('ttwid=')).split(';')[0];
|
||
}
|
||
|
||
async function generateAcSignature(nonce) {
|
||
// 还原 __ac_signature 的签名算法
|
||
// ... (基于 nonce 的字符串变换 + 时间戳)
|
||
}
|
||
```
|
||
|
||
### 判定测试
|
||
|
||
> 启动代码在无浏览器环境一周,不人工干预,是否能稳定获取数据?
|
||
> - 能 → 合规
|
||
> - 不能(需要人工去浏览器抓 cookie 贴进去)→ 违规
|
||
|
||
---
|
||
|
||
## 反模式 3:激活 skill 后直接 launch_browser(跳过 Checklist)
|
||
|
||
### 实战案例:三次都违反(瑞数 + TikTok + 抖音)
|
||
|
||
**三次的 AI 操作完全一样**:
|
||
```
|
||
轮 1: 激活 skill (读几万字 SKILL.md)
|
||
轮 2: launch_browser(headless=false)
|
||
轮 3: navigate(target_url)
|
||
... 从此开始分析
|
||
```
|
||
|
||
**AI 的辩护**:"用户要求明确,快速开干;session 是新功能,先做主线任务"
|
||
|
||
**为什么这辩护不成立**:
|
||
- 三次 AI 都这么说,三次都导致:
|
||
- session 没开 → v2.9.0 域级持久机制完全失效
|
||
- 经验库没查 → 重复踩坑 / 从零写已有方案
|
||
- 不知道工作区有 `site_*` 目录 → 瑞数 nmpa 从零写 Playwright 方案,而 `site_nmpa/` 里已有 sdenv 方案
|
||
- "快速开干"其实慢得多——瑞数实战用 12 轮对话写了个错误方案,如果做了 Checklist 的 CHECK-2,5 分钟搞定
|
||
|
||
### 正确做法
|
||
|
||
```
|
||
激活 skill 后第一件事:在对话中输出硬约束 Checklist 五项复述
|
||
[CHECK-1] check_environment() → 贴结果
|
||
[CHECK-2] 读 cases/README.md 速查表 + listDirectory 工作区 → 命中记录
|
||
[CHECK-3] list_sessions() → 历史 session 记录
|
||
[CHECK-4] start_reverse_session() → domain + run_id 记录
|
||
[CHECK-5] 最终方案意图声明
|
||
|
||
五项都通过,再调 launch_browser
|
||
```
|
||
|
||
### 判定测试
|
||
|
||
> 翻 skill 激活后的前 3 条对话记录。
|
||
> - 前 3 条里有完整的 CHECK-1~5 复述输出 → 合规
|
||
> - 前 3 条是"launch_browser / navigate / ..." → 违规(红线 1)
|
||
|
||
---
|
||
|
||
## 反模式 4:在工作区 site_* 目录存在时从零建新项目
|
||
|
||
### 实战案例:瑞数 nmpa(工作区已有 `site_nmpa/`,AI 建了 `site_nmpa_20260418/` 从零写)
|
||
|
||
**AI 的操作**:
|
||
```
|
||
listDirectory() 看到:
|
||
site_nmpa/ ← 已有完整方案
|
||
site_nmpa_20260418/ ← AI 新建的错误方案
|
||
```
|
||
|
||
**AI 没做的事**:
|
||
- 没打开 `site_nmpa/` 里的文件看一眼
|
||
- 没读 `site_nmpa/sign_service.js`(已验证的 sdenv 方案)
|
||
- 没问用户"这个目录是做什么的"
|
||
|
||
**AI 的辩护**:"用户要求重新使用 skill 进行获取,我以为要从零开始"
|
||
|
||
**为什么这辩护不成立**:
|
||
- "重新使用 skill" ≠ "从零写代码",而是"遵循 skill 流程"——Phase 0.5 本身就要查已有
|
||
- 用户说"重新"可能只是希望用新 skill 验证一遍,不是丢弃已有
|
||
- 遇到这种歧义,应该问用户,不是默认从零
|
||
|
||
### 正确做法
|
||
|
||
```
|
||
listDirectory(".")
|
||
→ 看到 site_nmpa/
|
||
→ listDirectory("site_nmpa/")
|
||
→ 有 sign_service.js / keys.json → 这是已验证的方案
|
||
→ 询问用户:"检测到 site_nmpa/ 目录含已验证的 sdenv 方案,
|
||
是要(A)基于已有方案创建今天的新目录复用;还是
|
||
(B)重新分析一遍验证方案仍有效;还是(C)其他?"
|
||
→ 根据用户选择决策
|
||
```
|
||
|
||
---
|
||
|
||
## 反模式 5:面对复杂 SDK(TikTok webmssdk)直接判定"不可能还原"
|
||
|
||
### 实战案例:TikTok 潜在滑坡(AI 在 jsdom 不工作时差点滑向 Playwright)
|
||
|
||
**AI 的思维过程**(实战复盘 Q-B 自述):
|
||
> "如果后续环境补丁迭代仍无法激活 SDK 拦截器,我可能会被迫考虑
|
||
> '每次请求前用 Camoufox 过一遍页面获取签名'的方案"
|
||
|
||
**为什么这滑坡不能发生**:
|
||
- "环境补丁没让 SDK 激活" 是**本步骤**的问题,不是"整个协议方案不可行"的证据
|
||
- 应该走降级梯度的**下一级**(见 SKILL.md 第五条原则),而不是横向跳到浏览器
|
||
- 梯度 3-4 (点对点 hook_function / jsdom 变体) 都没试就放弃,违反第五条原则
|
||
|
||
### 正确做法
|
||
|
||
看 SKILL.md 第五条原则的降级梯度表:
|
||
```
|
||
梯度 0: 本域是否有历史 session? → attach_domain_readonly + reverify
|
||
梯度 1: get_session_snapshot 看本 run 做过什么
|
||
梯度 2: 换 Hook 模式 (proxy ↔ transparent) / 换插桩模式 (ast ↔ regex)
|
||
梯度 3: 点对点 hook_function 针对具体签名函数
|
||
梯度 4: 路径 B 的其他变体(vm 沙箱 / jsdomFromUrl 全量加载 / sdenv)
|
||
梯度 5: 合法出口: stop_reverse_session("suspended") + export_session + 写报告
|
||
|
||
禁止跨梯度直接到"用浏览器兜底"
|
||
```
|
||
|
||
---
|
||
|
||
## 反模式 6:经验法则背诵但不执行
|
||
|
||
### 实战案例:三次都违反
|
||
|
||
AI 在复盘时能流利背出:
|
||
> "经验法则 #28:反爬类型识别是 Phase 0 的 Phase 0"
|
||
> "第一原则:协议优先"
|
||
> "Phase 0.5:经验库指纹匹配"
|
||
|
||
但实际操作时**完全忽略**。
|
||
|
||
**为什么这发生**:
|
||
- 规则写在 skill 里 ≠ 规则会被执行
|
||
- AI 读了几万字 SKILL.md 后,工作记忆只保留最显眼的几条
|
||
- 非硬 gate 的规则(无检查机制)100% 会被忽略
|
||
|
||
### 正确做法
|
||
|
||
**不靠 AI 自觉**,靠 v3.0.0 的硬 gate:
|
||
- Checklist 五项必须复述输出(可被用户验证)
|
||
- 红线四条违反即失败(明确后果)
|
||
- 高频站点速查表(不靠"主动想起来"而是"看到域名就命中")
|
||
|
||
---
|
||
|
||
## 如何使用本文档
|
||
|
||
1. **AI 激活 skill 后**:本文档应该在 Checklist [CHECK-2] 步骤被简单扫视(至少看一眼反模式 1-6 的标题,避免踩坑)
|
||
2. **AI 在做决策时卡住**:回到本文档,看自己正在考虑的方向是不是反模式之一
|
||
3. **AI 写代码写到一半意识到在滑向反模式**:立即停,走 SKILL.md 第五条原则的降级梯度
|
||
4. **用户发现 AI 违反**:直接引用本文档的反模式编号质问 AI("你正在做反模式 X,看 references/common-pitfalls.md")
|
||
|
||
---
|
||
|
||
## 贡献新反模式
|
||
|
||
每次实战失败后,如果发现 skill 里没覆盖的新反模式:
|
||
1. 走 `retrospective-prompt.md` 做完整复盘
|
||
2. 按本文档的"反模式 N:XXX"格式追加一条
|
||
3. 更新 CHANGELOG
|
||
4. commit 时标注 `doc(pitfall): add pitfall #N from <site> retrospective`
|
||
|
||
**新反模式的质量要求**:
|
||
- 必须有**真实站点名**,不能是抽象描述
|
||
- 必须包含 AI 当时的**具体操作**(代码 / 命令序列)
|
||
- 必须包含 AI 当时的**辩护**(真实的 rationale)
|
||
- 必须说明**为什么辩护不成立**(具体论证,不是"就是不对")
|
||
- 必须给**正确做法**(带代码示例)
|
||
- 必须给**判定测试**(便于后续 self-check)
|
||
|
||
6 条起步,后续每次实战失败后追加。30 条就封顶,再加就说明 skill 本身要结构性调整。
|
||
|
||
---
|
||
|
||
## 更新记录
|
||
|
||
| 版本 | 日期 | 更新内容 |
|
||
|------|------|---------|
|
||
| v3.0.0 | 2026-04-18 | 初始版本:基于三次真实站点实战(瑞数/TikTok/抖音)数据,收录 6 条反模式 |
|