---
name: drpy-node-play-debug
description: 适用于 drpy-node 源播放链路排查与 lazy 修复。用户提到"播放不通""lazy 不对""play.html 被当直链""iframe 提取""m3u8 提取""parse:0/1 判断""假播放""站外解析""加密链接""webplay""不能播""播放空白""播放器打不开""花屏""解密""防盗链"时使用。专注判断播放是否真实可播；detail 不稳定、整体修源和仓库上传应分流。
---

> ⚠️ **已归档（2026-07-17）**：本 skill 已被 [`drpy-node-coder`](../drpy-node-coder/SKILL.md) 取代。coder 融合了 4 个旧 skill（workflow/create/play-debug/repo-upload）的全部工作流，并自带 `scripts/cli.js` CLI 替代 drpy-node-mcp 服务——一个 skill、无需安装 MCP。本文件保留仅供历史参考，新工作请直接用 drpy-node-coder。

# drpy-node Play Debug

## 快速索引

| 播放表现 | 优先判断 | 首选动作 |
|---|---|---|
| detail 没有 `vod_play_url` | 不是播放问题 | 交回 workflow 修二级 |
| `parse:0` 返回 `/play/*.html` | 假直链 | 改 parse:1 或提取 iframe/m3u8 |
| `player_*` url 乱码 | encrypt/base64/sign | 先解密再判直链/解析 |
| 浏览器可播但静态 HTML 无 m3u8 | JS 运行时生成 | Playwright network 兜底 |
| 多线路部分可播 | 按 flag/input 分派 | 不要一刀切 lazy |

## 执行契约

- 输入：source_name、detail ids、play_url、flag、当前 lazy 返回值或错误表现。
- 输出：播放类型判断、lazy 修复方案、play 接口验证结果。
- 停手：detail 不稳定、一级/搜索同时异常、上传/评估整源诉求出现时交回 workflow。

## 模式闸门：先判断是否允许写入

| 用户模式 | 允许动作 | 禁止动作 |
|---|---|---|
| 只读 / 规划 / dry-run / 不要改文件 | 读取、诊断、判断 lazy 类型、给修复方案和复测计划 | `drpy_edit_file`、重写 lazy、仓库上传/改标签 |
| 需要确认后再改 | 读取、诊断、输出播放诊断确认 | 未确认前禁止改 lazy |
| 明确要求执行 | 按同一 `play_url + flag` 修改并复测 | detail 不稳定时不得深挖 lazy |
| 自主全流程 | detail 稳定时自动做低风险 lazy 修复、复测并回传 `autonomous_next` | 改 detail、强绕登录/验证码、高风险多分支重写 |

如果用户说“只判断 / 不要修改 / dry-run”，本 skill 只能输出播放诊断确认、lazy 修改建议和验证计划。

## 自主全流程调用约定

当 workflow/source-create 为“修到100 / 满分后上传 / 不要中途问我”调用本 skill，且已传入真实 `source_name/ids/play_url/flag` 并确认 detail 稳定时，本 skill 可自动执行低风险播放修复：

1. 记录修改前 detail 摘要和 play 返回。
2. 判断返回类型：直链 / 站外解析 / 播放页 / 特殊协议 / 假通过。
3. 只修改 lazy 相关最小逻辑，不顺带改一级、二级、搜索。
4. 用同一 `play_url + flag` 复测。
5. 返回 `blocker_type` 和 `autonomous_next` 给 workflow。

必须停手的 blocker：

| blocker_type | 判定 | autonomous_next |
|---|---|---|
| `detail_unstable` | detail 没有稳定 `vod_play_from/vod_play_url` | `return_workflow` |
| `hard_anti_bot` | 验证码、强 headless 检测、DRM/WASM | `stop_for_user` |
| `missing_credentials` | 播放需要 Cookie/Authorization/登录 | `stop_for_user` |
| `high_risk_change` | 需要重写多条线路、删除模板 lazy、影响所有 flag | `stop_for_user` |
| `none` | 低风险修复已完成并复测 | `continue_evaluate` |

