docs: add FKTV spider design
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
# FKTV Python 爬虫设计
|
||||
|
||||
## 目标
|
||||
|
||||
在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的凡客TV爬虫,行为参考用户提供的 FKTV JS 版本,但实现形式遵循仓库现有的单文件 Spider 约定。
|
||||
|
||||
本次实现范围:
|
||||
|
||||
- 首页分类
|
||||
- 分类浏览
|
||||
- 搜索
|
||||
- 详情解析
|
||||
- 多线路剧集组装
|
||||
- 播放接口直链优先
|
||||
- 接口失败时回退详情页
|
||||
|
||||
本次不实现:
|
||||
|
||||
- 弹幕
|
||||
- 页面嗅探
|
||||
- 本机 `curl` 回退
|
||||
- 验证码绕过
|
||||
|
||||
## 方案选择
|
||||
|
||||
采用“详情页解析状态,播放时调用官方切换接口”的方案,而不是只依赖详情页内嵌 `play_links`。
|
||||
|
||||
原因如下:
|
||||
|
||||
- FKTV 的剧集和线路是两套独立状态,详情页内嵌 `play_links` 可能只对应当前默认剧集
|
||||
- 如果只复用默认 `play_links`,用户切换到别的剧集时容易串集
|
||||
- 参考实现的核心可靠性来自 `link_id + line_id` 组合后再请求官方播放接口
|
||||
- 在当前 Python 仓库里保留接口直链优先、失败回退页面,已经覆盖核心需求,不必移植 OmniBox 专属能力
|
||||
|
||||
## 模块边界
|
||||
|
||||
新增 `py/凡客TV.py`,不修改 `py/base/`。
|
||||
|
||||
模块对外实现:
|
||||
|
||||
- `init`
|
||||
- `getName`
|
||||
- `homeContent`
|
||||
- `homeVideoContent`
|
||||
- `categoryContent`
|
||||
- `searchContent`
|
||||
- `detailContent`
|
||||
- `playerContent`
|
||||
|
||||
模块内部 helper 负责:
|
||||
|
||||
- 请求头与 cookie 管理
|
||||
- HTML / JSON 请求
|
||||
- 列表卡片解析
|
||||
- 详情页状态脚本解析
|
||||
- 线路 tab 提取
|
||||
- `play_id` 编解码
|
||||
- 播放接口请求与结果归一化
|
||||
- 直链与回退结果构造
|
||||
|
||||
## 站点策略
|
||||
|
||||
固定站点参数:
|
||||
|
||||
- host:`https://fktv.me`
|
||||
- 固定桌面端 `User-Agent`
|
||||
- 固定 cookie:`_did=57nTmEknMZ146xw4KXGHDCHk1MjshRyY`
|
||||
|
||||
请求策略:
|
||||
|
||||
- 列表、搜索、详情页使用普通 GET
|
||||
- 播放切换接口使用 POST
|
||||
- 播放接口请求头必须包含:
|
||||
- `User-Agent`
|
||||
- `Referer`
|
||||
- `Origin`
|
||||
- `X-Requested-With`
|
||||
- `Content-Type: application/x-www-form-urlencoded; charset=UTF-8`
|
||||
- 固定 cookie
|
||||
|
||||
## 分类与首页
|
||||
|
||||
`homeContent` 返回固定分类:
|
||||
|
||||
- `1` 电影
|
||||
- `2` 剧集
|
||||
- `4` 动漫
|
||||
- `3` 综艺
|
||||
- `8` 短剧
|
||||
- `6` 纪录片
|
||||
- `7` 解说
|
||||
- `5` 音乐
|
||||
|
||||
`homeVideoContent` 不单独抓推荐,返回空列表,和仓库中多个站点保持一致。
|
||||
|
||||
分类 URL 固定为:
|
||||
|
||||
- `/channel?page={page}&cat_id={type_id}&page_size=32&order=new`
|
||||
|
||||
分类页解析规则:
|
||||
|
||||
- 优先从 `.meta-wrap` 所在卡片提取数据
|
||||
- 兜底解析 `.hover-wrap`
|
||||
- 提取详情链接中的 `/movie/detail/{id}`
|
||||
- 提取标题、封面、标签和备注
|
||||
- `vod_id` 保留短 ID,不返回完整 URL
|
||||
|
||||
返回结构遵循仓库当前约定:
|
||||
|
||||
- `page`
|
||||
- `limit`
|
||||
- `total`
|
||||
- `list`
|
||||
|
||||
不返回 `pagecount`。
|
||||
|
||||
## 搜索
|
||||
|
||||
搜索 URL 固定为:
|
||||
|
||||
- `/search?keyword=<urlencoded keyword>`
|
||||
|
||||
搜索页解析复用分类卡片逻辑:
|
||||
|
||||
- 提取详情短 ID、标题、封面、标签
|
||||
- 去重后返回
|
||||
- 关键字为空时直接返回空列表
|
||||
|
||||
搜索返回:
|
||||
|
||||
- `page`
|
||||
- `limit`
|
||||
- `total`
|
||||
- `list`
|
||||
|
||||
不返回 `pagecount`。
|
||||
|
||||
## 详情解析
|
||||
|
||||
详情页 URL 固定为:
|
||||
|
||||
- `/movie/detail/{vod_id}`
|
||||
|
||||
详情页需要同时解析 HTML 可见信息和页面脚本状态。
|
||||
|
||||
基础元信息:
|
||||
|
||||
- `vod_id`
|
||||
- `vod_name`
|
||||
- `vod_pic`
|
||||
- `vod_content`
|
||||
- `vod_remarks`
|
||||
|
||||
页面脚本状态:
|
||||
|
||||
- `movieId`
|
||||
- `linkId`
|
||||
- `links`
|
||||
- `play_links`
|
||||
- `play_error_type`
|
||||
|
||||
其中:
|
||||
|
||||
- `links` 代表剧集列表,是详情页组装剧集的主数据源
|
||||
- `play_links` 仅用于诊断和线路兜底,不作为目标剧集的真值来源
|
||||
|
||||
线路提取规则:
|
||||
|
||||
- 优先从带 `data-line` 的线路 tab 提取线路 ID 和线路名
|
||||
- 如果页面没有线路 tab,则退化使用 `play_links` 中的线路 ID 和名称
|
||||
- 如果两者都没有,则详情页返回空播放字段
|
||||
|
||||
剧集提取规则:
|
||||
|
||||
- 逐个读取 `links` 中的剧集项
|
||||
- 每个剧集至少提取 `id` 和展示名
|
||||
- 如果没有名称,则按 `name`、`title`、`id` 顺序兜底
|
||||
|
||||
详情页播放字段组装规则:
|
||||
|
||||
- `vod_play_from` 为线路名称列表,使用 `$$$` 拼接
|
||||
- `vod_play_url` 为每条线路下的剧集列表,使用 `#` 拼接单集、`$$$` 拼接线路
|
||||
- 每个单集条目格式为 `剧集名$play_id`
|
||||
|
||||
这里会为每条线路复用同一批剧集。原因是 FKTV 的线路和剧集是分离状态:
|
||||
|
||||
- 剧集由 `links` 定义
|
||||
- 线路由线路 tab 或 `play_links` 定义
|
||||
- 真正播放地址必须在 `playerContent` 中通过 `link_id + line_id` 实时换取
|
||||
|
||||
`vod_remarks` 只保留少量站点状态提示:
|
||||
|
||||
- `play_error_type == captcha` 时提示站点需要验证码
|
||||
- `play_error_type == need_vip` 时提示站点存在 VIP 限制
|
||||
|
||||
## Play ID 设计
|
||||
|
||||
`play_id` 不直接保存页面 URL,而是编码为一个紧凑 JSON 字符串,至少包含:
|
||||
|
||||
- `movie_id`
|
||||
- `link_id`
|
||||
- `line_id`
|
||||
- `line_name`
|
||||
- `episode_name`
|
||||
- `type`
|
||||
- `page`
|
||||
|
||||
设计目标:
|
||||
|
||||
- 播放时能准确定位目标剧集和目标线路
|
||||
- 即使详情阶段只拿到短 ID,也能回退到详情页
|
||||
- 在测试里可以稳定断言 `play_id` 的结构和往返解码行为
|
||||
|
||||
## 播放解析
|
||||
|
||||
`playerContent` 分三层处理。
|
||||
|
||||
第一层,直接透传:
|
||||
|
||||
- 如果传入就是 `.m3u8`、`.mp4`、`.flv` 等明显媒体直链,直接返回 `parse=0`
|
||||
|
||||
第二层,播放接口直链优先:
|
||||
|
||||
- 解码 `play_id`
|
||||
- 回读详情页,重新获取当前 `movieId`、默认 `linkId` 和页面状态
|
||||
- 使用 `play_id` 中的 `link_id` 作为目标剧集,缺失时回退页面默认 `linkId`
|
||||
- POST `https://fktv.me/movie/detail/{movie_id}`
|
||||
- 表单固定为 `link_id={link_id}&is_switch=1`
|
||||
- 从响应 JSON 的 `data.play_links` 中提取各线路直链
|
||||
- 如果指定了 `line_id`,则只返回对应线路
|
||||
- 如果没有指定 `line_id`,则返回该剧集下接口给出的全部线路
|
||||
|
||||
第三层,失败回退:
|
||||
|
||||
- 接口没有可播地址
|
||||
- 接口返回 `need_vip`
|
||||
- `play_id` 非法但还能推导出详情页
|
||||
- 详情页能打开但无法换取直链
|
||||
|
||||
这些场景统一返回 `parse=1`,回退到详情页地址。
|
||||
|
||||
## 播放结果规范
|
||||
|
||||
直链结果:
|
||||
|
||||
- `parse=0`
|
||||
- `playUrl=""`
|
||||
- `url` 为首个可播地址
|
||||
- `header` 至少包含:
|
||||
- `User-Agent`
|
||||
- `Referer`
|
||||
- `Origin`
|
||||
|
||||
当接口返回多线路时:
|
||||
|
||||
- 额外返回 `urls`
|
||||
- 每项格式为 `{"name": "<线路名>", "url": "<直链>"}`
|
||||
|
||||
回退结果:
|
||||
|
||||
- `parse=1`
|
||||
- `playUrl=""`
|
||||
- `url` 为详情页地址
|
||||
- `header` 带页面请求所需的 `User-Agent` 和 `Referer`
|
||||
|
||||
不实现页面嗅探,不返回 OmniBox 风格的额外字段。
|
||||
|
||||
## 测试策略
|
||||
|
||||
新增 `py/tests/test_凡客TV.py`,全部使用离线 HTML / JSON fixture 和 mock,不做真实联网。
|
||||
|
||||
重点覆盖:
|
||||
|
||||
- `homeContent` 返回固定分类
|
||||
- `categoryContent` 正确构造 `/channel` URL 并解析卡片
|
||||
- `searchContent` 正确构造 `/search` URL,空关键字直接返回空列表
|
||||
- 详情页状态解析:`movieId`、`linkId`、`links`、`play_links`、`play_error_type`
|
||||
- 线路 tab 提取和剧集名称兜底
|
||||
- `detailContent` 组装 `vod_play_from` / `vod_play_url`
|
||||
- `play_id` 编解码往返一致
|
||||
- `playerContent` 对直链直接返回
|
||||
- `playerContent` 请求官方切换接口并按 `line_id` 过滤
|
||||
- `playerContent` 在线路未指定时返回全部接口线路
|
||||
- `playerContent` 在 `need_vip`、空结果、非法标识等场景下回退详情页
|
||||
|
||||
## 风险与边界
|
||||
|
||||
- FKTV 详情页里的脚本状态依赖站点当前模板,本次只兼容参考结构
|
||||
- 固定 cookie 未来可能失效,失效后播放接口可能退化为页面回退
|
||||
- 如果站点把线路 tab 或脚本变量名称整体改版,详情组装和播放接口都需要同步调整
|
||||
- 不实现验证码处理,遇到 `captcha` 只给出提示并回退页面
|
||||
- 不做 `curl` 回退和页面嗅探,意味着接口彻底失效时只能降级到页面解析
|
||||
Reference in New Issue
Block a user