Files
tvboxzt/py/docs/superpowers/specs/2026-04-19-gimy-spider-design.md
T

413 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<tid>.html`
-`page > 1` 或排序不为 `time` 时追加 `?page=<pg>&by=<sort>`
返回分页字段:
- `page = 当前页`
- `pagecount = pg + 1`,当页命中数量达到列表页常规规模时递增
- `limit = 20`
- `total = pg * 20 + 当前条数`
这里的总数是近似值,和当前仓库其他 HTML 站点的处理方式保持一致,不承诺真实总数。
## 文本清洗与繁转简
站点内容与搜索结果里可能混有繁体字。为了保证搜索命中率和展示稳定性,脚本内部会提供两层处理:
- `normalizeText`
- 去 HTML 标签
-`&nbsp;`
- 压缩空白
- `convertTraditionalToSimplified`
- 使用内置映射表做精简繁转简
不引入 `opencc` 或其他第三方库,原因是:
- 当前仓库未配置相关依赖
- 本次只需要覆盖站点常见影视字段和搜索词,不需要通用全文转换
- 减少安装和运行环境差异
映射表优先覆盖:
- 影视分类词
- 常见标题用字
- 搜索噪声词
展示用文本通过 `toDisplayText` 统一进入“清洗 + 繁转简”链路。
## 搜索设计
搜索 URL
- `/find/-------------.html?wd=<keyword>&page=<pg>`
搜索流程分两步:
1. 解析原始卡片列表
2. 对原始列表做关键词精筛
关键词精筛策略保留参考实现的核心思路:
- 对关键词和片名统一做繁转简
- 去空格、破折号、标点和括号
- 移除常见噪声后缀,例如:
- `线上看`
- `全集`
- `连续剧`
- `电视剧`
- `动漫`
- `电影`
- `综艺`
- 衍生 token
- 原始归一化词
- 去噪声词后的词
- 去掉结尾集数或纯数字后的词
- 打分规则:
- 完全相等最高
- 前缀匹配次之
- 包含匹配再次之
- 片名较短且被 token 包含作为弱命中
若打分结果为空,再回退到宽松包含匹配,避免误把合法结果全部过滤掉。
## 详情页设计
详情 URL 模式:
- `/detail/<id>.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`
字段提取策略:
- 标题优先取详情页主标题,回退页面 `<title>`
- 封面优先取 `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. 最后运行站点测试集合做回归确认