From 7c6efe310b864e17b7892ac69421fbf6d65fd413 Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Fri, 24 Apr 2026 20:42:54 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20add=20=E4=B8=96=E7=BA=AA=E9=9F=B3?= =?UTF-8?q?=E4=B9=90=20spider=20design?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-04-24-世纪音乐-spider-design.md | 384 ++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 py/docs/superpowers/specs/2026-04-24-世纪音乐-spider-design.md diff --git a/py/docs/superpowers/specs/2026-04-24-世纪音乐-spider-design.md b/py/docs/superpowers/specs/2026-04-24-世纪音乐-spider-design.md new file mode 100644 index 0000000..b565e12 --- /dev/null +++ b/py/docs/superpowers/specs/2026-04-24-世纪音乐-spider-design.md @@ -0,0 +1,384 @@ +# 世纪音乐 Python 爬虫设计 + +## 目标 + +在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的世纪音乐爬虫,能力参考用户提供的 OmniBox JS 实现,但交付物遵循当前仓库的单文件 Spider 结构和 `unittest` 测试约定。 + +首版仅覆盖仓库里最常用的五个接口: + +- 首页 +- 分类 +- 搜索 +- 详情 +- 播放 + +## 范围 + +本次实现包含: + +- 新增独立脚本 `py/世纪音乐.py` +- 新增独立测试 `py/tests/test_世纪音乐.py` +- 首页固定分类与推荐内容解析 +- 分类列表解析 +- 关键词搜索解析 +- 歌曲、MV、歌单、歌手、排行榜详情解析 +- 歌曲与 MV 播放直链拼装 +- 针对短 ID、字段回退、空结果和播放结果增加离线单测 + +本次实现不包含: + +- 歌词抓取与返回 +- 首页运行时缓存 +- 修改 `py/base/` 公共层 +- 引入新的第三方依赖 +- 真实联网集成测试 +- 复杂重试、反爬绕过和占位图补全 + +## 方案选择 + +采用“按仓库 Python Spider 形态重写站点逻辑”的方案,而不是直接把 OmniBox JS 原样翻译成 Python。 + +原因如下: + +- 当前仓库交付物是 Python Spider,而不是 OmniBox 脚本 +- 仓库现有测试体系围绕 `homeContent/categoryContent/detailContent/searchContent/playerContent` +- 参考 JS 中的核心能力可以映射到仓库接口,但其缓存和返回结构不适合直接搬运 +- 直接使用短 ID 和离线 HTML 测试,能降低 host 变化、缓存缺失和运行时状态对结果的影响 + +## 模块边界 + +新增模块 `py/世纪音乐.py` 只负责该站点逻辑,不修改 `py/base/`。 + +模块对外实现以下接口: + +- `init` +- `getName` +- `homeContent` +- `homeVideoContent` +- `categoryContent` +- `detailContent` +- `searchContent` +- `playerContent` + +模块内部使用 helper 收敛 URL 和解析逻辑: + +- `_build_url` + - 补全站点绝对地址 +- `_fetch_html` + - 统一请求页面并返回文本 +- `_load_html` + - 将 HTML 转成可 XPath 的文档对象 +- `_clean_text` + - 规范化文本、去掉多余空白和站点装饰词 +- `_extract_site_id` + - 从站点链接中提取原始资源 ID +- `_encode_vod_id` + - 将站点资源编码为仓库短 `vod_id` +- `_decode_vod_id` + - 将短 `vod_id` 还原为站点路径和资源类型 +- `_encode_play_id` + - 将播放资源编码为短播放 ID +- `_decode_play_id` + - 将短播放 ID 还原为直链参数 +- `_parse_home_items` + - 首页推荐解析 +- `_parse_list_cards` + - 分类和搜索卡片解析 +- `_parse_rank_detail` + - 排行榜详情解析 +- `_parse_song_detail` + - 歌曲详情解析 +- `_parse_mv_detail` + - MV 详情解析 +- `_parse_playlist_detail` + - 歌单详情解析 +- `_parse_singer_detail` + - 歌手详情解析 + +## 站点配置 + +固定站点配置如下: + +- 站点名:`世纪音乐` +- 根地址:`https://www.4c44.com` +- 默认请求头包含桌面浏览器 `User-Agent` +- 默认请求头带 `Referer: https://www.4c44.com/` + +固定首页分类如下: + +- `home -> 首页推荐` +- `rank_list -> 排行榜` +- `playlist -> 歌单` +- `singer -> 歌手` +- `mv -> MV` + +首版保留与参考实现一致的内容面,但不引入运行时缓存层。 + +## ID 设计 + +为避免在列表层暴露完整 URL,统一使用仓库短 ID。 + +`vod_id` 编码规则: + +- `rank:<榜单标识>` +- `song:` +- `mv:` +- `playlist:` +- `singer:` + +`play_id` 编码规则: + +- `music:` +- `vplay::1080` + +内部路径还原规则: + +- `song:` -> `/mp3/.html` +- `mv:` -> `/mp4/.html` +- `playlist:` -> `/playlist/.html` +- `singer:` -> `/singer/.html` +- `rank:` -> `/list/.html` + +首页推荐卡片直接编码为真实短 ID,不依赖额外缓存命中。 + +## 首页设计 + +`homeContent` 返回: + +- `class` +- `filters` +- `list` + +首页推荐解析策略: + +- 请求首页 HTML +- 从歌曲推荐区提取歌曲卡片 +- 从 MV 推荐区提取 MV 卡片 +- 根据链接前缀判断资源类型 +- 统一映射为短 `vod_id` +- 按 `vod_id` 去重 + +首页推荐列表项统一字段: + +- `vod_id` +- `vod_name` +- `vod_pic` +- `vod_remarks` + +`homeVideoContent` 直接复用首页的 `list`,避免重复解析逻辑。 + +## 分类设计 + +`categoryContent` 支持以下分类: + +- `home` +- `rank_list` +- `playlist` +- `singer` +- `mv` + +返回结构遵循仓库当前约定: + +- `page` +- `limit` +- `total` +- `list` + +不返回 `pagecount`。 + +各分类策略如下: + +### `home` + +- 直接复用首页推荐列表 +- `page=1` +- `limit` 与 `total` 使用当前列表长度 + +### `rank_list` + +- 使用模块内固定榜单映射 +- 每页返回固定数量的榜单卡片 +- 榜单项 `vod_id` 为 `rank:<榜单标识>` +- 榜单项不依赖站点页面结构 + +### `playlist` + +- 根据筛选参数构造歌单页 URL +- 从歌单列表容器解析 `/playlist/` 链接 +- 列表项编码为 `playlist:` + +### `singer` + +- 根据筛选参数构造歌手页 URL +- 从歌手列表容器解析 `/singer/` 链接 +- 列表项编码为 `singer:` + +### `mv` + +- 根据筛选参数构造 MV 页 URL +- 从 MV 列表容器解析 `/mp4/` 链接 +- 列表项编码为 `mv:` + +筛选器首版沿用参考实现中的三组能力: + +- `singer` + - 性别、地区、字母 +- `mv` + - 地区、类型、排序 +- `playlist` + - 语种、风格 + +## 搜索设计 + +`searchContent` 行为如下: + +- 空关键词直接返回空结构 +- 非空关键词请求站点搜索页 +- 统一扫描搜索结果中的歌曲、MV、歌单、歌手链接 +- 根据链接前缀判断资源类型并编码为短 `vod_id` +- 按 `vod_id` 去重 + +返回结构: + +- `page` +- `limit` +- `total` +- `list` + +空关键词返回固定结果: + +- `{"page": 1, "limit": 0, "total": 0, "list": []}` + +## 详情设计 + +`detailContent` 按 `vod_id` 前缀分流,不依赖首页缓存。 + +### 排行榜详情 + +- 请求 `/list/.html` +- 解析榜单页歌曲链接 +- 每首歌生成 `music:` 播放 ID +- 输出单线路 `vod_play_from` + +### 歌曲详情 + +- 请求 `/mp3/.html` +- 解析标题、歌手、封面 +- 输出单条播放项 +- `vod_play_url` 格式为 `歌曲名$music:` + +### MV 详情 + +- 请求 `/mp4/.html` +- 解析标题、歌手、封面 +- 输出单条播放项 +- `vod_play_url` 格式为 `标题$vplay::1080` + +### 歌单详情 + +- 请求 `/playlist/.html` +- 解析歌单标题、封面和歌曲列表 +- 播放列表中的每个条目编码为 `music:` + +### 歌手详情 + +- 请求 `/singer/.html` +- 解析歌手名称、封面、简介和歌曲列表 +- 播放列表中的每个条目编码为 `music:` + +详情对象至少包含以下字段: + +- `vod_id` +- `vod_name` +- `vod_pic` +- `vod_remarks` +- `vod_content` +- `vod_play_from` +- `vod_play_url` + +站点能稳定提供时再补充: + +- `vod_actor` +- `type_name` + +## 播放设计 + +`playerContent` 只负责短播放 ID 到可播放 URL 的还原,不抓歌词。 + +规则如下: + +- `music:` + - 还原为 `https://www.4c44.com/data/down.php?ac=music&id=` +- `vplay::1080` + - 还原为 `https://www.4c44.com/data/down.php?ac=vplay&id=&q=1080` + +返回结构遵循仓库常见模式: + +- `parse=0` +- `url=<直链>` +- `header` + +默认请求头返回: + +- `User-Agent` +- `Referer` + +若播放 ID 无法解析,则返回空 URL 和 `parse=0`,不抛异常。 + +## 错误处理设计 + +错误处理原则为“优先返回空结果,不中断主链路”。 + +- 页面请求失败 + - 首页、分类、搜索返回空列表 +- 分类页结构变化 + - 当前分类返回空列表,不影响其他分类 +- 搜索页无匹配结果 + - 返回空列表 +- 详情页解析失败 + - 返回 `{"list": []}` +- 播放 ID 无法解析 + - 返回空 URL + +不在首版加入复杂重试和缓存回退。 + +## 测试设计 + +新增测试文件 `py/tests/test_世纪音乐.py`,使用 `unittest` 和 `unittest.mock`,不访问真实网络。 + +测试范围如下: + +- `homeContent` + - 返回固定分类与筛选器 + - 能从首页 HTML 解析歌曲和 MV 推荐 +- `homeVideoContent` + - 复用首页列表 +- `categoryContent` + - `rank_list` 返回固定榜单分页 + - `playlist` 能解析歌单卡片 + - `singer` 能解析歌手卡片 + - `mv` 能解析 MV 卡片 + - 未知分类返回空结果 +- `searchContent` + - 空关键词返回空结果 + - 非空关键词能将歌曲、MV、歌单、歌手映射成短 `vod_id` +- `detailContent` + - `rank:` 能组装歌曲播放列表 + - `song:` 能组装歌曲详情 + - `mv:` 能组装 MV 详情 + - `playlist:` 能组装歌单详情 + - `singer:` 能组装歌手详情 +- `playerContent` + - `music:` 返回歌曲直链 + - `vplay::1080` 返回 MV 直链 + - 非法播放 ID 返回空 URL + +## 风险与约束 + +首版实现依赖站点当前 HTML 结构,因此有以下约束: + +- 列表解析对 CSS 类名和链接前缀有一定依赖 +- 歌单和歌手详情依赖页面内歌曲列表容器 +- 搜索结果若结构分散,需要在实现中优先选择稳定容器,并在测试中覆盖字段回退 + +这些风险都限制在站点模块内部,不扩散到公共层。