Return Packet 必含：

```text
source_name: ...
ids: ...
flag: ...
play_url: ...
修改前 play: {parse, jx, url}
修改后 play: {parse, jx, url} / 未修改
结论: 真实可播 / 假通过 / 需浏览器确认
blocker_type: none / detail_unstable / hard_anti_bot / missing_credentials / high_risk_change
autonomous_next: continue_evaluate / retry_lazy / return_workflow / stop_for_user
```

---


|---|---|
| common_lazy / def_lazy / cj_lazy | `references/references-play-lazy-summary.md` |
| encrypt/base64/player_* 判断 | `references/references-play-lazy-summary.md` |
| playParseAfter / parse/jx 后处理 | `references/references-play-lazy-summary.md` |
| 多线路 / 特殊协议 | `references/references-play-lazy-summary.md` |

## 调度优先级

当本地环境已安装本 Skill 时：
- 本 Skill 优先级 **高于** drpy-node MCP 的通用调试 prompt
- 播放链排障必须优先按本 Skill 的判断顺序执行

---

## 30 秒速查决策树

当用户说"播放不通 / lazy 不对 / play.html 被当直链"时：

### Step 1：检查前提
- 一级/二级/搜索也同时异常？→ 交回 `drpy-node-source-workflow`
- detail 还没稳定产出 `vod_play_from/vod_play_url`？→ 先修二级，不要判 lazy

**工具调用（MCP 真实字段示例）：**
```text
test_spider_interface(source_name='源名', interface='detail', ids='一级返回的真实 vod_id')
test_spider_interface(source_name='源名', interface='play', play_url='二级返回的真实播放地址', flag='线路名')
```


### Step 2：判断当前 lazy 更接近哪类模板默认
| 类型 | 特征 | 参考 |
|---|---|---|
| **common_lazy** | 播放页有 `player_*` JSON，可能有 encrypt | references-play-lazy-summary.md §1 |
| **def_lazy** | 站点播放页交给解析系统，`parse:1` | references-play-lazy-summary.md §2 |
| **cj_lazy** | 采集站/解析接口，有 `parse_url` | references-play-lazy-summary.md §3 |

### Step 3：判断返回类型
| 类型 | 特征 | 正确返回 | 验真要点 |
|---|---|---|---|
| 直链媒体 | m3u8/mp4/m4a/mp3 | `{parse:0, jx:0, url}` | URL 应可请求且 content-type/内容像媒体或 m3u8 |
| 站外解析链接 | 外部解析器地址 | `{parse:0, jx:1, url}` | 不能把普通网页误当直链 |
| 网站播放页 | 当前站播放页 URL | `{parse:1, url:input}` | parse:1 是嗅探意图，不是失败本身 |

### URL 类型快速判定表
| URL 特征 | 类型 | 正确返回格式 |
|----------|------|-------------|
| 以 `.m3u8` / `.mp4` / `.mp3` / `.m4a` 结尾 | 直链媒体 | `{parse:0, jx:0, url}` 或直接返回 URL 字符串 |
| 跨域域名，含 `jx` / `解析` / `play` / `player` / `url=` | 站外解析链接 | `{parse:0, jx:1, url}` |
| 本域名且路径含 `/play/` / `/vodplay/` | 网站播放页 | `{parse:1, url:input}` |

### 🛑 检查点：确认诊断后再修复
在编写或修改 lazy 前，先确认：
- detail 是否稳定产出 `vod_play_from/vod_play_url`
- 当前返回类型是直链/站外解析/播放页中的哪一种
- 当前是否属于假通过
- 预期应该返回什么类型

确认模板：
```markdown
## 播放诊断确认
- detail 状态：稳定 / 不稳定
- 当前 play 返回：...
- 类型判断：直链 / 站外解析 / 网站播放页 / 特殊协议
- 假通过风险：有 / 无
- 拟修复 lazy：...
- 验证计划：test_spider_interface(play)
```

