diff --git a/py/docs/superpowers/specs/2026-04-19-gimy-spider-design.md b/py/docs/superpowers/specs/2026-04-19-gimy-spider-design.md new file mode 100644 index 0000000..e45035e --- /dev/null +++ b/py/docs/superpowers/specs/2026-04-19-gimy-spider-design.md @@ -0,0 +1,412 @@ +# Gimy剧迷 Python 爬虫设计 + +## 目标 + +在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的 Gimy剧迷站点爬虫,覆盖以下能力: + +- 首页分类 +- 首页推荐视频 +- 分类列表 +- 搜索 +- 详情解析 +- 播放解析 + +实现基于网页 HTML 抓取与播放页脚本解析,不依赖 Playwright,不修改 `base/` 公共层。 + +## 范围 + +本次实现包含: + +- 新增独立脚本,文件名为 `剧迷.py` +- 使用单一站点主域:`https://gimyai.tw` +- 支持 `home/homeVideo/category/detail/search/player` 全链路 +- 支持分类排序筛选 +- 支持多线路详情播放列表 +- 支持搜索结果精筛 +- 支持播放页直链提取与 `parse.php` 回退解析 + +本次实现不包含: + +- 多域名自动回退 +- Cloudflare 或其他风控绕过 +- 第三方繁转简依赖 +- 参考 JS 中全部兼容分支的 1:1 平移 +- 通用电影网站抽象层 + +## 方案选择 + +采用仓库风格的单文件拆分方案: + +- 对外保持当前仓库统一的 `Spider` 接口 +- 对内拆成列表解析、详情解析、搜索精筛、播放解析、文本清洗与繁转简几个独立 helper +- 优先复用当前仓库里 HTML 站点的实现风格,而不是逐行翻译参考 JS + +不做逐行平移的原因是: + +- 当前仓库以单站点单文件为主,便于维护和测试 +- 参考 JS 中包含动态依赖探测和运行时日志逻辑,Python 版只保留真正影响解析结果的核心链路 +- 本次目标是先把常见场景稳定覆盖,而不是追求所有历史兼容分支同步迁移 + +## 模块边界 + +新增脚本只在站点文件内部维护逻辑,不修改 `base/`。 + +脚本内部职责拆分如下: + +- `init` + - 初始化主域、请求头、分类配置、筛选配置 +- `homeContent` + - 返回固定分类与筛选定义 +- `homeVideoContent` + - 抓取首页卡片,返回首页推荐列表 +- `categoryContent` + - 构造分类页 URL 并解析卡片 +- `detailContent` + - 请求详情页并整理影片元信息与多线路剧集列表 +- `searchContent` + - 请求站内搜索页,解析原始结果并做关键词精筛 +- `playerContent` + - 解析播放页脚本,优先直取媒体地址,失败时回退到 `parse.php` 或播放页 +- 私有辅助函数 + - URL 归一化 + - HTML 文本清洗 + - 精简繁转简映射 + - 列表卡片解析 + - 详情字段提取 + - 播放页脚本提取和 URL 解码 + - 搜索关键词归一化与打分 + +## Host 与请求策略 + +本次只实现单域: + +- `https://gimyai.tw` + +请求头固定为浏览器样式,至少包含: + +- `User-Agent` +- `Accept-Language` +- `Referer` +- `Accept` + +对播放页和 `parse.php` 请求,按需要补充: + +- `Origin` + +请求策略保持轻量: + +- 所有页面请求统一走 `self.fetch` +- 默认超时取 10 秒到 20 秒之间的站点级固定值 +- 非 200 响应统一回退为空 HTML 或在播放解析时进入回退流程 + +不做: + +- host 探活切换 +- 重试队列 +- 代理层改造 + +## 分类与筛选设计 + +分类固定为: + +- `1 -> 电影` +- `2 -> 电视剧` +- `4 -> 动漫` +- `29 -> 综艺` +- `34 -> 短剧` +- `13 -> 陆剧` + +筛选只支持排序字段 `by`,候选值为: + +- `time` +- `hits` +- `score` + +`homeContent` 返回: + +- `class` +- `filters` + +其中 `filter_def` 只在站点内部用于默认值,不额外暴露到返回结构。 + +## 首页与列表设计 + +### 首页推荐 + +`homeVideoContent` 直接请求首页 HTML,并复用列表卡片解析逻辑。 + +卡片解析范围: + +- 详情链接包含 `/detail/` +- 标题优先取 `title` +- 回退 `img alt` +- 再回退 `.title`、`.video-text`、标题标签文本或卡片文本 +- 封面优先取 lazyload 属性,再回退 `img src` 或背景图样式 +- 备注从卡片或父容器文本中抓取常见状态文案,例如: + - `更新至第X集` + - `全X集` + - `HD` + - `TC` + - `抢先版` + +首页推荐只返回前 24 条,保持与参考代码一致。 + +### 分类列表 + +分类 URL 模式: + +- `/genre/.html` +- 当 `page > 1` 或排序不为 `time` 时追加 `?page=&by=` + +返回分页字段: + +- `page = 当前页` +- `pagecount = pg + 1`,当页命中数量达到列表页常规规模时递增 +- `limit = 20` +- `total = pg * 20 + 当前条数` + +这里的总数是近似值,和当前仓库其他 HTML 站点的处理方式保持一致,不承诺真实总数。 + +## 文本清洗与繁转简 + +站点内容与搜索结果里可能混有繁体字。为了保证搜索命中率和展示稳定性,脚本内部会提供两层处理: + +- `normalizeText` + - 去 HTML 标签 + - 去 ` ` + - 压缩空白 +- `convertTraditionalToSimplified` + - 使用内置映射表做精简繁转简 + +不引入 `opencc` 或其他第三方库,原因是: + +- 当前仓库未配置相关依赖 +- 本次只需要覆盖站点常见影视字段和搜索词,不需要通用全文转换 +- 减少安装和运行环境差异 + +映射表优先覆盖: + +- 影视分类词 +- 常见标题用字 +- 搜索噪声词 + +展示用文本通过 `toDisplayText` 统一进入“清洗 + 繁转简”链路。 + +## 搜索设计 + +搜索 URL: + +- `/find/-------------.html?wd=&page=` + +搜索流程分两步: + +1. 解析原始卡片列表 +2. 对原始列表做关键词精筛 + +关键词精筛策略保留参考实现的核心思路: + +- 对关键词和片名统一做繁转简 +- 去空格、破折号、标点和括号 +- 移除常见噪声后缀,例如: + - `线上看` + - `全集` + - `连续剧` + - `电视剧` + - `动漫` + - `电影` + - `综艺` +- 衍生 token: + - 原始归一化词 + - 去噪声词后的词 + - 去掉结尾集数或纯数字后的词 +- 打分规则: + - 完全相等最高 + - 前缀匹配次之 + - 包含匹配再次之 + - 片名较短且被 token 包含作为弱命中 + +若打分结果为空,再回退到宽松包含匹配,避免误把合法结果全部过滤掉。 + +## 详情页设计 + +详情 URL 模式: + +- `/detail/.html` + +`detailContent` 支持传入: + +- 纯数字详情 id +- 完整详情 URL + +输出字段包含: + +- `vod_id` +- `vod_name` +- `vod_pic` +- `vod_content` +- `vod_remarks` +- `type_name` +- `vod_year` +- `vod_area` +- `vod_actor` +- `vod_director` +- `vod_play_from` +- `vod_play_url` + +字段提取策略: + +- 标题优先取详情页主标题,回退页面 `` +- 封面优先取 `og:image`,再回退详情图区域图片 +- 剧情简介优先取详情正文区域,回退 `meta description` +- `状态`、`类别`、`年代`、`国家/地区` 通过标签名前缀匹配提取 +- `主演` 与 `导演` 优先从带链接的列表区域提取并以逗号连接 + +### 多线路播放列表 + +详情页的线路和剧集采用 tab + playlist 容器结构: + +- 线路名从 `#playTab a[href^='#con_playlist_']` 提取 +- 每个 tab 内的剧集链接从 `/play/<id>.html` 提取 +- 单集格式为 `集名$play_id` + +最终按当前仓库约定输出: + +- `vod_play_from = 线路A$$$线路B` +- `vod_play_url = 第1集$100-1-1#第2集$100-1-2$$$正片$100-2-1` + +## 播放解析设计 + +播放 URL 模式: + +- `/play/<play_id>.html` + +`playerContent` 支持传入: + +- 紧凑 `play_id` +- 完整播放页 URL + +### 播放页数据提取 + +核心信息来自页面脚本中的 `player_data`: + +- `url` +- `encrypt` +- `from` + +提取方式: + +- 用正则抓取 `var player_data = {...}` +- JSON 解析失败则视为无数据 + +### URL 解码策略 + +保留参考实现的解码规则: + +- `encrypt = 0` + - 原样使用 +- `encrypt = 1` + - URL decode +- `encrypt = 2` + - base64 decode 后再尝试 URL decode + +### 播放优先级 + +1. 若解码后的 `rawUrl` 已经是以 `m3u8/mp4/flv/m4s` 结尾的媒体地址,直接返回: + - `parse = 0` + - `url = rawUrl` +2. 若 `rawUrl` 非空但不是直接媒体地址,则根据线路名构造解析器地址: + - 默认 `https://play.gimyai.tw/v/parse.php` + - `JD4K/JD2K/JDHG/JDQM` 走 `/d/parse.php` + - `NSYS` 走 `/n/parse.php` +3. 若 `parse.php` 返回 JSON,优先读取: + - `url` + - `video` + - `playurl` +4. 若解析器没有给出直链,但 `rawUrl` 自身是 http 地址,则返回: + - `parse = 1` + - `jx = 1` + - `url = rawUrl` +5. 全部失败时回退返回播放页地址,保证行为可用: + - `parse = 1` + - `jx = 1` + - `url = 播放页 URL` + +### 返回头策略 + +直链成功时按场景返回最小必要头: + +- 直接媒体地址: + - 保留浏览器 `User-Agent` + - `Referer = 播放页` +- `parse.php` 成功: + - `Referer = 对应解析页` + - `Origin = 解析页 origin` + +## 错误处理策略 + +站点脚本应尽量返回空结构或回退值,而不是把异常直接抛到上层。 + +原则如下: + +- 列表页请求失败时返回空列表分页 +- 详情页单条失败时可跳过当前 id,避免批量请求全失败 +- 播放解析失败时优先回退到播放页 URL +- JSON 解析失败一律回退空字典或空字符串 + +不做细粒度错误分类,只做站点级稳妥回退。 + +## 测试设计 + +新增测试文件: + +- `tests/test_剧迷.py` + +测试只做离线单元测试,使用 `patch` mock 网络请求,不依赖真实站点。 + +重点覆盖以下行为: + +1. `homeContent` + - 返回固定分类和 `filters` +2. 列表卡片解析 + - 能提取 `vod_id`、`vod_name`、`vod_pic`、`vod_remarks` +3. `detailContent` + - 能解析基础字段和多线路播放列表 +4. 搜索精筛 + - 简体关键词能命中繁体标题 + - 噪声后缀不会挤掉正确结果 +5. `playerContent` + - 直接媒体地址时返回 `parse=0` + - `parse.php` 成功时返回解析后的直链 + - `parse.php` 失败时能回退到原始 URL 或播放页 +6. 解码辅助函数 + - 覆盖 `encrypt=0/1/2` + +测试风格对齐: + +- `tests/test_dbku.py` +- `tests/test_youknow.py` + +## 非目标与风险 + +本次实现明确不覆盖: + +- 多 host 灾备 +- 强风控页面或验证码绕过 +- 站点未来改版后的自适应恢复 +- 通用繁转简库级别的高完整度转换 + +主要风险如下: + +- Gimy 的 DOM 类名若调整,列表和详情解析会失效 +- 播放解析依赖 `player_data` 和 `parse.php` 的现有格式,若接口切换需单独修复 +- 精简繁转简映射只覆盖常见影视词,极少数片名可能仍然需要宽松匹配兜底 + +## 实施顺序 + +推荐实施顺序如下: + +1. 先写 `tests/test_剧迷.py`,覆盖卡片、详情、搜索和播放关键行为 +2. 运行测试确认红灯 +3. 实现 `剧迷.py` 的基础结构与 helper +4. 逐步补齐搜索精筛与播放解析,直到测试转绿 +5. 最后运行站点测试集合做回归确认