docs: add shuangxing spider design
This commit is contained in:
@@ -0,0 +1,446 @@
|
|||||||
|
# 双星 Python 爬虫设计
|
||||||
|
|
||||||
|
**日期:** 2026-04-29
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
在当前 Python Spider 仓库中新增独立单站蜘蛛 `双星.py`。
|
||||||
|
|
||||||
|
目标站点主域名固定为:
|
||||||
|
|
||||||
|
- `https://1.star2.cn`
|
||||||
|
|
||||||
|
行为边界以用户提供的 Python 参考实现为准,并对齐当前仓库蜘蛛接口:
|
||||||
|
|
||||||
|
- 支持固定分类输出
|
||||||
|
- 支持分类分页列表
|
||||||
|
- 支持关键字搜索
|
||||||
|
- 支持详情页标题和网盘链接整理
|
||||||
|
- `playerContent` 只透传已识别的网盘分享链接
|
||||||
|
- 不做站内播放页解析
|
||||||
|
|
||||||
|
## 范围
|
||||||
|
|
||||||
|
本次实现包含:
|
||||||
|
|
||||||
|
- 新增蜘蛛文件 `py/双星.py`
|
||||||
|
- 新增测试文件 `py/tests/test_双星.py`
|
||||||
|
- 补充对应 spec 和 plan 文档
|
||||||
|
|
||||||
|
本次实现不包含:
|
||||||
|
|
||||||
|
- 抽取新的公共盘站基类
|
||||||
|
- 修改 `base/` 公共层
|
||||||
|
- 增加站内直链解析
|
||||||
|
- 接入外部聚合蜘蛛
|
||||||
|
- 实现多备用域名 failover
|
||||||
|
|
||||||
|
## 现状
|
||||||
|
|
||||||
|
仓库中已经存在多份结构接近的网盘聚合站蜘蛛,尤其是:
|
||||||
|
|
||||||
|
- `py/欧歌.py`
|
||||||
|
- `py/闪电.py`
|
||||||
|
- `py/二小.py`
|
||||||
|
|
||||||
|
这些实现已经验证了当前仓库对同类站点的稳定接入方式:
|
||||||
|
|
||||||
|
- `homeContent/homeVideoContent/categoryContent/searchContent/detailContent/playerContent` 作为统一入口
|
||||||
|
- `vod_id` 保持为站内短路径
|
||||||
|
- `detailContent` 只负责把详情页里的网盘分享链接整理成播放线路
|
||||||
|
- `playerContent` 不做二次路由,直接对支持的网盘链接透传
|
||||||
|
|
||||||
|
双星和上述盘站不同的一点是:
|
||||||
|
|
||||||
|
- 首次访问首页后需要缓存响应 cookie,后续列表、搜索和详情请求都要带回该 cookie
|
||||||
|
|
||||||
|
因此,本次工作的重点是:
|
||||||
|
|
||||||
|
- 在现有盘站模式下补上 cookie 初始化链路
|
||||||
|
- 按双星页面结构实现列表、搜索和详情解析
|
||||||
|
- 保持 `playerContent` 返回形状与仓库现有蜘蛛兼容
|
||||||
|
|
||||||
|
## 方案选择
|
||||||
|
|
||||||
|
采用“独立单站实现 + 仓库接口适配”的方案。
|
||||||
|
|
||||||
|
### 方案 A:推荐
|
||||||
|
|
||||||
|
- 新增 `py/双星.py`
|
||||||
|
- 参考用户给出的 Python 逻辑实现 cookie 初始化和 DOM 解析
|
||||||
|
- 对外接口对齐仓库现有 `Spider` 约定
|
||||||
|
- 使用 `unittest` 做离线单测
|
||||||
|
|
||||||
|
优点:
|
||||||
|
|
||||||
|
- 与当前仓库现有调用链兼容
|
||||||
|
- 风险边界小
|
||||||
|
- 测试可直接复用已有盘站蜘蛛的断言模式
|
||||||
|
|
||||||
|
### 方案 B:不采用
|
||||||
|
|
||||||
|
- 严格照搬用户参考中的 `route/id` 分发模式
|
||||||
|
|
||||||
|
不采用原因:
|
||||||
|
|
||||||
|
- 与当前仓库 `playerContent` 返回结构不一致
|
||||||
|
- 会在单站蜘蛛里引入额外的路由语义
|
||||||
|
- 对当前调用方没有直接收益
|
||||||
|
|
||||||
|
## Spider 对外行为
|
||||||
|
|
||||||
|
### 文件
|
||||||
|
|
||||||
|
- `py/双星.py`
|
||||||
|
- `py/tests/test_双星.py`
|
||||||
|
|
||||||
|
### `init`
|
||||||
|
|
||||||
|
初始化时访问首页:
|
||||||
|
|
||||||
|
- URL:`https://1.star2.cn`
|
||||||
|
- 请求头至少包含 `User-Agent` 和 `Referer`
|
||||||
|
- `allow_redirects=False`
|
||||||
|
|
||||||
|
从首页响应 cookies 中提取 `name=value`,按 `; ` 拼接后缓存到实例字段中。
|
||||||
|
|
||||||
|
设计约束:
|
||||||
|
|
||||||
|
- `init` 失败不做复杂重试
|
||||||
|
- 后续请求统一通过 helper 带上缓存 cookie
|
||||||
|
- 如果 cookie 为空,后续请求仍然允许继续发送基础请求头
|
||||||
|
|
||||||
|
### `homeContent`
|
||||||
|
|
||||||
|
返回固定 7 个分类:
|
||||||
|
|
||||||
|
- `ju -> 国剧`
|
||||||
|
- `zy -> 综艺`
|
||||||
|
- `mv -> 电影`
|
||||||
|
- `rh -> 日韩`
|
||||||
|
- `ym -> 英美`
|
||||||
|
- `wj -> 外剧`
|
||||||
|
- `dm -> 动漫`
|
||||||
|
|
||||||
|
返回结构:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{"class": [...]}
|
||||||
|
```
|
||||||
|
|
||||||
|
不返回筛选项。
|
||||||
|
|
||||||
|
### `homeVideoContent`
|
||||||
|
|
||||||
|
返回:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{"list": []}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `categoryContent`
|
||||||
|
|
||||||
|
分类 URL:
|
||||||
|
|
||||||
|
- `/{cate_id}_{page}/`
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
- `/ju_1/`
|
||||||
|
|
||||||
|
解析容器:
|
||||||
|
|
||||||
|
- `body > div > div > main > div > ul > li`
|
||||||
|
|
||||||
|
每个列表项提取:
|
||||||
|
|
||||||
|
- `vod_id`:`div.a > a[href]`
|
||||||
|
- `vod_name`:`div.a > a` 文本
|
||||||
|
- `vod_pic`:固定空字符串
|
||||||
|
- `vod_remarks`:固定空字符串
|
||||||
|
|
||||||
|
结果结构包含:
|
||||||
|
|
||||||
|
- `page`
|
||||||
|
- `limit`
|
||||||
|
- `total`
|
||||||
|
- `list`
|
||||||
|
|
||||||
|
不返回 `pagecount`。
|
||||||
|
|
||||||
|
`limit` 采用站点参考中的每页基准值 `15`,`total` 采用仓库当前惯例的保守估算:
|
||||||
|
|
||||||
|
- `total = (page - 1) * 15 + len(list)`
|
||||||
|
|
||||||
|
这样可以避免伪造不可验证的总页数。
|
||||||
|
|
||||||
|
### `searchContent`
|
||||||
|
|
||||||
|
搜索 URL:
|
||||||
|
|
||||||
|
- `/search/?keyword={quote(keyword)}&page={page}`
|
||||||
|
|
||||||
|
空关键词直接返回:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{"page": page, "total": 0, "list": []}
|
||||||
|
```
|
||||||
|
|
||||||
|
解析容器与字段同分类页:
|
||||||
|
|
||||||
|
- `body > div > div > main > div > ul > li`
|
||||||
|
- `vod_id`:`div.a > a[href]`
|
||||||
|
- `vod_name`:`div.a > a` 文本
|
||||||
|
- `vod_pic`:空字符串
|
||||||
|
- `vod_remarks`:空字符串
|
||||||
|
|
||||||
|
返回结构包含:
|
||||||
|
|
||||||
|
- `page`
|
||||||
|
- `total`
|
||||||
|
- `list`
|
||||||
|
|
||||||
|
### `detailContent`
|
||||||
|
|
||||||
|
输入 `vod_id` 为站内相对路径,例如:
|
||||||
|
|
||||||
|
- `/post/123`
|
||||||
|
|
||||||
|
内部请求时用 `urljoin` 拼成完整详情 URL。
|
||||||
|
|
||||||
|
详情页提取:
|
||||||
|
|
||||||
|
- 标题:`body > div > div.s20erx.erx-m-bot.erx-content > main > article > h1`
|
||||||
|
- 分享链接:`#maximg > div.dlipp-cont-wp > div > div.dlipp-cont-bd > a[href]`
|
||||||
|
|
||||||
|
输出字段:
|
||||||
|
|
||||||
|
- `vod_id`
|
||||||
|
- `vod_name`
|
||||||
|
- `vod_pic`
|
||||||
|
- `vod_remarks`
|
||||||
|
- `vod_content`
|
||||||
|
- `vod_director`
|
||||||
|
- `vod_actor`
|
||||||
|
- `vod_play_from`
|
||||||
|
- `vod_play_url`
|
||||||
|
|
||||||
|
其中:
|
||||||
|
|
||||||
|
- `vod_pic` 固定空字符串
|
||||||
|
- `vod_remarks` 固定空字符串
|
||||||
|
- `vod_content` 固定空字符串
|
||||||
|
- `vod_director` 固定空字符串
|
||||||
|
- `vod_actor` 固定空字符串
|
||||||
|
|
||||||
|
### `playerContent`
|
||||||
|
|
||||||
|
对已识别网盘分享链接返回:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{"parse": 0, "playUrl": "", "url": id}
|
||||||
|
```
|
||||||
|
|
||||||
|
对未识别链接返回:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{"parse": 0, "playUrl": "", "url": ""}
|
||||||
|
```
|
||||||
|
|
||||||
|
不返回 `route/id` 风格结构。
|
||||||
|
|
||||||
|
## URL 与请求设计
|
||||||
|
|
||||||
|
### 主域名
|
||||||
|
|
||||||
|
- `https://1.star2.cn`
|
||||||
|
|
||||||
|
### 基础请求头
|
||||||
|
|
||||||
|
- `User-Agent`:沿用用户给定的浏览器 UA
|
||||||
|
- `Referer`:`https://1.star2.cn`
|
||||||
|
|
||||||
|
### 请求 helper
|
||||||
|
|
||||||
|
实现两个内部 helper:
|
||||||
|
|
||||||
|
- `_headers()`:生成带 cookie 的请求头
|
||||||
|
- `_get_html(url)`:统一请求 HTML 文本
|
||||||
|
|
||||||
|
`_headers()` 规则:
|
||||||
|
|
||||||
|
- 始终带 `User-Agent`
|
||||||
|
- 始终带 `Referer`
|
||||||
|
- 仅在缓存 cookie 非空时追加 `cookie`
|
||||||
|
|
||||||
|
`_get_html(url)` 规则:
|
||||||
|
|
||||||
|
- 统一通过 `self.fetch` 请求
|
||||||
|
- 失败时返回空字符串或由上层解析成空结果
|
||||||
|
- 不在 helper 内猜测页面编码之外的站点行为
|
||||||
|
|
||||||
|
## 页面解析策略
|
||||||
|
|
||||||
|
### HTML 解析方式
|
||||||
|
|
||||||
|
优先使用仓库现有 `self.html()` + XPath,而不是引入 `BeautifulSoup` 新依赖。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 与当前仓库其他蜘蛛一致
|
||||||
|
- 单测更容易直接构造 HTML 片段
|
||||||
|
- 避免为一个站点引入不同解析风格
|
||||||
|
|
||||||
|
### 列表与搜索卡片
|
||||||
|
|
||||||
|
站点参考选择器较深,但字段实际很少,只提取以下最稳定的数据:
|
||||||
|
|
||||||
|
- `href`
|
||||||
|
- 文字标题
|
||||||
|
|
||||||
|
如节点缺失,直接跳过当前卡片,不做兜底猜测。
|
||||||
|
|
||||||
|
### 详情页分享链接
|
||||||
|
|
||||||
|
详情页只收集 `a[href]` 中真实可用的分享链接:
|
||||||
|
|
||||||
|
- 空链接跳过
|
||||||
|
- 重复链接去重
|
||||||
|
- 未识别网盘类型的链接跳过
|
||||||
|
|
||||||
|
## 网盘线路规则
|
||||||
|
|
||||||
|
支持识别以下 9 类网盘:
|
||||||
|
|
||||||
|
- `quark`:夸克
|
||||||
|
- `ali`:阿里
|
||||||
|
- `115`:115
|
||||||
|
- `tianyi`:天翼
|
||||||
|
- `uc`:UC
|
||||||
|
- `baidu`:百度
|
||||||
|
- `xunlei`:迅雷
|
||||||
|
- `123pan`:123
|
||||||
|
- `yd`:移动云盘
|
||||||
|
|
||||||
|
域名识别规则:
|
||||||
|
|
||||||
|
- `quark`:`quark`
|
||||||
|
- `115`:`115.com`
|
||||||
|
- `tianyi`:`cloud.189.cn`
|
||||||
|
- `uc`:`drive.uc.cn` 或 `uc.cn`
|
||||||
|
- `baidu`:`pan.baidu.com`
|
||||||
|
- `xunlei`:`xunlei`
|
||||||
|
- `123pan`:`123pan`
|
||||||
|
- `yd`:`caiyun` 或 `139.com`
|
||||||
|
- `ali`:`aliyundrive` 或 `alipan`
|
||||||
|
|
||||||
|
### 排序与命名
|
||||||
|
|
||||||
|
线路输出顺序固定为:
|
||||||
|
|
||||||
|
1. `quark`
|
||||||
|
2. `ali`
|
||||||
|
3. `115`
|
||||||
|
4. `tianyi`
|
||||||
|
5. `uc`
|
||||||
|
6. `baidu`
|
||||||
|
7. `xunlei`
|
||||||
|
8. `123pan`
|
||||||
|
9. `yd`
|
||||||
|
|
||||||
|
每个线路名使用稳定 key,而不是展示中文:
|
||||||
|
|
||||||
|
- `vod_play_from = "quark$$$ali$$$baidu"...`
|
||||||
|
|
||||||
|
每个线路内的剧集项使用展示名加 `$` 拼接:
|
||||||
|
|
||||||
|
- `夸克资源$https://pan.quark.cn/s/demo`
|
||||||
|
- `百度资源$https://pan.baidu.com/s/demo`
|
||||||
|
|
||||||
|
多个同类链接使用 `#` 拼接。
|
||||||
|
|
||||||
|
### 组装规则
|
||||||
|
|
||||||
|
详情页提取到的分享链接经过以下流程:
|
||||||
|
|
||||||
|
1. 去空
|
||||||
|
2. 去重
|
||||||
|
3. 识别网盘类型
|
||||||
|
4. 按预定义顺序分组
|
||||||
|
5. 生成 `vod_play_from`
|
||||||
|
6. 生成 `vod_play_url`
|
||||||
|
|
||||||
|
设计上不为同类链接额外制造 `夸克1/夸克2` 这样的条目名。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 当前仓库同类盘站一般使用统一展示名
|
||||||
|
- 用户最终消费的是分享链接,不依赖人工编号
|
||||||
|
- 可减少无意义字符串差异,便于测试断言
|
||||||
|
|
||||||
|
## 异常与空结果策略
|
||||||
|
|
||||||
|
### 分类页 / 搜索页
|
||||||
|
|
||||||
|
当请求失败、HTML 为空或解析不到卡片时,返回空列表,但结构保持稳定。
|
||||||
|
|
||||||
|
### 详情页
|
||||||
|
|
||||||
|
当请求失败或解析失败时,返回:
|
||||||
|
|
||||||
|
- `{"list": []}`
|
||||||
|
|
||||||
|
不返回半残缺占位对象。
|
||||||
|
|
||||||
|
原因:
|
||||||
|
|
||||||
|
- 当前仓库多数新蜘蛛对详情失败采取空列表回退
|
||||||
|
- 比返回字段为空但看似成功的对象更容易让调用方识别失败
|
||||||
|
|
||||||
|
### `playerContent`
|
||||||
|
|
||||||
|
不尝试修正、跳转或二次解析链接:
|
||||||
|
|
||||||
|
- 支持的网盘链接直接透传
|
||||||
|
- 非支持链接直接返回空 `url`
|
||||||
|
|
||||||
|
## 测试设计
|
||||||
|
|
||||||
|
测试文件:
|
||||||
|
|
||||||
|
- `py/tests/test_双星.py`
|
||||||
|
|
||||||
|
使用 `unittest` 与 `unittest.mock`,全部通过 mock 隔离网络访问。
|
||||||
|
|
||||||
|
核心测试点:
|
||||||
|
|
||||||
|
1. `homeContent` 返回固定 7 个分类
|
||||||
|
2. `homeVideoContent` 返回空列表
|
||||||
|
3. `init` 能从首页响应 cookies 组装出 `a=1; b=2` 形式的缓存 cookie
|
||||||
|
4. `_headers()` 在 cookie 存在和不存在时都返回正确请求头
|
||||||
|
5. `categoryContent` 组装正确 URL 并解析列表卡片
|
||||||
|
6. `searchContent` 组装正确搜索 URL 并解析结果
|
||||||
|
7. `searchContent` 对空关键词直接返回空列表
|
||||||
|
8. `detailContent` 能提取标题和多类网盘链接
|
||||||
|
9. `detailContent` 会去重、过滤未知链接并按固定顺序组装线路
|
||||||
|
10. `detailContent` 在详情页请求失败时返回空列表
|
||||||
|
11. `playerContent` 透传支持的网盘链接
|
||||||
|
12. `playerContent` 拒绝非网盘链接
|
||||||
|
|
||||||
|
## 实现约束
|
||||||
|
|
||||||
|
- 不引入新的第三方依赖
|
||||||
|
- 不修改 `base/` 公共行为
|
||||||
|
- 不在 `playerContent` 中引入路由分发语义
|
||||||
|
- 不伪造 `pagecount`
|
||||||
|
- 不把完整 URL 存成列表页 `vod_id`
|
||||||
|
|
||||||
|
## 验收标准
|
||||||
|
|
||||||
|
完成后应满足:
|
||||||
|
|
||||||
|
- `homeContent(False)` 返回预期分类
|
||||||
|
- `categoryContent/searchContent` 能按参考 URL 组织请求并解析基础卡片
|
||||||
|
- `detailContent` 正确输出 `vod_play_from` 和 `vod_play_url`
|
||||||
|
- `playerContent` 对支持网盘链接返回可直接消费的透传结构
|
||||||
|
- 新增单测全部通过
|
||||||
Reference in New Issue
Block a user