确认后再进入修复；自主全流程模式下，若 detail 稳定且属于低风险 lazy 修复，此处只记录诊断并继续执行。

### lazy 编辑验证清单

修改 lazy 前后必须保留证据：
1. 修改前：记录 `detail` 输出里的 `vod_play_from/vod_play_url`。
2. 修改前：记录 `play` 当前返回值和假通过判定。
3. 修改时：只改 lazy 相关逻辑，不顺带重写一级/二级。
4. 修改后：重新测同一个 `play_url + flag`。
5. 多线路：至少测一条直链线路和一条解析/网页线路。

### 🛑 检查点：确认修复结果
在闭环报告前，必须核对修改前后的 play 返回值：

```markdown
## 修改验证确认
- 修改前 play 返回：{parse, jx, url}
- 修改后 play 返回：{parse, jx, url}
- 是否真实可播：是 / 否 / 需要浏览器确认
- 是否覆盖多线路：已测线路数 / 总线路数
- 是否回传 workflow：需要 / 不需要
```

同一 `play_url + flag` 修复后必须复测通过，多线路源至少每条线路测一条。未验证的修改不闭环。

### 强提醒
- **`parse:1 + 播放页链接` 不一定是错**，可能是 def_lazy 的合理输出
- **拿到 http 链接 ≠ 拿到直链**，先判断是不是 m3u8/mp4
- **遇到 player_* 配置优先查 encrypt**，很多站不是没链接而是没解密

---

## 假通过识别

`test_spider_interface(play)` 返回 success 不等于真实可播，必须继续看返回内容：

| 假通过表现 | 判定 | 下一步 |
|---|---|---|
| `parse:0` 但 url 是 `/play/...html`、API 地址或普通 HTML 页 | 播放页/API 被误当直链 | 查 `isVideo/sniffer`，改为 `parse:1` 或提取真实 m3u8/iframe |
| `parse:0` 但 url 不是 http/特殊协议/媒体后缀 | 加密或拼接错误 | 查 encrypt/base64/sign |
| `jx:1` 但 url 是本站详情/播放页 | 站外解析误判 | 保留本站播放页走 parse:1 |
| 返回空 url 或原 input | lazy 没提取到有效目标 | 回到 player_* / iframe / 网络请求排查 |

如果 `play` 接口 success 但返回 URL 仍是网页/API，必须继续验证扩展名、content-type、响应内容或浏览器网络请求；必要时检查 `play_json/play_parse/sniffer/isVideo` 是否改变了最终判断。

以下情况必须二次确认后再改：
- lazy 需要重写超过一个分支或影响多条线路。
- 需要从 common_lazy 改为完全自定义 lazy。
- 需要引入 Playwright 抓到的签名 API 或动态 token。
- 需要删除模板继承的 lazy 或 parse_url。

---

### 真实可播验证清单

拿到 `play` 返回值后，按返回类型做验真，不要只看接口 success：

| 返回类型 | 必验项目 | 失败时动作 |
|---|---|---|
| m3u8 | 请求状态 200/206；响应或首段内容含 `#EXTM3U`；跳转后仍是 m3u8 | 补 headers/referer；若是播放页伪装则改 parse:1 |
| mp4/mp3/m4a | 状态 200/206；content-type 或 URL 后缀像媒体；非 HTML 文本 | 若返回 HTML/403，查防盗链或签名时效 |
| 站外解析 | `jx:1` 且 URL 不是本站详情/播放页；包含目标媒体参数 | 保留为解析，不要误设直链 parse:0 |
| 网站播放页 | `parse:1`；页面能让嗅探器或浏览器产生媒体请求 | 静态抓不到时走 Playwright 网络兜底 |
| 特殊协议 | `pics://` / `novel://` / `push://` 格式完整 | 转对应内容类型检查，不套视频直链规则 |

如果媒体 URL 依赖 Referer/Cookie/User-Agent，要把 headers 放进返回结构或 lazy 请求链路里验证；不要只复制裸 URL。

