From ce784ea9c2a444be964de7191375d03d1b41a8c0 Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Wed, 29 Apr 2026 15:35:32 +0800 Subject: [PATCH] feat: complete shuangxing spider --- py/tests/test_双星.py | 57 +++++++++++++++++++++++++++++++++ py/双星.py | 74 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/py/tests/test_双星.py b/py/tests/test_双星.py index 39f4374..5629102 100644 --- a/py/tests/test_双星.py +++ b/py/tests/test_双星.py @@ -109,6 +109,63 @@ class TestShuangXingSpider(unittest.TestCase): def test_search_content_short_circuits_blank_keyword(self): self.assertEqual(self.spider.searchContent("", False, "1"), {"page": 1, "total": 0, "list": []}) + @patch.object(Spider, "_get_html") + def test_detail_content_extracts_title_and_sorted_deduplicated_pan_lines(self, mock_get_html): + mock_get_html.return_value = """ + +
+
+

双星示例

+
+
+
+
+ + + + +
+
+ + """ + result = self.spider.detailContent(["/post/demo"]) + self.assertEqual(mock_get_html.call_args.args[0], "https://1.star2.cn/post/demo") + self.assertEqual( + result, + { + "list": [ + { + "vod_id": "/post/demo", + "vod_name": "双星示例", + "vod_pic": "", + "vod_remarks": "", + "vod_content": "", + "vod_director": "", + "vod_actor": "", + "vod_play_from": "quark$$$baidu", + "vod_play_url": "夸克资源$https://pan.quark.cn/s/q-demo$$$百度资源$https://pan.baidu.com/s/b-demo", + } + ] + }, + ) + + @patch.object(Spider, "_get_html") + def test_detail_content_returns_empty_list_for_blank_html(self, mock_get_html): + mock_get_html.return_value = "" + self.assertEqual(self.spider.detailContent(["/post/missing"]), {"list": []}) + + def test_player_content_passthroughs_supported_pan_links(self): + self.assertEqual( + self.spider.playerContent("quark", "https://pan.quark.cn/s/demo", {}), + {"parse": 0, "playUrl": "", "url": "https://pan.quark.cn/s/demo"}, + ) + + def test_player_content_rejects_unknown_links(self): + self.assertEqual( + self.spider.playerContent("site", "https://example.com/video", {}), + {"parse": 0, "playUrl": "", "url": ""}, + ) + if __name__ == "__main__": unittest.main() diff --git a/py/双星.py b/py/双星.py index 91671c8..eb167fb 100644 --- a/py/双星.py +++ b/py/双星.py @@ -1,7 +1,7 @@ # coding=utf-8 import re import sys -from urllib.parse import quote +from urllib.parse import quote, urljoin from base.spider import Spider as BaseSpider @@ -24,6 +24,18 @@ class Spider(BaseSpider): ("wj", "外剧"), ("dm", "动漫"), ] + PAN_TITLES = { + "quark": "夸克资源", + "ali": "阿里资源", + "115": "115资源", + "tianyi": "天翼资源", + "uc": "UC资源", + "baidu": "百度资源", + "xunlei": "迅雷资源", + "123pan": "123资源", + "yd": "移动云盘资源", + } + PAN_ORDER = ["quark", "ali", "115", "tianyi", "uc", "baidu", "xunlei", "123pan", "yd"] def __init__(self): self.name = "双星" @@ -112,3 +124,63 @@ class Spider(BaseSpider): return {"page": page, "total": 0, "list": []} items = self._parse_cards(self._get_html(f"{self.BASE_URL}/search/?keyword={quote(keyword)}&page={page}")) return {"page": page, "total": len(items), "list": items} + + def _build_pan_lines(self, share_links): + groups = {} + seen = set() + for link in share_links: + raw = str(link or "").strip() + pan_type = self._detect_pan_type(raw) + if not raw or not pan_type or raw in seen: + continue + seen.add(raw) + groups.setdefault(pan_type, []).append(f"{self.PAN_TITLES[pan_type]}${raw}") + if not groups: + return {"vod_play_from": "", "vod_play_url": ""} + names = [name for name in self.PAN_ORDER if name in groups] + return { + "vod_play_from": "$$$".join(names), + "vod_play_url": "$$$".join("#".join(groups[name]) for name in names), + } + + def detailContent(self, ids): + vod_id = str((ids or [""])[0] or "").strip() + if not vod_id: + return {"list": []} + html = self._get_html(urljoin(self.BASE_URL, vod_id)) + root = self.html(html) + if root is None: + return {"list": []} + title = self._clean_text( + "".join( + root.xpath( + "/html/body/div/div[contains(@class,'s20erx') and contains(@class,'erx-content')]/main/article/h1//text()" + ) + ) + ) + share_links = [ + str(value).strip() + for value in root.xpath("//*[@id='maximg']//div[contains(@class,'dlipp-cont-bd')]//a[@href]/@href") + ] + play = self._build_pan_lines(share_links) + return { + "list": [ + { + "vod_id": vod_id, + "vod_name": title, + "vod_pic": "", + "vod_remarks": "", + "vod_content": "", + "vod_director": "", + "vod_actor": "", + "vod_play_from": play["vod_play_from"], + "vod_play_url": play["vod_play_url"], + } + ] + } + + def playerContent(self, flag, id, vipFlags): + target = str(id or "").strip() + if self._detect_pan_type(target): + return {"parse": 0, "playUrl": "", "url": target} + return {"parse": 0, "playUrl": "", "url": ""}