docs: add tengxun spider design

This commit is contained in:
Harold
2026-04-23 20:52:34 +08:00
parent 4e1d725f08
commit c6e4366d40
@@ -0,0 +1,266 @@
# 腾讯视频 Python 蜘蛛设计
## 目标
在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的腾讯视频蜘蛛,覆盖以下能力:
- 首页分类与推荐
- 分类列表
- 搜索
- 详情解析
- 播放透传
实现以用户提供的 Node/JS 版本为行为参考,但落地形式遵循当前仓库的单文件 Spider 约定。
## 范围
本次实现包含:
- 新增独立脚本,文件名为 `腾讯视频.py`
- 首页使用腾讯频道页 HTML 抓取推荐卡片
- 分类页使用腾讯频道页 HTML 抓取卡片并支持常见筛选参数
- 详情页调用 `float_vinfo2` 与批量详情接口,组装单条播放线路
- 搜索调用 `MultiTerminalSearch`
- 播放阶段只做原始链接透传
- 为新增行为补齐 `unittest`
本次实现不包含:
- 参考代码中的 HTTP 路由导出层
- 任何 parse 相关逻辑
- 解析器线路生成
- 花絮/预告拆分为独立线路
- 公共 helper 抽象
## 方案选择
候选方案有三种:
1. 单文件直译版
2. 单文件加 `lxml` 解析列表页
3. 站点文件加多个私有 helper 重构
选用方案 1,并保留少量私有 helper。
原因:
- 与用户提供的参考实现最接近
- 列表页测试夹具最容易构造
- 不额外扩大实现范围
## 模块边界
新增脚本只在站点文件内部维护逻辑,不修改 `base/`
Spider 对外方法:
- `init`
- `getName`
- `homeContent`
- `homeVideoContent`
- `categoryContent`
- `detailContent`
- `searchContent`
- `playerContent`
站点内部私有 helper
- `_headers`
- 统一请求头
- `_parse_list_items`
- 从频道页 HTML 中提取卡片
- `_get_batch_video_info`
- 批量请求分集信息
- `_safe_json`
- 解析 JSON 和 JSONP 载荷
## 请求与 ID 设计
固定主域:
- `https://v.qq.com`
固定请求头:
- `User-Agent: PC_UA`
`vod_id` 设计:
- 首页直接使用卡片里的 `data-float`
- 分类页使用 `频道$sourceId`
- 搜索结果直接使用 `doc.id`
详情阶段:
- 如果传入的是 `频道$sourceId`,取 `$` 后面的值作为 `cid`
- 如果没有 `$`,直接把原值作为 `cid`
## 首页与分类设计
### 首页
首页请求:
- `/x/bu/pagesheet/list?_all=1&append=1&channel=cartoon&listpage=1&offset=0&pagesize=21&iarea=-1&sort=18`
解析规则:
- 用正则提取 `list_item`
-`img alt` 作为 `vod_name`
-`img src` 作为 `vod_pic`
- 取第一个链接文本作为 `vod_remarks`
-`a[data-float]` 作为 `vod_id`
首页只返回:
- `class`
- `list`
不返回 `filters`
### 分类
分类请求基础路径:
- `/x/bu/pagesheet/list`
固定参数:
- `_all=1`
- `append=1`
- `channel=<id>`
- `listpage=1`
- `offset=(page-1)*21`
- `pagesize=21`
- `iarea=-1`
可选筛选参数:
- `sort`
- `iyear`
- `year`
- `itype`
- `ifeature`
- `iarea`
- `itrailer`
- `gender`
分类页返回结构遵循当前参考实现:
- `list`
- `page`
- `pagecount`
- `limit`
- `total`
## 详情设计
详情请求:
- `https://node.video.qq.com/x/api/float_vinfo2?cid=<targetCid>`
详情字段映射:
- `vod_name <- json.c.title`
- `type_name <- json.typ`
- `vod_actor <- json.nam`
- `vod_year <- json.c.year`
- `vod_content <- json.c.description`
- `vod_remarks <- json.rec`
- `vod_pic <- json.c.pic`
播放列表生成:
1. 读取 `json.c.video_ids`
2. 调用批量接口:
- `https://union.video.qq.com/fcgi-bin/data?...&idlist=<vid1,vid2,...>`
3. 按原始 `video_ids` 顺序回填标题
4. 正片与花絮/预告不拆线路,统一合并为一条 `腾讯视频`
5. 每个播放项格式为:
- `标题$https://v.qq.com/x/cover/<cid>/<vid>.html`
最终详情仅保留一条线路:
- `vod_play_from = "腾讯视频"`
## 搜索设计
搜索请求:
- `https://pbaccess.video.qq.com/trpc.videosearch.mobile_search.MultiTerminalSearch/MbSearch?vplatform=2`
请求头:
- 桌面浏览器 UA
- `Content-Type: application/json`
- `Origin: https://v.qq.com`
- `Referer: https://v.qq.com/`
请求体字段沿用参考实现:
- `version`
- `clientType`
- `query`
- `pagenum`
- `pagesize`
- `extraInfo`
结果提取:
- 同时遍历 `data.normalList.itemList`
- 同时遍历 `data.areaBoxList[].itemList`
- 只保留 `doc.id` 存在且长度大于 11 的结果
- 标题去掉 `<em>` 标签
分页结构保持参考行为:
- `list`
- `page`
- `pagecount`
- `limit`
- `total`
## 播放设计
用户明确要求:
- 不要 parse 相关内容
- 不保留任何解析器线路
因此 `playerContent` 只做原始链接透传,固定返回:
- `parse = 1`
- `jx = 1`
- `url = id`
- `header = {"User-Agent": "PC_UA"}`
不做任何站内解析或解析器回退。
## 容错策略
- 首页和分类页 HTML 抓取失败时返回空列表
- 批量详情接口单次失败时忽略该批次,保留已成功条目
- 详情接口失败时跳过当前 `id`
- 搜索接口异常时返回空分页结构
- 播放阶段不抛异常,直接透传原始 `id`
## 测试设计
新增 `tests/test_腾讯视频.py`,使用 `unittest``unittest.mock`,不访问真实网络。
首轮测试覆盖:
1. 首页 HTML 提取推荐卡片
2. 首页返回固定分类
3. 分类页正确拼接筛选参数
4. 分类页返回 `频道$sourceId` 形式的 `vod_id`
5. `_get_batch_video_info` 解析 `QZOutputJson=` 响应
6. 详情页把正片和花絮/预告合并为单条 `腾讯视频` 线路
7. 搜索同时处理 `normalList``areaBoxList`
8. 播放透传返回 `parse=1/jx=1`
## 风险与边界
- 列表页 HTML 结构若改动,正则提取会失效,但这与参考实现保持一致
- 腾讯搜索返回结构可能包含多种卡片形态,首轮只覆盖参考代码已处理的两种容器
- `PC_UA` 是否需要替换成真实 UA 可在实现阶段决定,但行为上保持统一 header 即可