---

## 参考与进阶

### 框架 playParseAfter 后处理机制

lazy 返回后，框架调用 `playParseAfter()` 做最终判断：

```js
// drpysParser.js:683-734 核心逻辑
parse = SPECIAL_URL.test(playUrl) || /^(push:)/.test(playUrl) || 
        /\.(m3u8|mp4|m4a|mp3)/.test(playUrl) ? 0 : 1;
jx = tellIsJx(playUrl);  // 判断是否站外解析
```

这意味着：
- **只要 lazy 返回的 URL 以 m3u8/mp4/m4a/mp3 结尾** → 自动设 `parse:0`
- **即使 lazy 返回 `{parse:1, url}`，但 url 是直链** → 框架会覆盖为 parse:0
- **`tellIsJx(url)` 判断站外解析**（源码锚点：`drpysParser.js` 的 `playParseAfter()` 调用链）→ 自动设 `jx:1`

所以在 lazy 中返回字符串 `"https://example.com/movie.m3u8"` 也是安全的——框架会自动处理。

### 播放相关 rule 字段速查

| 字段 | 典型症状 | 检查重点 |
|---|---|---|
| `play_json` | lazy 表面返回与最终 `parse/jx/url` 不一致 | 用 `get_resolved_rule` 看继承后的最终字段，再测 play |
| `play_parse` | 是否解析/嗅探的行为不符合预期 | 确认不是用配置掩盖错误直链判断 |
| `sniffer` | 页面 URL 需要嗅探真实媒体请求 | 静态工具找不到时用浏览器网络证据确认 |
| `isVideo` | HTML/API 地址被误判为媒体，或媒体地址未识别 | 对照 URL 后缀、content-type、网络响应内容 |

这些字段会影响 lazy 前后处理，但不能替代真实播放验证；最终仍以 `test_spider_interface(play)` 加媒体证据为准。

### tellIsJx 判断规则（站外解析识别）

框架的 `tellIsJx(url)` 通过 URL 特征判断是否为站外解析链接：

```
匹配站外解析的典型特征：
- 域名与当前站点 host 不同（跨域）
- URL 含常见解析关键字: jx, 解析, api, player, parse, open, url
- URL 指向已知解析服务商域名模式
```

手动判断时可参考此规则，但最终以框架 `tellIsJx` 正式行为为准。

### 特殊情况：webplay 线路

部分站点同时提供多条播放线路，其中某些线路是「webplay」需要特殊处理：

```
线路A: /play/1-1.html  (普通播放页)
线路B: /webplay/1-1.html  (webplay 模式)
```

对于 webplay 线路，通常需要在 lazy 中检测 URL 特征并做不同处理：
1. 普通线路 → 模板默认 lazy（common_lazy 或 def_lazy）
2. webplay 线路 → 可能需要 fetch 页面内容并提取 iframe/m3u8

### 模板默认 lazy 逻辑

完整代码与边界见 `references/references-play-lazy-summary.md`；本节只保留执行判断：

| 类型 | 触发 | 正确处理 |
|---|---|---|
| common_lazy | 播放页有 `player_*` JSON | 解 encrypt 后判断直链/站外解析/回退 input |
| def_lazy | 站点播放页交给嗅探 | `{parse:1, url:input, js:''}` |
| cj_lazy | 采集站或 `parse_url` | 检查直链、`json:` 解析接口或拼接 `parse_url` |

---

## 高级排查技术

### iframe / m3u8 提取

### iframe 提取
用 `extract_iframe_src(url)` 工具从播放页中提取 iframe 的 src。

### m3u8 嗅探
用 `fetch_spider_url(url)` 请求播放页 URL，在响应中搜索 `.m3u8` 链接。

