Files
tvboxzt/py/docs/superpowers/specs/2026-04-20-wanou-aggregate-spider-design.md
T

10 KiB
Raw Blame History

玩偶聚合 Spider 设计

日期: 2026-04-20

目标

在当前 py/ Spider 仓库中新增一个“玩偶聚合”源,提供以下能力:

  • 以站点为一级分类展示聚合站。
  • 每个站点在分类页内继续使用原站分类和筛选项。
  • 搜索时并发请求多个站点,并将同片结果聚合为单条记录。
  • 聚合详情页合并多个站点的网盘分享线路。
  • playerContent 不展开网盘剧集,只透传分享链接。

明确不做:

  • 独立管理后台。
  • 监控页面。
  • 常驻定时域名巡检任务。
  • 在 Spider 内调用网盘驱动展开剧集列表。

现状与约束

当前仓库是单文件 Python Spider 集合,统一继承 base/spider.py 中的 Spider 基类,测试使用 unittestunittest.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
  • bysort
  • 其他与模板兼容的筛选字段

这样,使用方在 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_fromvod_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 或等价移动云盘域名

flagid 能识别为上述网盘分享链接时,返回:

{"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/...

同类型多站资源允许并存,以来源站区分。

搜索聚合规则

名称归一化

对片名做规范化,用于跨站聚合判断:

  • 转小写。
  • 去除空白和常见标点。
  • 去除常见分辨率或来源尾巴,如 4KHDR2160P1080P
  • 去除常见站点附加标签。

聚合判定

优先使用以下规则:

  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 和网盘剧集展开,均不在本次任务中。