diff --git a/py/docs/superpowers/specs/2026-04-18-dbku-spider-design.md b/py/docs/superpowers/specs/2026-04-18-dbku-spider-design.md new file mode 100644 index 0000000..db4f6d8 --- /dev/null +++ b/py/docs/superpowers/specs/2026-04-18-dbku-spider-design.md @@ -0,0 +1,252 @@ +# 独播库 Python 爬虫设计 + +## 目标 + +在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的独播库站点爬虫,覆盖以下能力: + +- 分类列表 +- 详情页 +- 搜索 +- 播放解析 + +实现基于网页 DOM 抓取,不依赖站点私有 API,不修改 `base/` 公共层。 + +## 范围 + +本次实现包含: + +- 新增独立脚本,暂定文件名为 `独播库.py` +- 使用单一站点主域:`https://www.dbku.tv` +- 支持列表页、搜索页、详情页和站内播放解析 + +本次实现不包含: + +- 多域名自动回退 +- 网盘资源解析 +- 通用网页源抽象框架 + +## 方案选择 + +采用 `requests + lxml` 直接抓取网页 HTML,并吸收参考插件 `plugin_dbku` 中已经验证过的 DOM 结构与 `player_data` 解析规则。 + +不直接把整份 JS 逐行平移到 Python,原因是: + +- Python 代码更容易维护 +- 更贴合当前仓库的 `Spider` 接口 +- 只保留独播库真正需要的站点规则,减少噪音 + +## 模块边界 + +新增脚本只在站点文件内部维护逻辑,不修改 `base/`。 + +脚本内部职责拆分如下: + +- `init` + - 初始化主域、请求头、分类映射 +- `homeContent` + - 返回固定分类 +- `categoryContent` + - 请求分类页并解析媒体卡片 +- `searchContent` + - 请求搜索页并解析搜索结果 +- `detailContent` + - 提取影片基础信息和剧集列表 +- `playerContent` + - 解析剧集页中的 `player_data`,得到最终播放地址 +- 私有辅助函数 + - URL 归一化 + - 列表卡片解析 + - 搜索结果解析 + - 详情页解析 + - `player_data` 提取与解码 + +## Host 策略 + +本次只实现单域: + +- `https://www.dbku.tv` + +请求失败时不做多域切换,只返回空结果或空播放地址。后续如果该站常态化换域,再补 host 回退。 + +## 分类设计 + +分类 ID 直接使用参考实现中的键,避免额外映射层: + +- `index` +- `movie` +- `variety` +- `anime` +- `hk` +- `luju` + +分类 URL 映射如下: + +- `index -> /vodtype/2--------{pg}---.html` +- `movie -> /vodtype/1--------{pg}---.html` +- `variety -> /vodtype/3--------{pg}---.html` +- `anime -> /vodtype/4--------{pg}---.html` +- `hk -> /vodtype/20--------{pg}---.html` +- `luju -> /vodtype/13--------{pg}---.html` + +首页推荐 `homeVideoContent` 维持空列表,避免额外抓站点首页。 + +## 列表与搜索解析 + +### 分类页 + +分类页主要解析 `myui-vodlist__box` 卡片。 + +每张卡片提取规则: + +- 链接:优先选择 `href` 含 `/voddetail/` 的链接 +- 标题:优先链接 `title`,回退到节点文本 +- 封面:优先取 `data-original`,其次 `src` +- 描述:优先取类名含 `pic-text` 的文本,回退到卡片中首个有意义的附加文本 + +输出字段: + +- `vod_id` + - 直接使用详情页绝对 URL +- `vod_name` +- `vod_pic` +- `vod_remarks` + +### 搜索页 + +搜索 URL: + +- `/vodsearch/-------------.html?wd=&submit=` + +搜索结果优先解析 `#searchList` 容器内的卡片;若没有搜索容器或解析结果为空,则回退到普通列表卡片解析,以兼容不同模板。 + +### 分页策略 + +返回分页字段: + +- `page = 当前页` +- `pagecount = pg + 1`,若当页无内容则为当前页 +- `limit = 实际条目数` +- `total = 近似值` + +不依赖站点总数统计。 + +## 详情页设计 + +`detailContent` 使用 `vod_id` 请求详情页,提取: + +- `vod_name` +- `vod_pic` +- `vod_year` +- `vod_area` +- `vod_actor` +- `vod_director` +- `vod_content` + +播放列表只保留站内源: + +- `vod_play_from = "独播库"` + +剧集项格式: + +- `标题$剧集页URL` + +若发现多组播放列表,先按页面顺序合并同站剧集,去重后输出为单一播放源。 + +## 播放解析设计 + +独播库播放解析核心是剧集页中的 `player_data`。 + +实现步骤: + +1. 请求剧集页 +2. 提取页面脚本中的 `player_data` JSON +3. 读取: + - `url` + - `from` + - `encrypt` +4. 按 `encrypt` 解码播放地址 + +解码规则: + +- `encrypt = 0` + - 原样返回 +- `encrypt = 1` + - 执行 `unescape` 等价解码 +- `encrypt = 2` + - `base64` 解码后再尝试 URL 解码 +- `encrypt = 3` + - 按参考实现做裁剪后的 `base64` 变体解码 + +如果解码后的 URL 仍是站内中间页而不是最终播放地址,则继续请求该页面并再次尝试提取 `player_data` 或直链,直到: + +- 得到最终可播放 URL +- 无法继续解析,此时返回空 URL + +返回格式: + +- `parse = 0` +- `playUrl = ""` +- `url = 最终播放地址` +- `header = {"User-Agent": "...", "Referer": "..."}` + +## 请求与兼容性 + +统一请求头至少包含: + +- 浏览器 `User-Agent` +- 需要时补 `Referer` + +本次优先采用无状态请求;如果验证时发现独播库对 cookie 敏感,再补最小 cookie 维护。 + +## 错误处理 + +实现遵循“失败返回空,不抛异常中断”的原则: + +- 页面请求失败时返回空列表或空播放 URL +- DOM 节点缺失时字段回退为空字符串 +- `player_data` 解析失败时继续尝试回退规则 +- 最终失败则返回 `{"parse": 0, "playUrl": "", "url": ""}` + +日志只保留必要调试信息。 + +## 测试设计 + +采用测试优先方式实现,先给纯解析函数写测试,再补生产代码。 + +测试重点: + +1. 分类卡片解析 + - 断言能从 `myui-vodlist__box` 提取详情链接、标题、封面和描述 +2. 搜索结果解析 + - 断言优先使用 `#searchList` + - 断言缺失时会回退到普通列表解析 +3. 详情页解析 + - 断言影片元信息提取正确 + - 断言剧集列表生成正确并去重 +4. `player_data` 解码 + - 覆盖 `encrypt = 0/1/2/3` +5. `playerContent` + - 断言能从剧集页解析到最终播放地址 + +优先使用 `unittest` 和 mock,避免测试依赖真实站点网络。 + +## 实施顺序 + +1. 新增独播库测试文件,覆盖卡片、搜索、详情和播放器解析 +2. 新增 `独播库.py` 基本骨架与分类映射 +3. 实现分类页和搜索页抓取 +4. 实现详情页和剧集列表 +5. 实现 `player_data` 解码与高层 `playerContent` +6. 运行测试并做最小手工验证 + +## 风险 + +- 页面模板可能调整 +- `player_data` 格式或 `encrypt` 规则可能变化 +- 个别剧集可能存在站内二跳或额外播放器包装 + +对应策略: + +- 列表和搜索解析都保留回退路径 +- 播放解析按 `encrypt` 分类处理 +- 测试覆盖所有已知解码路径,降低回归风险