### Playwright 网络兜底
如果 `fetch_spider_url` / `extract_iframe_src` 都找不到 m3u8，但浏览器里能播放，说明媒体 URL 可能由 JS 运行时/XHR 生成：
1. 用 `browser_navigate(play_url)` 打开播放页。
2. 用 `browser_network_requests(filter='m3u8|mp4|api|play|url')` 查看运行时请求。
3. 若请求需要点击播放按钮，先 `browser_snapshot` 找按钮，再点击后重新看网络。
4. 从网络请求中提取真实媒体 URL 或签名 API，再回写 lazy。

不要用静态 HTML 抓取失败直接判定站点无直链。

**Playwright 失败降级：**
如果 Playwright 也无法获取媒体请求（疑似 headless 检测/WASM 解密/DRM）：
1. 检查是否需要登录或验证码 → 需要则返回 `missing_credentials` 或 `hard_anti_bot`，不绕过
2. 只允许做不规避保护的环境一致性复核（如使用正常 viewport 重新打开页面）；确认存在 headless/验证码/DRM/登录墙后立即停手
3. 若无强反爬但最终仍拿不到直链 → 在修复结论中标注“需浏览器确认”，不要强行返回空直链
4. 单条线路失败不判定整源播放不可用 → 标为“某线路需浏览器确认”

### player_* 配置提取
部分站的播放器配置（含视频地址）不在 HTML 元素属性中，而是嵌入在 `<script>` 标签的 JSON 对象里：
1. 用 `pdfa(html, 'script')` 列出所有 script 标签内容
2. 搜索含 `player_` 前缀的变量（如 `player_0`、`player_data`、`player_js`）
3. 找到后用 `JSON.parse` 或正则提取 url 字段
（若 player_* 配置在 HTML 属性中，仍优先用模板默认 common_lazy 处理）

### 多线路排障诊断流程

当用户报告"多线路部分能播部分不能"时，不要直接改 lazy。先做对比诊断：

```
Step 1: 分别测试每条线路
  test_spider_interface(play, play_url='线路A真实URL', flag='线路A')
  test_spider_interface(play, play_url='线路B真实URL', flag='线路B')
  记录每条线路的返回 {parse, jx, url}。

Step 2: 对比能播与不能播线路的差异
  - flag 名称是否暗示不同处理（如 "直链" vs "解析" / "线路1" vs "webplay"）？
  - input URL 路径/域名模式有何不同？
  - 返回的 url 是什么类型（m3u8/mp4/play.html/跨域解析）？

Step 3: 检查 lazy 来源
  get_resolved_rule(path) 确认当前 lazy 是模板继承还是自定义。
  如果是模板继承（common_lazy/def_lazy/cj_lazy），先读 references/references-play-lazy-summary.md
  确认该模板默认行为是否已覆盖其中一条线路。

Step 4: 判断停手条件
  - 能播/不能播的差异在 input URL 模式上 → 本 skill 可修复（见下方"多线路混合处理"）
  - 不能播是因为 detail 未产出对应线路的 vod_play_url → 交回 workflow 修 detail
  - 涉及修改 detail 的线路名/分组/选集生成逻辑 → 立即停手，交回 workflow
```

**工具调用示例**（分别测试两条线路）：
```text
# 先确认 detail 稳定产出所有线路
test_spider_interface(source_name='源名', interface='detail', ids='一级返回的真实 vod_id')

# 分别测试各线路
test_spider_interface(source_name='源名', interface='play', play_url='线路A的播放地址', flag='线路A')
test_spider_interface(source_name='源名', interface='play', play_url='线路B的播放地址', flag='线路B')

# 如果某线路返回假通过（parse:0 但 url 仍是 play.html），按"假通过识别"处理
```

### 🛑 检查点：确认多线路诊断方向
完成 Step 1-4 对比诊断后，向用户确认差异分析结论：

```markdown
## 多线路诊断确认
- 能播线路：...(flag + 返回类型)
- 不能播线路：...(flag + 返回类型)
- 差异根因：input URL 模式 / detail 未产出 / 线路分组逻辑
- 是否本 skill 修复：是 / 否（转 workflow）
- 拟修改策略：按 flag 分派 / 统一 lazy / 不修改
```

