From 33fd50339c1f3ef5b4a85a3a5ceab36893081a04 Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Sat, 25 Apr 2026 09:17:23 +0800 Subject: [PATCH] =?UTF-8?q?AAZ=E9=9F=B3=E4=B9=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- py/AAZ音乐.py | 2 +- .../2026-04-25-dudu-duanju-spider-design.md | 83 ------------------- py/tests/test_AAZ音乐.py | 18 ++-- py/短剧优选.py | 38 ++++----- 4 files changed, 32 insertions(+), 109 deletions(-) delete mode 100644 py/docs/superpowers/specs/2026-04-25-dudu-duanju-spider-design.md diff --git a/py/AAZ音乐.py b/py/AAZ音乐.py index ee7478a..9b41a5f 100644 --- a/py/AAZ音乐.py +++ b/py/AAZ音乐.py @@ -197,7 +197,7 @@ class Spider(BaseSpider): def _post_play_api(self, song_id): body = "id=%s&type=music" % quote(str(song_id or "")) - response = self.fetch( + response = self.post( self._build_url("/js/play.php"), headers={ "User-Agent": self.headers["User-Agent"], diff --git a/py/docs/superpowers/specs/2026-04-25-dudu-duanju-spider-design.md b/py/docs/superpowers/specs/2026-04-25-dudu-duanju-spider-design.md deleted file mode 100644 index f39a1fe..0000000 --- a/py/docs/superpowers/specs/2026-04-25-dudu-duanju-spider-design.md +++ /dev/null @@ -1,83 +0,0 @@ -# 嘟嘟短剧 Python 爬虫设计 - -## 目标 - -在当前 Python 仓库中新增一个符合 `base.spider.Spider` 接口的 `嘟嘟短剧` 爬虫,对接 `https://api-v2.cenguigui.cn/api/duanju.html`,支持首页、分类、搜索、详情和播放。 - -## 范围 - -本次实现包含: - -- 新增 `py/嘟嘟短剧.py` -- 新增 `py/tests/test_嘟嘟短剧.py` -- 固定分类列表 -- 分类、搜索、详情与播放 API 对接 -- 标题清洗与多清晰度播放地址选择 - -本次实现不包含: - -- 修改 `py/base/` 公共层 -- 改造 `py/短剧优选.py` -- 新增真实联网测试 -- 引入缓存、代理或登录逻辑 - -## 方案 - -采用“单 Spider 文件 + API helper + 离线 JSON 测试”的方案。 - -原因: - -- 用户目标是新增独立爬虫,不是替换聚合源 -- 目标站点是 JSON API,核心工作是请求构造和字段映射,不需要 HTML 解析 -- 仓库现有测试体系适合用 `unittest + mock` 固定接口响应 - -## 接口映射 - -- `homeContent` - - 返回固定分类列表 -- `homeVideoContent` - - 请求 `?name=热播&page=1` - - 将返回列表映射为首页推荐 -- `categoryContent` - - 请求 `?name=<分类名>&page=<页码>` - - 返回列表、`page`、`limit`、`total` - - 不额外引入复杂分页推断,仅在当前页有结果时维持最小可翻页行为 -- `searchContent` - - 请求 `?name=<关键词>&page=<页码>` - - 与分类复用同一列表映射 -- `detailContent` - - 请求 `?id=<剧集ID>` - - 将分集数组整理为单线路 `嘟嘟短剧` - - 使用 `video_id` 作为播放条目 ID -- `playerContent` - - 请求 `?video_id=<分集video_id>` - - 对 `qualities` 按画质优先级排序并返回直链 - -## 数据约定 - -- `vod_id` 使用 API 返回的剧目 `id` -- `vod_name` 对标题做轻量清洗,移除如 `【热播】`、`【新剧】` 等标签 -- `vod_pic` 使用 `cover` -- `vod_remarks` 优先使用总集数,格式为 `更新至N集` -- `vod_play_from` 固定为 `嘟嘟短剧` -- `vod_play_url` 使用 `集名$video_id` 形式拼接 -- `playerContent` 优先返回更高清晰度地址,优先级为 `1080p > sc > sd` - -## 错误处理 - -- 请求非 2xx 或 JSON 解析失败时,列表接口返回空列表 -- 详情失败时返回空 `list` -- 播放失败时返回 `parse=0` 且 `url` 为空字符串 -- 尽量把错误限制在当前接口,不影响其他入口 - -## 测试策略 - -使用嵌入式 JSON 夹具和 mock 覆盖: - -- 首页分类与首页推荐 -- 分类请求参数与列表映射 -- 搜索空关键字与正常搜索 -- 标题清洗逻辑 -- 详情页分集整理与基础元数据 -- 播放画质排序与直链输出 -- 请求失败时的空结果回退 diff --git a/py/tests/test_AAZ音乐.py b/py/tests/test_AAZ音乐.py index b5fa3e6..5c5965a 100644 --- a/py/tests/test_AAZ音乐.py +++ b/py/tests/test_AAZ音乐.py @@ -194,17 +194,23 @@ class TestAAZMusicSpider(unittest.TestCase): def test_detail_content_returns_empty_list_for_invalid_vod_id(self): self.assertEqual(self.spider.detailContent(["bad"])["list"], []) - @patch.object(Spider, "fetch") - def test_player_content_resolves_song_id_to_direct_url(self, mock_fetch): - mock_fetch.return_value = SimpleNamespace(status_code=200, text=PLAY_JSON) + @patch.object(Spider, "post") + def test_player_content_resolves_song_id_to_direct_url(self, mock_post): + mock_post.return_value = SimpleNamespace(status_code=200, text=PLAY_JSON) result = self.spider.playerContent("AAZ音乐", "song:2001", {}) self.assertEqual(result["parse"], 0) self.assertEqual(result["url"], "https://cdn.example.com/aaz/song2001.mp3") self.assertEqual(result["header"]["Referer"], "https://www.aaz.cx/") + self.assertEqual(mock_post.call_args.args[0], "https://www.aaz.cx/js/play.php") + self.assertEqual(mock_post.call_args.kwargs["data"], "id=2001&type=music") + self.assertEqual( + mock_post.call_args.kwargs["headers"]["Content-Type"], + "application/x-www-form-urlencoded; charset=UTF-8", + ) - @patch.object(Spider, "fetch") - def test_player_content_returns_empty_url_for_invalid_or_empty_payload(self, mock_fetch): - mock_fetch.return_value = SimpleNamespace(status_code=200, text=EMPTY_PLAY_JSON) + @patch.object(Spider, "post") + def test_player_content_returns_empty_url_for_invalid_or_empty_payload(self, mock_post): + mock_post.return_value = SimpleNamespace(status_code=200, text=EMPTY_PLAY_JSON) self.assertEqual(self.spider.playerContent("AAZ音乐", "song:2001", {})["url"], "") self.assertEqual(self.spider.playerContent("AAZ音乐", "bad", {})["url"], "") diff --git a/py/短剧优选.py b/py/短剧优选.py index 39901f3..9c7f02c 100644 --- a/py/短剧优选.py +++ b/py/短剧优选.py @@ -83,11 +83,11 @@ class Spider(BaseSpider): "search": "/v3/search", "login": "https://u.shytkjgs.com/user/v1/account/login", }, - "甜圈": { - "host": "https://mov.cenguigui.cn", - "url1": "/duanju/api.php?classname", - "url2": "/duanju/api.php?book_id", - "search": "/duanju/api.php?name", + "红果": { + "host": "https://api-v2.cenguigui.cn", + "url1": "/api/duanju/api.php?classname", + "url2": "/api/duanju/api.php?book_id", + "search": "/api/duanju/api.php?name", }, "短剧网": { "host": "https://sm3.cc", @@ -100,7 +100,7 @@ class Spider(BaseSpider): # {"type_id": "围观", "type_name": "围观短剧"}, # {"type_id": "剧王", "type_name": "剧王短剧"}, {"type_id": "七星", "type_name": "七星短剧"}, - # {"type_id": "甜圈", "type_name": "甜圈短剧"}, + {"type_id": "红果", "type_name": "红果短剧"}, {"type_id": "短剧网", "type_name": "短剧网"}, ] self.filters = { @@ -164,7 +164,7 @@ class Spider(BaseSpider): ("七星剧场", "1"), ("七星新剧", "3"), ("七星热播", "2"), ("七星星选", "7"), ("七星阳光", "5"), ])], - "甜圈": [self._filter_area("分类", [ + "红果": [self._filter_area("分类", [ ("逆袭", "逆袭"), ("霸总", "霸总"), ("现代言情", "现代言情"), ("打脸虐渣", "打脸虐渣"), ("豪门恩怨", "豪门恩怨"), ("神豪", "神豪"), ("马甲", "马甲"), ("都市日常", "都市日常"), @@ -198,7 +198,7 @@ class Spider(BaseSpider): self.filter_defaults = { "七猫": {"area": "0"}, "星芽": {"area": "1"}, "西饭": {"area": ""}, "围观": {"area": "都市"}, "剧王": {"area": "/tag/逆袭/"}, - "七星": {"area": "1"}, "甜圈": {"area": "逆袭"}, + "七星": {"area": "1"}, "红果": {"area": "逆袭"}, "短剧网": {"area": "1"}, } @@ -228,8 +228,7 @@ class Spider(BaseSpider): return { "list": items, "page": page, - "pagecount": pagecount, - "total": pagecount * len(items) if items else 0, + "total": pagecount * 30 + len(items) if items else 0, } def detailContent(self, ids): @@ -272,8 +271,9 @@ class Spider(BaseSpider): } def playerContent(self, flag, id, vipFlags): - if "甜圈" in flag: - return {"parse": 0, "jx": 0, "url": f"https://mov.cenguigui.cn/duanju/api.php?video_id={id}&type=mp4"} + if "红果" in flag: + res = self._get_json("https://api-v2.cenguigui.cn/api/duanju/api.php?video_id=" + id) + return {"parse": 0, "jx": 0, "url": res.get("url")} if "星芽" in flag or "七猫" in flag or "西饭" in flag: return {"parse": 0, "jx": 0, "url": id} if "七星" in flag: @@ -410,7 +410,7 @@ class Spider(BaseSpider): "围观": self._cat_weiguan, "剧王": self._cat_juwang, "七星": self._cat_qixing, - "甜圈": self._cat_tianquan, + "红果": self._cat_tianquan, "短剧网": self._cat_duanjuwang, }.get(tid) if handler: @@ -535,7 +535,7 @@ class Spider(BaseSpider): items = [] for it in res.get("data") or []: items.append({ - "vod_id": f"甜圈@{it.get('book_id', '')}", + "vod_id": f"红果@{it.get('book_id', '')}", "vod_name": self._s(it.get("title")), "vod_pic": self._s(it.get("cover")), "vod_remarks": self._s(it.get("sub_title")), @@ -596,7 +596,7 @@ class Spider(BaseSpider): "围观": self._detail_weiguan, "剧王": self._detail_juwang, "七星": self._detail_qixing, - "甜圈": self._detail_tianquan, + "红果": self._detail_tianquan, "短剧网": self._detail_duanjuwang, }.get(platform) if handler: @@ -753,7 +753,7 @@ class Spider(BaseSpider): "vod_remarks": self._s(res.get("duration")), "vod_year": f"更新时间:{res.get('time', '')}", "vod_actor": self._s(res.get("author")), - "vod_play_from": "甜圈短剧", + "vod_play_from": "红果短剧", "vod_play_url": play_urls, } @@ -866,7 +866,7 @@ class Spider(BaseSpider): "围观": self._search_weiguan, "剧王": self._search_juwang, "七星": self._search_qixing, - "甜圈": self._search_tianquan, + "红果": self._search_tianquan, "短剧网": self._search_duanjuwang, }.get(platform_id) if handler: @@ -982,10 +982,10 @@ class Spider(BaseSpider): items = [] for it in res.get("data") or []: items.append({ - "vod_id": f"甜圈@{it.get('book_id', '')}", + "vod_id": f"红果@{it.get('book_id', '')}", "vod_name": self._s(it.get("title")), "vod_pic": self._s(it.get("cover")), - "vod_remarks": f"甜圈短剧 | {self._s(it.get('sub_title'))}", + "vod_remarks": f"红果短剧 | {self._s(it.get('sub_title'))}", }) return items