From 68e6fbac400042b43c6878d9c49b67b25bad95ad Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Fri, 24 Apr 2026 18:30:48 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20add=20=E4=B8=A4=E4=B8=AABT=20spider=20d?= =?UTF-8?q?esign?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../specs/2026-04-24-两个BT-design.md | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 py/docs/superpowers/specs/2026-04-24-两个BT-design.md diff --git a/py/docs/superpowers/specs/2026-04-24-两个BT-design.md b/py/docs/superpowers/specs/2026-04-24-两个BT-design.md new file mode 100644 index 0000000..e5c4c81 --- /dev/null +++ b/py/docs/superpowers/specs/2026-04-24-两个BT-design.md @@ -0,0 +1,185 @@ +# 两个BT Spider 设计 + +## 目标 + +在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的 `两个BT` spider,参考用户提供的 JS 站点逻辑,实现首页、分类、搜索、详情和播放能力,目标站点为 `https://www.bttwoo.com`。 + +## 范围 + +本次实现包含: + +- 新增 `py/两个BT.py` +- 新增 `py/tests/test_两个BT.py` +- 固定 14 个分类 +- 首页推荐、分类页与搜索页卡片解析 +- 详情页元数据与单线路剧集解析 +- 播放页直链提取、iframe 二次提取与失败回退 + +本次实现不包含: + +- 修改 `py/base/` 共享基类 +- 迁移 JS 版本中的 `OmniBox.log`、`processScraping`、`getScrapeMetadata`、`sniffVideo` +- 真实联网测试 +- 站外解析器或额外第三方线路适配 + +## 分类与 URL + +首页固定返回以下分类: + +- `zgjun` 国产剧 +- `meiju` 美剧 +- `jpsrtv` 日韩剧 +- `movie_bt_tags/xiju` 喜剧 +- `movie_bt_tags/aiqing` 爱情 +- `movie_bt_tags/adt` 冒险 +- `movie_bt_tags/at` 动作 +- `movie_bt_tags/donghua` 动画 +- `movie_bt_tags/qihuan` 奇幻 +- `movie_bt_tags/xuanni` 悬疑 +- `movie_bt_tags/kehuan` 科幻 +- `movie_bt_tags/juqing` 剧情 +- `movie_bt_tags/kongbu` 恐怖 +- `gf` 高分电影 + +`homeContent` 只返回 `class`,不返回 `filters`。参考脚本没有定义筛选项,Python 版本也不额外引入未经验证的筛选参数。 + +URL 规则如下: + +- 首页推荐走 `/` +- 分类页默认走 `/{tid}` +- `movie_bt_tags/*` 保留原始路径 +- 页码大于 1 时追加 `paged` 查询参数 +- 搜索走 `/xssssearch?q=` +- 搜索翻页追加 `p=` + +分类和搜索结果返回仓库当前通用字段: + +- `list` +- `page` +- `limit` +- `total` + +不返回 `pagecount`。 + +## 接口映射 + +- `homeContent` + - 返回固定分类 +- `homeVideoContent` + - 请求首页 + - 复用列表卡片解析函数返回推荐列表 +- `categoryContent` + - 构造分类 URL + - 解析卡片并返回分页字段 +- `searchContent` + - 构造搜索 URL + - 解析卡片并做轻量相关性过滤 +- `detailContent` + - 请求详情页 + - 解析标题、封面、简介、导演、主演 + - 解析所有 `/v_play/` 剧集链接并组装单线路播放列表 +- `playerContent` + - 解码 `play_id` + - 请求播放页并优先提取站内直链 + - 必要时进入 iframe 二次提取 + - 提取失败时回退 `parse=1` + +## 数据模型 + +### 列表卡片 + +首页推荐、分类和搜索统一复用卡片解析逻辑: + +- 从包含 `/movie/.html` 的链接提取详情入口 +- `vod_id` 只保留数字 ID,不返回绝对 URL +- 标题按 `h3 a`、`h3`、`a[title]`、`.title`、`.name` 顺序提取 +- 图片按 `data-original`、`data-src`、`src` 顺序提取 +- 图片地址统一补全为绝对 URL +- 备注从评分、状态或常见角标文本提取 +- 对重复卡片按 `vod_id` 去重 + +### 搜索精排 + +搜索结果在卡片解析后做一层轻量过滤,以贴近参考脚本: + +- 标题直接包含关键词时保留 +- 否则按去空白后的字符集合计算交集比例 +- 关键词长度不超过 2 时只做直接包含判断 +- 未命中过滤条件的结果丢弃 + +### 详情 + +详情页字段按“DOM 优先,兜底选择器回退”的方式提取: + +- 标题优先 `h1`、`h2`、`title` +- 封面优先 `img.poster`、`.poster img`、首张可用图片 +- 简介优先 `.intro`、`.description`、`.desc` +- 主演与导演优先标签文本,再用正则清理 `主演`、`导演` 前缀 + +播放列表统一输出为单线路: + +- `vod_play_from`: `两个BT` +- `vod_play_url`: `剧集名$playId#剧集名$playId` + +选集按详情页上的 `/v_play/...html` 链接提取,顺序保持页面顺序。 + +### 播放 ID + +`play_id` 使用轻量 JSON 编码后再 base64,至少包含: + +- `pid`: 站内 `/v_play/` 的核心 ID +- `sid`: 当前影片 `vod_id` +- `name`: 原始剧集名 + +这样可以在播放解析阶段直接恢复播放页地址,并保留回退所需上下文。 + +## 播放解析 + +播放解析优先返回站内直链,处理顺序如下: + +1. 解码 `play_id` +2. 如果输入本身已是媒体直链,则直接返回 `parse=0` +3. 构造并请求 `/v_play/.html` +4. 优先从播放页 HTML 中提取显式媒体地址,例如 `.m3u8`、`.mp4` +5. 若存在 iframe,则继续请求 iframe 页面并重复提取 +6. 若仍无法拿到直链,则回退返回播放页 URL,并设置 `parse=1` + +播放器返回头至少包含: + +- `User-Agent` +- `Referer` +- `Origin` + +这部分只实现仓库现有能力边界内的站内解析,不迁移参考脚本对外部运行时的依赖。 + +## 错误处理 + +- HTML 请求失败时返回空字符串,不向上抛站点异常 +- 首页、分类、搜索失败时返回空列表和基本分页字段 +- 详情页解析失败时返回空列表 +- 播放解析失败时回退 `parse=1` +- 坏的 `play_id`、空节点和无匹配正则都按空值处理,不抛未捕获异常 + +## 测试策略 + +使用 `unittest` 与 `unittest.mock`,不走真网。 + +至少覆盖: + +- `homeContent` 返回固定 14 个分类 +- 列表卡片正确提取 `vod_id`、标题、图片、备注 +- `categoryContent` 正确拼接分类 URL 与页码参数 +- `searchContent` 正确请求搜索 URL 并执行相关性过滤 +- `detailContent` 正确提取元数据和剧集列表 +- `play_id` 编码与解码保持一致 +- `playerContent` 能直接返回已给定的媒体地址 +- `playerContent` 能从播放页提取媒体直链 +- `playerContent` 能进入 iframe 页面继续提取 +- `playerContent` 在拿不到直链时回退 `parse=1` + +## 验收标准 + +- 新增 spider 与测试文件后,可独立运行 `tests.test_两个BT` +- 首页、分类、搜索、详情在离线夹具下输出稳定 +- 播放支持“直接媒体地址 -> 播放页提取 -> iframe 二次提取 -> 失败回退”四条路径 +- 不修改共享基类即可完成接入