From bb3d89405beb605d2f5cba96ad17f3b952471fbc Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Mon, 20 Apr 2026 11:38:33 +0800 Subject: [PATCH] docs: add heimao app spider design --- .../2026-04-20-heimao-app-spider-design.md | 364 ++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 py/docs/superpowers/specs/2026-04-20-heimao-app-spider-design.md diff --git a/py/docs/superpowers/specs/2026-04-20-heimao-app-spider-design.md b/py/docs/superpowers/specs/2026-04-20-heimao-app-spider-design.md new file mode 100644 index 0000000..fdaabce --- /dev/null +++ b/py/docs/superpowers/specs/2026-04-20-heimao-app-spider-design.md @@ -0,0 +1,364 @@ +# 黑猫 APP Python 爬虫设计 + +## 目标 + +在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的黑猫 APP 爬虫,覆盖以下能力: + +- 首页分类与筛选 +- 分类列表 +- 搜索 +- 详情解析 +- 播放解析 + +实现以用户提供的 Node/JS 版本为行为参考,但落地形式遵循当前仓库的单文件 Spider 约定。 + +## 范围 + +本次实现包含: + +- 新增独立脚本,文件名为 `黑猫APP.py` +- 使用黑猫 APP 的 AES-CBC 接口协议 +- 支持固定 `url`,并为后续动态 `site` 获取 host 预留扩展 +- 支持 `home/category/detail/search/player` 全链路 +- 支持分类排序、分类屏蔽和分类重命名 +- 支持地区融合和年份自动补充 +- 支持线路排序、线路重命名和线路屏蔽 +- 为新增行为补齐 `unittest` + +本次实现不包含: + +- Node/Fastify 路由层 +- OCR 服务联调或真实验证码识别 +- 修改 `base/` 公共层 +- 多站点自动探活 +- 本地缓存和重试机制 + +## 方案选择 + +采用仓库现有的“单站点单文件 + 单测”方案: + +- 对外保持 `Spider` 接口兼容 +- 对内拆分成配置、AES 加解密、API 调用、分类处理、详情解析、播放解析几个 helper +- 保留参考 JS 的主要业务分支,但移除与仓库无关的 HTTP 路由包装 + +不直接照搬参考 JS 导出层的原因是: + +- 当前仓库消费的是 Spider 方法,不是独立 HTTP 服务 +- 单文件 Spider 与现有项目结构一致,测试成本最低 +- Python 版重点应落在字段映射、短 ID 与播放分支,而不是请求分发 + +## 模块边界 + +新增脚本只在站点文件内部维护逻辑,不修改 `base/`。 + +脚本内部职责拆分如下: + +- `init` + - 初始化 host、API path、AES key/iv、UA、分类配置、地区融合配置、线路配置 +- `homeContent` + - 拉取初始化接口,输出分类与筛选 +- `homeVideoContent` + - 返回空列表 +- `categoryContent` + - 请求分类筛选接口,必要时执行大陆地区聚合 +- `searchContent` + - 请求搜索接口,执行本地结果过滤 +- `detailContent` + - 请求详情接口并整理影片元数据与播放列表 +- `playerContent` + - 根据线路模式、解析类型和 `vodParse` 结果输出播放信息 +- 私有辅助函数 + - AES 加解密 + - API 请求 + - 年份补全 + - 地区融合 + - 线路整理 + - OCR 预留 + +## Host 与请求策略 + +默认主域使用配置中的: + +- `http://app1-0-0.87333.cc` + +请求策略: + +- 接口路径按 `api=1` 组装为 `/api.php/getappapi.index/` +- 请求方法统一为 `POST` +- 请求头默认只带 `User-Agent` 与 `Accept-Encoding` +- 返回体中的 `data` 字段先做 AES-CBC 解密,再解析 JSON + +扩展策略: + +- 如果后续配置了 `site`,则先请求 `site` 取得真实 host,再继续后续接口请求 +- 若初始化接口中出现 `system_search_verify_status`,则把搜索验证码状态置为开启 + +异常策略: + +- 单次请求失败时尽量返回空结果,不向上抛出未处理异常 +- 某个详情接口失败时允许切换备用端点继续尝试 +- 播放解析失败时返回空 URL 或回退系统解析,而不是抛异常 + +## 配置设计 + +站点文件内部维护默认配置对象,至少包含: + +- `name` +- `url` +- `api` +- `dataKey` +- `dataIv` +- `init` +- `search` +- `version` +- `ua` +- `headers` +- `categories` +- `areaMerge` +- `ocr` + +分类管理配置: + +- `blockedNames` +- `renameMap` +- `forceOrder` + +地区融合配置: + +- `enabled` +- `displayName` +- `mergeList` + +线路管理配置: + +- 线路匹配关键字 +- 显示名 +- 排序权重 +- 解析模式 +- 是否启用 + +这样可以把黑猫的站点差异保持在站点文件内部,避免污染公共层。 + +## 分类与筛选设计 + +`homeContent` 的输入来源是初始化接口返回的 `type_list`。 + +分类处理规则: + +- 屏蔽名称包含在 `blockedNames` 里的分类,例如 `伦理` +- 对命中的分类名应用 `renameMap` +- 若配置了 `forceOrder`,则按配置顺序重排已保留分类 + +筛选转换规则: + +- 将接口中的 `class/area/lang/year/sort` 转成仓库常用的筛选结构 +- `sort` 对外统一映射为 `by` +- 显示名映射为 `类型/地区/语言/年份/排序` + +特殊筛选处理: + +- 地区筛选启用融合时,把 `中国大陆/大陆/内地` 合并显示为单个 `大陆` +- 年份筛选自动补入当前年份;若列表里无当前年份,则插入到 `全部` 后面 + +首页返回字段: + +- `class` +- `filters` +- `list` + +其中 `list` 直接复用初始化返回的分类推荐数据。 + +## 列表与搜索设计 + +分类列表使用接口: + +- `typeFilterVodList` + +请求参数包括: + +- `type_id` +- `page` +- `area` +- `year` +- `sort` +- `lang` +- `class` + +对外筛选字段中的 `by` 在请求时还原成接口所需的 `sort`。 + +大陆地区融合开启且用户选择融合值时: + +- 不直接请求一次 `area=大陆` +- 改为依次请求 `中国大陆/大陆/内地` +- 按 `vod_id` 去重后合并结果 + +搜索使用接口: + +- `searchList`,也允许通过配置覆盖 + +搜索额外规则: + +- 若初始化要求验证码,且 OCR 关闭或识别失败,则返回空列表和错误信息 +- 本地过滤掉 `vod_class` 包含 `伦理` 的结果 +- 如果有搜索词,则只保留标题、备注或分类文本中包含关键词的结果 + +分页返回字段遵循仓库当前约定: + +- `page` +- `limit` +- `total` +- `list` + +不返回 `pagecount`。 + +## 详情设计 + +详情优先尝试两个端点: + +- `vodDetail` +- `vodDetail2` + +只要任一端点成功即可继续解析。 + +输出字段至少包含: + +- `vod_id` +- `vod_name` +- `vod_pic` +- `vod_remarks` +- `vod_content` +- `vod_actor` +- `vod_director` +- `vod_year` +- `vod_area` +- `vod_play_from` +- `vod_play_url` + +详情线路处理规则: + +- 线路名来自 `vod_play_list[].player_info.show` +- 若线路名包含 `防走丢/群/防失群/官网` 等无意义提示词,则回退为 `1线/2线...` +- 同名线路追加序号避免冲突 +- 被线路配置禁用的线路直接跳过 +- 线路显示名、排序权重和解析模式从线路配置读取 + +每个播放条目输出为: + +- `<剧集名>$<线路名>@@<模式>@@` + +其中 `payload` 由以下字段拼接: + +- `parse_api` +- `play_url` +- `token` +- `player_parse_type` +- `parse_type` + +多集用 `#` 连接,多线路用 `$$$` 连接。 + +## 播放解析设计 + +`playerContent` 输入的 `id` 实际承载详情阶段拼好的内部播放串。 + +解析顺序如下: + +第一层,线路被禁用: + +- 直接返回空地址 + +第二层,自动解析线路: + +- 若线路模式为 `auto` +- 则按配置里的解析器优先级调用对应 app 解析器 +- 任意解析器返回可用 `url` 即结束 + +第三层,按 `parse_type` 直接分支: + +- `parse_type == 0` + - 认为 `play_url` 已是直链 + - 返回 `parse=0` +- `parse_type == 2` + - 返回 `parse=1` + - URL 为 `parse_api + play_url` + +第四层,按 `player_parse_type == 2` 尝试解析接口: + +- 直接请求 `parse_api + play_url` +- 如果响应 JSON 中存在 `url`,则返回直链 + +第五层,回退 `vodParse`: + +- 先把 `play_url` 做 AES-CBC 加密 +- 调用 `vodParse` +- 从返回 JSON 里提取真实 URL + +返回字段统一为: + +- `parse` +- `jx` +- `url` +- `header` + +其中直链场景返回 `parse=0, jx=0`;需要二次解析的场景返回 `parse=1, jx=1`。 + +## 验证码与 OCR 设计 + +首版只做结构预留,不把 OCR 作为通过条件。 + +保留两个辅助函数: + +- `replaceCode` +- `getVerificationCode` + +行为约束: + +- 只有在初始化明确声明搜索需要验证码时才尝试获取 +- 若 `ocr.enabled` 为 `false`,则搜索直接返回空列表和错误信息 +- 不在单测中依赖真实 OCR 服务 + +## 测试设计 + +新增 `tests/test_黑猫APP.py`,覆盖最小可验证行为: + +- 初始化后首页分类会按配置过滤与重排 +- 地区筛选会把大陆相关值合并 +- 年份筛选会补入当前年份 +- 分类请求会把 `by` 映射为 `sort` +- 选择融合地区时会发起多次请求并按 `vod_id` 去重 +- 搜索会过滤 `伦理` 内容并做关键词匹配 +- 详情会清洗线路名、处理重复线路名并按排序输出 +- 播放解析会覆盖 `parse_type=0` +- 播放解析会覆盖 `parse_type=2` +- 播放解析会覆盖 `player_parse_type=2` 的直连分支 +- 播放解析会覆盖 `vodParse` 回退分支 + +测试实现手段: + +- 使用 `unittest` +- 通过 `patch.object` mock `_api_post`、`fetch`、`post` +- 使用固定 JSON fixture,不访问真实站点和 OCR 服务 + +## 风险与约束 + +主要风险: + +- 黑猫接口 host 可能变动 +- 某些接口返回结构可能在不同部署之间存在细微差异 +- OCR 验证码链路依赖外部服务,不适合首版强耦合 + +约束处理: + +- 通过站点配置与 helper 隔离 host、key、路径差异 +- 详情接口按双端点兜底 +- 搜索验证码默认走可关闭策略,避免首版被外部依赖卡死 + +## 验收标准 + +满足以下条件即可视为本次实现完成: + +- 新增 `黑猫APP.py`,可被仓库按普通 Spider 加载 +- 首页返回分类和筛选,且体现分类过滤、地区融合和年份补全 +- 分类、搜索、详情、播放四条链路都有对应单测 +- 单测不依赖真实网络 +- 新增结果字段格式与仓库现有 Spider 保持一致 +- 分类和搜索结果不返回 `pagecount`