docs: add wanou aggregate spider design

This commit is contained in:
Harold
2026-04-20 13:56:07 +08:00
parent 196a544d93
commit ed77ebdea1
@@ -0,0 +1,429 @@
# 玩偶聚合 Spider 设计
**日期:** 2026-04-20
## 目标
在当前 `py/` Spider 仓库中新增一个“玩偶聚合”源,提供以下能力:
- 以站点为一级分类展示聚合站。
- 每个站点在分类页内继续使用原站分类和筛选项。
- 搜索时并发请求多个站点,并将同片结果聚合为单条记录。
- 聚合详情页合并多个站点的网盘分享线路。
- `playerContent` 不展开网盘剧集,只透传分享链接。
明确不做:
- 独立管理后台。
- 监控页面。
- 常驻定时域名巡检任务。
- 在 Spider 内调用网盘驱动展开剧集列表。
## 现状与约束
当前仓库是单文件 Python Spider 集合,统一继承 `base/spider.py` 中的 `Spider` 基类,测试使用 `unittest``unittest.mock`。现有仓库已接受以下模式:
- `homeContent` 返回 `class` 和可选 `filters`
- `categoryContent` 使用 `extend` 解析筛选参数。
- `detailContent` 可以返回网盘分享链接组成的 `vod_play_from` / `vod_play_url`
- `playerContent` 可以对网盘链接做原样透传。
- 返回列表结果时不包含 `pagecount`
因此,新 Spider 必须遵守当前仓库接口和测试风格,而不是照搬原始 Node/Fastify 插件结构。
## 方案选择
采用“配置驱动的单 Spider 聚合层”方案:
- 新增单文件 `玩偶聚合.py`
- 文件内部包含站点配置、通用抓取逻辑、聚合编排逻辑。
- 对差异较大的站点保留少量站点钩子,避免把所有逻辑写成纯硬编码分支。
不采用“每站点一个适配器文件”的方案,原因是当前仓库以单文件 Spider 为主,这种拆分会引入额外组织成本;也不采用“全部 if/else 硬编码”的方案,原因是后续修站难度会快速上升。
## 模块职责
`玩偶聚合.py` 内部拆成三层职责:
### 1. 站点配置层
维护聚合站点定义,包括:
- 站点 ID 和中文名。
- 域名列表,按优先顺序排列。
- 分类 URL 模板。
- 搜索 URL 模板。
- 列表、搜索、详情解析用选择器或 XPath 规则。
- 原站默认分类。
- 可选的本地筛选配置文件映射。
- 站点优先级。
### 2. 通用采集层
负责:
- 请求 HTML。
- 域名失败切换。
- 列表卡片解析。
- 搜索卡片解析。
- 详情页元数据提取。
- 网盘分享链接识别与分组。
- 聚合 ID 编解码辅助。
### 3. 聚合编排层
负责:
- 首页按站点输出分类和筛选结构。
- 分类请求路由到指定站点。
- 搜索并发拉取多站结果。
- 基于名称和年份做结果聚合。
- 聚合详情拉取多站详情并合并线路。
- `playerContent` 透传网盘链接。
## Spider 对外行为
### homeContent
首页使用“站点优先”模式:
- `class` 中每个条目代表一个聚合站点。
- `type_id` 使用 `site_<site_id>` 格式。
- `type_name` 使用站点中文名。
每个站点的 `filters` 至少包含:
- `categoryId` 分组,对应原站分类。
若该站有可落地的本地筛选配置,则继续追加该站支持的:
- `area`
- `year`
- `class`
- `by``sort`
- 其他与模板兼容的筛选字段
这样,使用方在 UI 上会先选站点,再选该站原始分类和筛选项。
### categoryContent
`tid` 必须是 `site_<site_id>`
处理逻辑:
1.`tid` 提取目标站点。
2.`extend` 中读取 `categoryId`
3. 读取该站支持的附加筛选项。
4. 按该站模板拼分类 URL。
5. 请求 HTML,解析列表卡片。
6. 返回当前页结果。
`extend` 未提供 `categoryId` 时,默认使用该站首个分类。
### searchContent
搜索默认启用聚合模式:
1. 对所有站点并发发起搜索请求。
2. 每个站点单独设置超时,超时只影响该站。
3. 解析得到站内搜索卡片列表。
4. 基于名称归一化和年份辅助规则进行跨站聚合。
5. 选择优先级最高的站点结果作为聚合主信息。
6. 生成聚合 `vod_id` 并返回聚合后的列表。
聚合结果默认不再把同片不同站拆成多条返回。
### detailContent
支持两类 ID
- 单站详情 ID。
- 聚合详情 ID。
单站详情:
- 请求对应站点详情页。
- 提取片名、海报、年份、导演、演员、简介。
- 提取并识别所有网盘分享链接。
- 生成 `vod_play_from``vod_play_url`
聚合详情:
- 解码聚合 ID 中包含的站点条目。
- 按站点优先级依次请求多个站点详情。
- 跳过失败站点和无网盘站点。
- 合并多个站点的网盘线路并输出。
详情页不展开网盘剧集,不调用外部盘驱动。
### playerContent
仅负责透传常见网盘分享链接。
支持:
- `pan.baidu.com`
- `pan.quark.cn`
- `drive.uc.cn`
- `alipan.com`
- `aliyundrive.com`
- `pan.xunlei.com`
- `115.com`
- `123pan.com`
- `cloud.189.cn` 或等价天翼域名
- `yun.139.com` 或等价移动云盘域名
`flag``id` 能识别为上述网盘分享链接时,返回:
```python
{"parse": 0, "playUrl": "", "url": "<share-url>"}
```
无法识别时返回空 URL,不尝试站内播放页解析。
## 数据模型设计
### 单站结果 ID
分类页中的普通条目使用:
`site:<site_id>:<detail_path>`
示例:
`site:wanou:/voddetail/12345.html`
要求:
- 保留站点信息。
- 保留足够重建详情 URL 的短路径。
- 不直接存储整页 HTML 或冗长 JSON。
### 聚合结果 ID
搜索聚合结果使用:
`agg:<base64_json>`
JSON 负载为条目数组,每个条目至少包含:
- `site`
- `path`
- `name`
- `year`
这样可以:
- 避免把多个站点路径用裸字符串硬拼到 `vod_id` 里。
- 在详情阶段稳定恢复参与聚合的站点列表。
- 控制 `vod_id` 长度和结构清晰度。
### 播放线路
`vod_play_from` 使用盘类型加来源站点命名,例如:
- `baidu#玩偶`
- `quark#木偶`
- `a123#蜡笔`
`vod_play_url` 直接存分享链接:
- `百度合集$https://pan.baidu.com/s/...`
- `夸克资源$https://pan.quark.cn/s/...`
同类型多站资源允许并存,以来源站区分。
## 搜索聚合规则
### 名称归一化
对片名做规范化,用于跨站聚合判断:
- 转小写。
- 去除空白和常见标点。
- 去除常见分辨率或来源尾巴,如 `4K``HDR``2160P``1080P`
- 去除常见站点附加标签。
### 聚合判定
优先使用以下规则:
1. 归一化片名完全相同,可聚合。
2. 若双方年份都存在且不相同,则不能聚合。
3. 若年份一致或其中一方缺失年份,允许聚合。
4. 对明显不同的片名,即使部分包含,也不强行聚合。
本次实现不引入复杂模糊匹配算法,避免误合并;先用保守规则建立稳定版本。
### 主信息来源
聚合组内按站点优先级排序,优先级最高的记录决定:
- `vod_name`
- `vod_pic`
- `vod_remarks`
- `vod_year`
同时保留来源标签供详情阶段使用。
## 详情聚合规则
### 单站详情解析
每个站点详情页尽量提取:
- 标题
- 海报
- 年份
- 地区
- 类型
- 导演
- 演员
- 简介
- 网盘分享链接
如果该站只有网盘资源,没有站内剧集线路,也视为有效站点。
### 网盘识别
根据链接域名识别盘类型,优先以分享域名为准,而不是文案标签:
- 百度
- 夸克
- UC
- 阿里
- 迅雷
- 115
- 123
- 天翼
- 移动云盘
### 合并策略
聚合详情按站点优先级遍历多站结果:
- 失败站点直接跳过。
- 无网盘链接的站点直接跳过。
- 同一站内重复链接去重。
- 同一分享链接跨站重复时只保留一份。
- `vod_play_from` 顺序先按盘优先级,再按站点优先级。
主详情信息取第一个成功站点。
如果所有站点都没有可用网盘链接,则返回空播放线路。
## 域名容错
不做后台监控,只做请求时故障切换。
每个站点维护多个候选域名:
1. 请求时按当前顺序逐个尝试。
2. 某个域名成功后,将其提升到列表前部。
3. 失败时继续尝试下一个域名。
4. 全部失败则该站本次请求记为空结果或详情失败。
该策略适用于:
- 分类页。
- 搜索页。
- 详情页。
不会引入线程、定时器或额外 HTTP 管理接口。
## 站点筛选策略
优先从仓库本地配置文件加载可用筛选项;若某站缺少本地筛选配置,则仅提供:
- 分类 `categoryId`
分类分组必须按用户配置顺序输出,避免被 Python 字典顺序或后处理逻辑打乱。
对于筛选 URL,支持两种模式:
- 站点自定义模板。
- 通用 CMS 模板回退。
## 测试设计
为新 Spider 新增 `tests/test_玩偶聚合.py`,覆盖三层行为。
### 1. 纯函数与辅助方法
- 站点 ID 解析。
- 单站 `vod_id` 编解码。
- 聚合 `vod_id` 编解码。
- 片名归一化。
- 聚合判定规则。
- 网盘类型识别。
### 2. 单站解析与 URL 构建
- `homeContent` 输出站点分类和筛选结构。
- 分类 URL 根据 `extend` 正确拼接。
- 搜索 URL 根据关键字正确构建。
- 单站列表卡片正确解析。
- 单站详情正确抽取网盘链接。
- 域名切换在首域失败后能使用备用域。
### 3. 聚合行为
- 多站搜索结果合并为单条。
- 年份冲突结果不合并。
- 聚合详情合并多个站点线路。
- 聚合详情对重复分享链接去重。
- `playerContent` 透传 quark / baidu / uc / aliyun / xunlei 等常见盘链接。
所有测试使用内嵌 HTML 或 mock response,不访问真实网络。
## 文件变更计划
预计后续实现阶段将修改或新增:
- 新增 `py/玩偶聚合.py`
- 新增 `py/tests/test_玩偶聚合.py`
- 如有必要,新增本地筛选配置文件,但优先复用已有 `筛选` 目录资源
本设计阶段只新增本 spec 文件。
## 风险与处理
### 1. 站点结构不完全一致
处理方式:
- 使用站点配置驱动。
- 仅为少量差异保留钩子函数。
### 2. 搜索误聚合
处理方式:
- 初版采用保守聚合规则。
- 以完全归一化片名匹配为主,年份冲突即拆开。
### 3. 聚合 ID 过长
处理方式:
- 使用精简 JSON 负载。
- 仅保存详情恢复必需字段。
### 4. 备用域名经常失效
处理方式:
- 保持请求时故障切换。
- 不引入复杂监控模块。
## 实施结论
后续实现应严格围绕以下范围展开:
- Python 单文件聚合 Spider。
- 站点优先首页结构。
- 搜索默认聚合。
- 详情合并网盘分享线路。
- `playerContent` 只透传分享链接。
超出上述范围的监控后台、定时巡检、管理 API 和网盘剧集展开,均不在本次任务中。