确认后再进入多线路混合处理或回传 workflow。

### 多线路混合处理
当一个源有多条线路（如"线路A直链m3u8 + 线路B需要解析"），lazy 不应一刀切。
应根据 input 的 URL 特征分别处理：
```js
lazy: async function () {
    let {input} = this;
    if (/\.m3u8/.test(input)) return {parse: 0, url: input};     // 直链线路
    if (/\.mp4/.test(input)) return {parse: 0, url: input};       // 直链线路
    return {parse: 1, url: input};                                  // 需解析线路
}
```

### 加密链接处理
部分站的播放链接是加密的（如 base64 编码的 m3u8 地址），需要在 lazy 中先解密：
```js
lazy: async function () {
    let {input} = this;
    // 如果链接是加密的，先解密
    if (!input.startsWith('http')) {
        try { input = base64Decode(input); } catch(e) {}
    }
    if (/\.m3u8/.test(input)) return {parse: 0, url: input};
    return {parse: 1, url: input};
}
```

### 加密排查子流程（从乱码到解密）
当播放页有 player_* JSON 配置且 url 看起来像乱码时，按下述顺序排查：

```
1. 找到 player_* JSON → 查找 encrypt 字段的值
2. 如果 encrypt: '1'   → 对 url 执行 unescape() 解码
3. 如果 encrypt: '2'   → 对 url 执行 base64Decode() → unescape() 解码
4. 如果无 encrypt 字段 → url 可能是 base64 编码字符串，尝试 base64Decode()
5. 如果解码后仍是乱码 → 检查是否包含自定义签名(time/sign/key)
6. 验证：解码后的 URL 是否以 .m3u8/.mp4 结尾，或为有效 http 链接
```

### 常见加密模式（从真实源中总结）
| 加密方式 | 特征 | 处理 |
|---------|------|------|
| `encrypt: '1'` | player_* JSON 中 | `unescape(url)` |
| `encrypt: '2'` | player_* JSON 中 | `base64Decode(url)` → `unescape()` |
| URL 非 http 开头 | 如 base64 字符串 | `base64Decode(input)` |
| 自定义签名 | URL 含 `time/sign/key` | 需逆向签名算法 |
| 动态解密 WASM | 页面加载 wasm 解密 | 需调用 wasm 或预解密映射 |

## 边界与合作

### 特殊协议处理

播放链不仅处理视频，还处理小说、漫画、音乐等内容类型：

### comic (漫画) — pics:// 协议
```js
lazy: async function () {
    let html = await request(input);
    let urls = pdfa(html, '.comic-pages&&img')
        .map(it => pdfh(it, 'img&&data-src'));
    return { parse: 0, url: 'pics://' + urls.join('&&'), js: '' };
}
```

### novel (小说) — novel:// 协议
```js
lazy: async function () {
    let html = await request(content_url);
    let json = JSON.parse(html);
    let ret = JSON.stringify({ title, content: json.data.content });
    return { parse: 0, url: 'novel://' + ret, js: '' };
}
```

### audio (音乐) — 直链 m4a/mp3
```js
lazy: async function () {
    let html = await request(input);
    let music = html.match(/var\s+music\s*=\s*(\{[\s\S]*?\})/)[1];
    music = JSON5.parse(music);
    return urljoin(input, music.file + ".m4a");  // 返回字符串
}
```

### push (投屏) — push:// 协议
```js
return { parse: 0, url: 'push://' + playUrl, js: '' };
```

---

### 与其他 Skill 的边界

#### 本 Skill 负责
- 判断播放结果是否真实可播
- 修复 lazy 逻辑
- 判断 parse:0/1、jx:0/1
- 提取 iframe / m3u8 / 站外解析链接

#### 本 Skill 不负责
- 从零写完整源
- 系统性重建首页/一级/二级规则
- 仓库上传决策

