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": ""}