From be456a02d97187949be661125e91666529966b86 Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Mon, 20 Apr 2026 10:44:05 +0800 Subject: [PATCH] docs: add zxzjhd spider design --- .../specs/2026-04-20-zxzjhd-spider-design.md | 375 ++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 py/docs/superpowers/specs/2026-04-20-zxzjhd-spider-design.md diff --git a/py/docs/superpowers/specs/2026-04-20-zxzjhd-spider-design.md b/py/docs/superpowers/specs/2026-04-20-zxzjhd-spider-design.md new file mode 100644 index 0000000..e00094a --- /dev/null +++ b/py/docs/superpowers/specs/2026-04-20-zxzjhd-spider-design.md @@ -0,0 +1,375 @@ +# 在线之家 Python 爬虫设计 + +## 目标 + +在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的在线之家站点爬虫,覆盖以下能力: + +- 首页分类与筛选 +- 分类列表 +- 搜索 +- 详情解析 +- 播放解析 + +实现基于站点 HTML 页面结构,不引入 JS 服务端路由层,不修改 `base/` 公共层,并且保留详情页中的网盘分享线路。 + +## 范围 + +本次实现包含: + +- 新增独立脚本,文件名为 `在线之家.py` +- 使用单一站点主域:`https://www.zxzjhd.com` +- 支持 `home/category/detail/search/player` 主链路 +- 首页返回固定分类与静态筛选配置 +- 详情页同时输出普通播放线路和网盘分享线路 +- `playerContent` 对普通线路执行 `zxzj` 解密,对网盘线路直接透传分享链接 + +本次实现不包含: + +- 参考 JS 中的 Fastify 路由封装 +- 网盘驱动对象注入、驱动缓存映射、网盘目录展开 +- 浏览器自动化或 JS 执行环境 +- 多域名探活与自动切换 +- 登录态、Cookie 常驻维护、反爬绕过 + +## 方案选择 + +采用仓库现有的“单站点单文件 + 单测”方案: + +- 对外保持 `Spider` 接口兼容 +- 对内拆分为请求封装、筛选路径拼接、列表解析、详情解析、播放解析几个 helper +- 保留参考 JS 规则里的关键业务行为,但只实现 Python Spider 实际需要的部分 + +不引入 JS 版 drive 处理层的原因是: + +- 当前 Python 仓库没有统一的 drive 注入协议 +- 现有同类蜘蛛对网盘资源的惯例是保留线路名与分享链接,让上层插件处理 +- 本次重点是 HTML 解析、数据整形和 `zxzj` 播放解密,不是中间层编排 + +## 模块边界 + +新增脚本只在站点文件内部维护逻辑,不修改 `base/`。 + +脚本内部职责拆分如下: + +- `init` + - 初始化主域、请求头、固定分类、筛选项与筛选默认值 +- `homeContent` + - 返回固定 `class` 与 `filters` +- `homeVideoContent` + - 返回空列表,不额外抓首页推荐 +- `categoryContent` + - 根据分类、筛选和分页拼出 URL,请求 HTML 并解析卡片 +- `searchContent` + - 根据关键词请求搜索页并解析结果 +- `detailContent` + - 请求详情页,提取影片元数据、普通播放线路和网盘线路 +- `playerContent` + - 对 `zxzj` 普通线路做播放解密,对网盘线路直接透传分享链接 +- 私有辅助函数 + - 补全相对地址 + - 清理文本 + - 修复 JSON 包裹 HTML + - 拼接筛选路径 + - 解析卡片列表 + - 提取详情字段 + - 提取标签页与剧集列表 + - 提取播放页中的网盘分享链接 + - 识别网盘类型 + - 执行 `zxzj` 解密 + +## Host 与请求策略 + +本次只实现单域: + +- `https://www.zxzjhd.com` + +请求统一通过 `self.fetch` 发起,固定请求头至少包含: + +- `User-Agent` +- `Referer: https://www.zxzjhd.com/` + +请求原则: + +- HTML 请求超时固定为 10 秒 +- 请求失败时返回空 HTML 或空结果,不抛出未处理异常 +- 对字符串响应先尝试修复 JSON 包裹 HTML 的情况,避免站点返回 `""` 这种包裹值时解析失败 +- 不引入浏览器执行,也不依赖外部解压、解密服务 + +## 分类与筛选设计 + +首页分类固定为: + +- `1 -> 电影` +- `2 -> 美剧` +- `3 -> 韩剧` +- `4 -> 日剧` +- `5 -> 泰剧` +- `6 -> 动漫` + +筛选配置直接内置到脚本中,字段与参考实现保持一致: + +- `class` +- `area` +- `year` +- `by` + +筛选默认值保留 `cateId`,通过分类 id 决定。 + +筛选 URL 规则采用模板拼接: + +- `{{fl.cateId}}-{{fl.area}}-{{fl.by}}-{{fl.class}}-----fypage---{{fl.year}}` + +输出路径再包到: + +- `/vodshow/.html` + +规则说明: + +- `cateId` 来自当前分类默认配置 +- `class/area/year/by` 来自传入的 `extend` +- 缺失筛选值时保留空段,不重排位置 +- 当前仓库约定不返回 `pagecount`,分类结果只返回 `list/page/limit/total` + +`homeContent` 返回: + +- `class` +- `filters` + +不返回首页推荐列表。 + +## 列表与搜索设计 + +分类页和搜索页均解析 `stui-vodlist` 卡片结构。 + +卡片提取字段: + +- 标题:`a[title]` +- 图片:`data-original`,缺失时回退到 `data-src` 或 `src` +- 备注:`.pic-text` +- 链接:`a[href]` + +输出统一卡片结构: + +- `vod_id` +- `vod_name` +- `vod_pic` +- `vod_remarks` + +其中 `vod_id` 保留短详情路径,而不是完整 URL,形式为: + +- `voddetail/12345.html` + +或等价的相对详情路径片段。 + +这样可以遵循仓库当前短 id 约定,避免把完整域名泄露到列表结果里。 + +搜索接口使用: + +- `/vodsearch/<关键词>-------------.html` + +搜索直接请求完整搜索页 HTML,不做 POST,也不做二次过滤。 + +分页策略采用保守估计: + +- 分类页 `limit` 固定为 24 +- 搜索结果 `limit` 使用结果数 +- `total` 采用 `page * 30 + len(items)` 的仓库常见保守写法 +- 不返回 `pagecount` + +## 详情页设计 + +详情页解析以下字段: + +- 标题 +- 封面 +- 简介 +- 年份 +- 地区 +- 类型 +- 导演 +- 主演 +- 更新信息或集数备注 + +详情输出字段至少包含: + +- `vod_id` +- `vod_name` +- `vod_pic` +- `vod_content` +- `vod_remarks` +- `vod_year` +- `vod_area` +- `vod_class` +- `vod_lang` +- `vod_director` +- `vod_actor` +- `vod_play_from` +- `vod_play_url` + +图片地址统一补全为完整 URL,修复站点相对路径与 `//` 形式地址。 + +详情页播放来源分为两组: + +1. 普通站内线路 + - 来源于详情页的播放标签和对应剧集列表 + - 不保留原站复杂线路名,统一归并成 `zxzj` + - 每条剧集格式为 `名称$` + - 同一普通线路下多集使用 `#` 连接 +2. 网盘线路 + - 来源于详情页标签中明显标记为网盘、百度、夸克、UC、阿里、迅雷的线路 + - 详情页中的剧集链接先指向站内播放页,不直接给出分享链接 + - 需要继续请求这些播放页,读取其中 `player_aaaa.url` + - 如果 `player_aaaa.url` 是分享地址,则按网盘类型分组保留 + +网盘线路识别范围: + +- `baidu` +- `quark` +- `uc` +- `aliyun` +- `xunlei` + +网盘类型优先通过分享链接域名识别: + +- `pan.baidu.com` -> `baidu` +- `pan.quark.cn` -> `quark` +- `drive.uc.cn` -> `uc` +- `alipan.com` 或 `aliyundrive.com` -> `aliyun` +- `pan.xunlei.com` -> `xunlei` + +如果标签名称与域名不一致,以分享链接域名为准。 + +网盘线路保留策略: + +- 每种网盘类型单独作为一条线路 +- 每个条目格式为 `剧集名$分享链接` +- 按分享链接去重,保留第一次出现的标题 +- 没有有效分享链接的网盘项直接跳过 + +最终线路组合规则: + +- `vod_play_from` 为 `zxzj$$$quark$$$baidu` 这类拼接形式 +- `vod_play_url` 与线路顺序严格对应 +- 没有普通线路时,不强行保留 `zxzj` +- 没有网盘线路时,只返回普通线路 + +## 播放设计 + +`playerContent(flag, id, vipFlags)` 按来源分流。 + +### 普通线路 `zxzj` + +输入 `id` 为相对播放路径或短播放 id 时,先补全为完整播放页 URL,再执行以下流程: + +1. 请求播放页 +2. 提取页面中指向 `jx.zxzj` 的中间页 URL +3. 请求中间页 +4. 提取 `result_v2` JSON 中的加密串 +5. 执行参考实现中的解密算法: + - 先翻转字符串 + - 按两位十六进制转字符 + - 移除中间插入的 7 位混淆字符 +6. 如果解密结果为媒体直链,则返回直链 + +成功返回: + +- `parse = 0` +- `jx = 0` +- `url = 解密后的媒体地址` +- `header.Referer = 中间页 URL` + +如果任一步失败,则回退为原播放页地址,返回: + +- `parse = 1` +- `jx = 1` +- `url = 播放页 URL` + +### 网盘线路 + +当 `flag` 为以下值之一时视为网盘线路: + +- `baidu` +- `quark` +- `uc` +- `aliyun` +- `xunlei` + +对网盘线路不做二次解析,直接透传分享链接: + +- `parse = 0` +- `jx = 0` +- `url = 原始分享链接` +- `header = {}` + +这样可以保持和仓库内现有带网盘蜘蛛的一致行为,让上层网盘插件继续接管。 + +## 解析与兼容性策略 + +为了降低页面结构波动影响,实现上采用“XPath/HTML 结构优先,正则兜底”的策略: + +- 列表与搜索主走 XPath +- 详情字段主走 XPath 或相邻文本提取 +- 播放页 `player_aaaa` 与中间页 `result_v2` 使用正则提取 +- 网盘分享链接通过播放页内嵌 JSON 提取 + +文本处理原则: + +- 缺失字段返回空字符串 +- 标题、备注、简介统一做空白折叠 +- 多值字段使用逗号连接 +- 简介保留段落换行,不保留多余空白 + +URL 处理原则: + +- 相对路径补全为完整 URL +- 列表和搜索输出短 `vod_id` +- 详情内普通剧集输出短 `play-id` +- 网盘线路保留完整分享链接 + +## 测试设计 + +新增测试文件: + +- `tests/test_在线之家.py` + +测试全部使用 `unittest` 和 `unittest.mock`,不访问真实网络,覆盖以下行为: + +1. 首页 + - 断言分类 id 和筛选 key 正确输出 +2. 分类 URL + - 断言筛选模板拼接出的路径符合站点规则 +3. 列表解析 + - 断言能提取短 `vod_id`、标题、封面和备注 +4. 搜索解析 + - 断言能解析搜索页卡片 +5. 详情解析 + - 断言能提取标题、封面、简介、年份、地区、类型、导演、主演 + - 断言普通线路统一输出为 `zxzj` + - 断言网盘播放页会被继续请求并转成 `baidu/quark/uc/aliyun/xunlei` 线路 + - 断言网盘链接按分享链接去重 +6. 播放解密 + - 断言 `zxzj` 解密算法能还原媒体地址 + - 断言 `playerContent` 对普通线路成功返回直链 + - 断言解密失败时回退到播放页地址 +7. 网盘播放分流 + - 断言 `playerContent` 对 `baidu/quark/uc/aliyun/xunlei` 直接透传分享链接 + +## 风险与约束 + +- 站点反爬较强,真实页面可能偶发返回包裹字符串或异常结构,因此实现只保证对已知 HTML 结构稳健解析 +- 网盘分享链接需要额外请求网盘播放页,详情阶段请求数会比普通站点更高 +- 普通线路统一命名为 `zxzj` 是刻意收敛行为,丢弃了站点原始标签名,但换来更稳定的播放分流 +- 该实现不承担网盘目录展开和鉴权,依赖上层现有网盘插件消费分享链接 + +## 验收标准 + +完成后应满足以下结果: + +- 新增 `在线之家.py`,且不修改 `base/` +- `home/category/search/detail/player` 五条主链路都有单测覆盖 +- 列表和搜索输出短 `vod_id` +- 详情页能同时输出普通线路和网盘线路 +- 网盘线路按 `baidu/quark/uc/aliyun/xunlei` 标准名返回 +- `playerContent("zxzj", ...)` 能执行解密并优先返回直链 +- `playerContent(<网盘线路>, ...)` 直接返回分享链接 +- 相关单测可在本仓库的 `unittest` 环境中稳定运行