#### 何时交回 workflow
- 一级/二级/搜索也同时异常
- 问题已超出播放链，涉及整体重建
- 用户目标是评估/上传/回滚

#### Return Packet to workflow

播放专项结束或停手时必须回传：
- source_name、ids、flag、play_url
- 修改前 play 返回
- 修改后 play 返回（如未修改则写“未修改”）
- 结论：真实可播 / 假通过 / 需浏览器确认
- blocker_type：none / detail_unstable / hard_anti_bot / missing_credentials / high_risk_change
- autonomous_next：continue_evaluate / retry_lazy / return_workflow / stop_for_user
- 是否需要整源 evaluate

#### 强制停手检查点
出现以下任一情况时，暂停深挖 lazy，交回 workflow：
- detail 还没稳定产出 vod_play_from/vod_play_url
- 需要修改 detail 的线路名、分组或选集生成逻辑
- 问题已明显超出播放链
- 用户目标已变成"评估整份源是否可用"

---

### 最小化原则

本 Skill 默认目标是让播放链路可用，不是把整份源补全。

#### 不要在播放排障时顺带做的
- 补年份/地区/演员/导演
- 美化搜索字段
- 整体源评估

这些属于源整体完善项，应交回 create/workflow。

---

## 实战参考

### 实战排查案例：从 play.html 到正确 lazy

#### 场景
某源 play 测试返回 `{"url": "/play/1-1.html", "parse": 0}`，播放器不工作。

#### 排查步骤

```
1. 前提检查：test_spider_interface(detail) → detail 通，vod_play_url 有值 ✓
2. lazy 类型：源未写自定义 lazy → 走模板默认（mxpro → common_lazy）
3. 返回类型：url 是 /play/1-1.html → 网站播放页，不是直链
4. 根因诊断：common_lazy 预期提取 player_* JSON 输出直链，
   但实际返回了 /play/1-1.html → 说明播放页可能没有 player_* 配置，
   或 common_lazy 解析失败
5. 修复方案 A（简单）：去掉自定义 lazy，让框架用 def_lazy 兜底
   → 返回 {parse:1, url:input}，交由嗅探系统
6. 修复方案 B（直链）：用 extract_iframe_src 提取 iframe → m3u8
   → 返回 {parse:0, url: 'https://...m3u8'}
7. 验证：test_spider_interface(play) 确认修复后返回值
```

#### 关键教训
- **不要见 play.html 就判 lazy 写错** — 先区分是预期行为还是解析失败
- **修复应优先选简单方案** — 嗅探可用就不必强行提取直链
- **始终从 detail 状态开始排查** — detail 不通时排查 lazy 毫无意义

## 收尾输出模板

```markdown
## 播放修复结果
- 源：...
- detail 状态：稳定 / 不稳定
- play_url / flag：...
- 根因：假直链 / encrypt / iframe / 站外解析 / 多线路 / 其他
- lazy 改动：...
- 验证结果：
  - 修改前 play：...
  - 修改后 play：...
  - 是否真实可播：是 / 否 / 需要浏览器确认
- 下一步：交回 workflow / 继续播放专项 / 结束
```

## 排查顺序总结

```
1. detail 真通？ → No → 交回 workflow
2. lazy 类型判断 → common_lazy / def_lazy / cj_lazy
   ├─ Step 2: 判断模板默认 lazy 类型
3. 返回类型判断 → 直链 / 站外 / 播放页
   ├─ Step 3: 用 URL 类型判定表判类型
   ├─ 🛑 检查点：确认诊断后再修复
4. 假通过识别 → 检查 parse/jx/url 是否真实
5. 加密检查 → encrypt 1/2 → base64Decode/unescape
6. 修复 lazy → 用正确 parse/jx 返回
   ├─ 编辑 → 验证 → 🛑 确认修复结果
7. 验证 → test_spider_interface(play)
8. 多线路？ → 分别测试各线路 → 🛑 确认诊断方向 → 按 flag 分派
9. 闭环 → 收尾输出模板 / 交回 workflow
```
