docs: add 四万影视 spider design

This commit is contained in:
Harold
2026-04-24 18:11:05 +08:00
parent 5e49b8f4fa
commit dcf57a11e1
@@ -0,0 +1,293 @@
# 四万影视 Spider 设计
## 目标
在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的 `四万影视` spider,参考用户提供的 JS 采集脚本,接入 `https://40000.me/api/maccms`,实现分类、搜索、详情和播放能力。
## 范围
本次实现包含:
- 新增 `py/四万影视.py`
- 新增 `py/tests/test_四万影视.py`
- 固定主分类与静态筛选
- 分类接口请求与列表字段标准化
- 搜索接口请求与列表字段标准化
- 详情接口请求与多线路播放字段组装
- 播放直链透传与非直链 `parse=1` 回退
本次实现不包含:
- 首页推荐抓取
- 动态分类发现
- 站点 HTML 页面解析
- 真实联网测试
- 第三方解析器实现
## 目标接口形态
Spider 将实现以下标准方法:
- `homeContent(filter)`
- `homeVideoContent()`
- `categoryContent(tid, pg, filter, extend)`
- `searchContent(key, quick, pg="1")`
- `detailContent(ids)`
- `playerContent(flag, id, vipFlags)`
遵循当前仓库约定:
- 单文件 spider
- 使用 `fetch` 发起请求,不改共享基类
- 列表与搜索结果不返回 `pagecount`
- 详情结果使用 `vod_play_from` / `vod_play_url`
## 分类与筛选
沿用参考脚本的固定分类 ID 与名称:
- `20` 电影
- `30` 电视剧
- `39` 动漫
- `45` 综艺
- `32` 欧美
筛选为静态定义,不从远端动态读取。
每个主分类都返回三类筛选:
- `subType`
- 使用参考脚本中的子分类映射
- 默认值为当前主分类 `type_id`
- `year`
- `全部``2026``2000`
- `sort`
- `time`
- `hits`
- `score`
- `up`
Python 版本筛选值沿用仓库现有格式:
- `{"n": "显示名", "v": "实际值"}`
## 数据模型
### 列表项
分类和搜索返回的每个视频项统一标准化为:
- `vod_id`
- `vod_name`
- `vod_pic`
- `vod_remarks`
- `vod_year`
- `type_name`
字段来源以 maccms JSON 为准。
额外规范:
- `vod_pic` 为空时回退到 `https://40000.me/public/favicon.png`
- `type_name` 优先使用接口值,缺失时按固定主分类映射兜底
- `vod_actor` 中的 `'` 还原为 `'`
- 演员列表中的逗号空白归一化为英文逗号
### 详情项
详情页输出保持仓库当前约定,不保留 JS 版本里的 `vod_play_sources` 数组,而是转换为:
- `vod_play_from`
- `vod_play_url`
组装规则:
- 上游 `vod_play_from``$$$` 拆成线路名
- 上游 `vod_play_url``$$$` 拆成线路内容
- 每条线路内部按 `#` 拆分剧集
- 每个剧集按 `剧集名$playId` 编码
- 若某条线路名缺失,回退为 `线路N`
- 若某个剧集标题缺失,回退为 `第N集`
最终:
- `vod_play_from = 线路A$$$线路B`
- `vod_play_url = 第1集$playId#第2集$playId$$$正片$playId`
### 播放 ID
播放 ID 不再额外编码,直接复用接口里的剧集值:
- 若值本身是 `http/https` 链接,则播放器直接返回
- 若值不是完整 URL,则播放器返回 `parse=1` 交给外部解析
## 请求映射
Spider 只请求一个接口端点:
- `https://40000.me/api/maccms`
### homeContent
不请求远端,直接返回固定分类和固定筛选。
### homeVideoContent
按用户确认,固定返回:
- `{"list": []}`
### categoryContent
请求参数:
- `ac=detail`
- `t=<subType>`
- `pg=<page>`
- `h=<year>`,仅在年份非空时传入
- `by=<sort>`
其中:
- `subType` 来自 `extend["subType"]`,缺失时回退 `tid`
- `sort` 来自 `extend["sort"]`,默认 `time`
- `year` 来自 `extend["year"]`
返回:
- `page`
- `limit`
- `total`
- `list`
`limit` 使用当前返回列表长度,`total` 优先使用接口 `total`,缺失时回退为列表长度。
### searchContent
请求参数:
- `ac=detail`
- `wd=<keyword>`
- `pg=<page>`
空关键字直接返回空列表。
返回:
- `page`
- `limit`
- `total`
- `list`
### detailContent
请求参数:
- `ac=detail`
- `ids=<videoId>`
流程:
1. 读取第一条详情记录
2. 标准化基础字段
3. 解析 `vod_play_from` / `vod_play_url`
4. 输出单个详情对象
重点字段:
- `vod_id`
- `vod_name`
- `vod_pic`
- `type_name`
- `vod_year`
- `vod_area`
- `vod_director`
- `vod_actor`
- `vod_content`
- `vod_remarks`
- `vod_play_from`
- `vod_play_url`
### playerContent
处理规则保持与参考脚本一致:
1. 空播放 ID 返回:
- `parse = 1`
- `jx = 1`
- `url = ""`
2.`id``http://``https://` 开头:
- `parse = 0`
- `jx = 0`
- `url = id`
3. 否则:
- `parse = 1`
- `jx = 1`
- `url = id`
播放器返回头固定包含:
- `User-Agent`
- `Referer: https://40000.me/`
## 架构与辅助函数
`py/四万影视.py` 保持单类实现,但拆成小 helper,避免方法过长。
建议 helper
- `_build_filters()`
- 生成固定筛选配置
- `_type_name_by_id(type_id)`
- 固定主分类名称映射
- `_normalize_vod(item)`
- 统一标准化列表/详情基础字段
- `_api_get(params)`
- 调用 maccms JSON 接口
- 非 200 或非对象响应时抛出 `ValueError`
- 上层入口方法负责捕获并返回空结果
- `_parse_play_groups(item)`
- 把上游播放源字段转换成仓库需要的 `vod_play_from` / `vod_play_url`
- `_page_result(items, page, total)`
- 构造列表返回体
## 错误处理
- 请求失败时不向外抛未处理异常
- 分类失败返回:
- `{"page": 当前页, "limit": 0, "total": 0, "list": []}`
- 搜索失败返回:
- `{"page": 当前页, "limit": 0, "total": 0, "list": []}`
- 详情失败返回:
- `{"list": []}`
- 播放失败返回:
- `{"parse": 1, "jx": 1, "url": "", "header": {...}}`
这样与仓库现有 spider 的防御性风格保持一致。
## 测试策略
使用 `unittest``unittest.mock`,不走真网。
至少覆盖以下行为:
1. `homeContent` 返回预期分类 ID 与筛选 key
2. `_build_filters` 生成 `subType/year/sort` 三组筛选
3. `_api_get` 正确请求 `/api/maccms`,并处理非 200 / 非对象响应
4. `categoryContent` 正确映射 `subType/year/sort/page`
5. `categoryContent` 不返回 `pagecount`
6. `searchContent` 正确映射 `wd` 和分页
7. `searchContent` 在空关键字时直接返回空列表
8. `detailContent` 正确提取详情基础字段并组装 `vod_play_from` / `vod_play_url`
9. `_parse_play_groups` 能处理多线路、多剧集、缺失线路名与缺失剧集名
10. `playerContent` 对直链返回 `parse=0`
11. `playerContent` 对非直链返回 `parse=1`
## 验收标准
- 新增 `py/四万影视.py``py/tests/test_四万影视.py`
- 可独立运行 `python -m unittest tests.test_四万影视 -v`
- 分类、搜索、详情的离线夹具输出稳定
- `homeVideoContent()` 固定返回空列表
- 播放器对直链和非直链的行为与参考脚本一致
- 不修改共享基类即可完成接入