feat: complete shuangxing spider
This commit is contained in:
@@ -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 = """
|
||||
<body>
|
||||
<div>
|
||||
<div class="s20erx erx-m-bot erx-content">
|
||||
<main><article><h1>双星示例</h1></article></main>
|
||||
</div>
|
||||
</div>
|
||||
<div id="maximg">
|
||||
<div class="dlipp-cont-wp"><div><div class="dlipp-cont-bd">
|
||||
<a href="https://pan.baidu.com/s/b-demo"></a>
|
||||
<a href="https://pan.quark.cn/s/q-demo"></a>
|
||||
<a href="https://pan.baidu.com/s/b-demo"></a>
|
||||
<a href="https://example.com/ignored"></a>
|
||||
</div></div></div>
|
||||
</div>
|
||||
</body>
|
||||
"""
|
||||
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()
|
||||
|
||||
@@ -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": ""}
|
||||
|
||||
Reference in New Issue
Block a user