短剧优选

This commit is contained in:
Harold
2026-04-25 12:18:20 +08:00
parent 0a61cf566d
commit 30533046c4
2 changed files with 281 additions and 199 deletions
+113 -127
View File
@@ -1,8 +1,8 @@
import unittest
import json import json
import unittest
from importlib.machinery import SourceFileLoader from importlib.machinery import SourceFileLoader
from pathlib import Path from pathlib import Path
from unittest.mock import patch, MagicMock from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
MODULE = SourceFileLoader("duanjuyx_spider", str(ROOT / "短剧优选.py")).load_module() MODULE = SourceFileLoader("duanjuyx_spider", str(ROOT / "短剧优选.py")).load_module()
@@ -13,164 +13,164 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
def setUp(self): def setUp(self):
Spider._instance = None Spider._instance = None
self.spider = Spider() self.spider = Spider()
self.spider.init() self.spider.xingya_headers = {"authorization": "token"}
def test_home_content_has_active_platforms(self): def test_home_content_has_active_platforms(self):
content = self.spider.homeContent(False) content = self.spider.homeContent(False)
ids = [c["type_id"] for c in content["class"]] ids = [c["type_id"] for c in content["class"]]
self.assertIn("七猫", ids) self.assertEqual(ids, ["七猫", "星芽", "星星", "西饭", "锦鲤", "红果", "短剧网"])
self.assertIn("", ids) self.assertNotIn("", ids)
self.assertIn("西饭", ids) self.assertNotIn("围观", ids)
self.assertIn("七星", ids)
self.assertIn("短剧网", ids)
def test_home_content_has_filters(self): def test_home_content_has_filters(self):
content = self.spider.homeContent(False) content = self.spider.homeContent(False)
self.assertIn("七猫", content["filters"]) self.assertIn("七猫", content["filters"])
self.assertIn("星星", content["filters"])
self.assertIn("锦鲤", content["filters"])
self.assertIn("短剧网", content["filters"]) self.assertIn("短剧网", content["filters"])
qm_filter = content["filters"]["七猫"][0] self.assertEqual(content["filters"]["锦鲤"][0]["key"], "area")
self.assertEqual(qm_filter["key"], "area")
self.assertTrue(len(qm_filter["value"]) > 50)
def test_home_video_content_returns_empty(self): def test_home_video_content_returns_empty(self):
self.assertEqual(self.spider.homeVideoContent(), {"list": []}) self.assertEqual(self.spider.homeVideoContent(), {"list": []})
def test_player_tianquan(self): @patch.object(Spider, "_get_json")
result = self.spider.playerContent("甜圈短剧", "vid123", {}) def test_player_hongguo_uses_video_api_result(self, mock_json):
mock_json.return_value = {"url": "https://media.example.com/play.mp4"}
result = self.spider.playerContent("红果短剧", "vid123", {})
self.assertEqual(result["parse"], 0) self.assertEqual(result["parse"], 0)
self.assertIn("video_id=vid123", result["url"]) self.assertEqual(result["url"], "https://media.example.com/play.mp4")
def test_player_xingya_passthrough(self): def test_player_xingya_passthrough(self):
result = self.spider.playerContent("星芽短剧", "https://example.com/video.m3u8", {}) result = self.spider.playerContent("星芽短剧", "https://example.com/video.m3u8", {})
self.assertEqual(result["url"], "https://example.com/video.m3u8") self.assertEqual(result["url"], "https://example.com/video.m3u8")
def test_player_weiguan_selects_super(self):
play_setting = {"super": "s.mp4", "high": "h.mp4", "normal": "n.mp4"}
result = self.spider.playerContent("围观短剧", json.dumps(play_setting), {})
self.assertEqual(result["url"], "s.mp4")
def test_player_weiguan_falls_back_to_high(self):
play_setting = {"high": "h.mp4", "normal": "n.mp4"}
result = self.spider.playerContent("围观短剧", json.dumps(play_setting), {})
self.assertEqual(result["url"], "h.mp4")
def test_player_weiguan_invalid_json(self):
result = self.spider.playerContent("围观短剧", "not-json", {})
self.assertEqual(result["url"], "not-json")
def test_player_duanjuwang_passthrough(self): def test_player_duanjuwang_passthrough(self):
result = self.spider.playerContent("短剧网", "push://pan.quark.cn/abc", {}) result = self.spider.playerContent("短剧网", "push://pan.quark.cn/abc", {})
self.assertEqual(result["parse"], 0) self.assertEqual(result["parse"], 0)
self.assertEqual(result["url"], "push://pan.quark.cn/abc") self.assertEqual(result["url"], "push://pan.quark.cn/abc")
@patch.object(Spider, "_get_text") @patch.object(Spider, "_post_json")
def test_player_juwang_extracts_m3u8(self, mock_text): def test_category_jinli(self, mock_post_json):
mock_text.return_value = 'var data = {"wwm3u8": "https:\\/\\/cdn.example.com\\/video.m3u8"};' mock_post_json.return_value = {
result = self.spider.playerContent("剧王短剧", "https://djw1.com/play/123", {}) "data": {
self.assertEqual(result["url"], "https://cdn.example.com/video.m3u8") "list": [
self.assertEqual(result["parse"], 0) {"vod_id": "j1", "vod_name": "重生逆袭遇良人", "vod_pic": "http://pic/j1.jpg", "vod_total": 88}
]
}
}
result = self.spider.categoryContent("锦鲤", "1", False, {"area": ""})
self.assertEqual(len(result["list"]), 1)
self.assertEqual(result["list"][0]["vod_id"], "锦鲤@j1")
self.assertEqual(result["list"][0]["vod_name"], "重生逆袭遇良人")
self.assertGreater(result["total"], 0)
@patch.object(Spider, "_get_json") @patch.object(Spider, "_get_json")
def test_category_tianquan(self, mock_json): def test_category_xingxing(self, mock_get_json):
mock_json.return_value = { mock_get_json.return_value = {
"data": {
"datalist": [
{
"id": "420424102051841",
"name": "穿越诡计爱上你",
"introduction": "简介",
"icon": "http://pic/xx.jpg",
"heat": "42",
}
],
"total": 10,
}
}
result = self.spider.categoryContent("星星", "1", False, {"area": "1287"})
self.assertEqual(len(result["list"]), 1)
self.assertEqual(result["list"][0]["vod_id"], "星星@420424102051841@@穿越诡计爱上你@@简介")
self.assertEqual(result["list"][0]["vod_remarks"], "42万播放")
@patch.object(Spider, "_get_json")
def test_category_hongguo(self, mock_get_json):
mock_get_json.return_value = {
"data": [ "data": [
{"book_id": "b1", "title": "逆袭人生", "cover": "http://pic/1.jpg", "sub_title": "60集"}, {"book_id": "b1", "title": "逆袭人生", "cover": "http://pic/1.jpg", "sub_title": "60集"},
{"book_id": "b2", "title": "霸总归来", "cover": "http://pic/2.jpg", "sub_title": "80集"}, {"book_id": "b2", "title": "霸总归来", "cover": "http://pic/2.jpg", "sub_title": "80集"},
] ]
} }
result = self.spider.categoryContent("甜圈", "1", False, {"area": "逆袭"}) result = self.spider.categoryContent("红果", "1", False, {"area": "逆袭"})
self.assertEqual(len(result["list"]), 2) self.assertEqual(len(result["list"]), 2)
self.assertEqual(result["list"][0]["vod_id"], "甜圈@b1") self.assertEqual(result["list"][0]["vod_id"], "红果@b1")
self.assertEqual(result["list"][0]["vod_name"], "逆袭人生") self.assertEqual(result["page"], 1)
self.assertEqual(result["pagecount"], 2)
@patch.object(Spider, "_post_json")
def test_category_weiguan(self, mock_json):
mock_json.return_value = {
"data": [
{"oneId": "wg1", "title": "都市情缘", "vertPoster": "http://pic/wg.jpg", "episodeCount": 50},
]
}
result = self.spider.categoryContent("围观", "1", False, {"area": "都市"})
self.assertEqual(len(result["list"]), 1)
self.assertEqual(result["list"][0]["vod_id"], "围观@wg1")
@patch.object(Spider, "_get_text") @patch.object(Spider, "_get_text")
def test_category_duanjuwang(self, mock_text): def test_category_duanjuwang(self, mock_get_text):
mock_text.return_value = """ mock_get_text.return_value = """
<li class="col-6"> <li class="col-6">
<h3 class="f-14"><a href="/detail/123.html">短剧名(60集完结)</a></h3> <h3 class="f-14"><a href="/?id=123">短剧名(60集完结)</a></h3>
<img class="lazy" data-original="/cover.jpg" /> <img class="lazy" data-original="/cover.jpg" />
</li> </li>
""" """
result = self.spider.categoryContent("短剧网", "1", False, {"area": "1"}) result = self.spider.categoryContent("短剧网", "1", False, {"area": "1"})
self.assertEqual(len(result["list"]), 1) self.assertEqual(len(result["list"]), 1)
self.assertEqual(result["list"][0]["vod_id"], "短剧网@https://sm3.cc/detail/123.html") self.assertEqual(result["list"][0]["vod_id"], "短剧网@https://sm3.cc/?id=123")
self.assertEqual(result["list"][0]["vod_name"], "短剧名(60集完结)") self.assertEqual(result["page"], 1)
self.assertEqual(result["list"][0]["vod_remarks"], "")
self.assertEqual(result["pagecount"], 2)
@patch.object(Spider, "_get_json") @patch.object(Spider, "_get_json")
def test_category_empty_result(self, mock_json): def test_detail_jinli(self, mock_get_json):
mock_json.return_value = {"data": []} mock_get_json.return_value = {
result = self.spider.categoryContent("甜圈", "2", False, {"area": "逆袭"}) "data": {
self.assertEqual(result["list"], []) "vod_name": "重生逆袭遇良人",
self.assertEqual(result["pagecount"], 2) "vod_class": "重生",
"vod_pic": "http://pic/j1.jpg",
"vod_remarks": "88集",
"vod_blurb": "剧情简介",
"player": {"第1集": "https://player.example.com/?url=abc"},
}
}
result = self.spider.detailContent(["锦鲤@j1"])
vod = result["list"][0]
self.assertEqual(vod["vod_name"], "重生逆袭遇良人")
self.assertEqual(vod["vod_play_from"], "锦鲤短剧")
self.assertIn("第1集$https://player.example.com/?url=abc", vod["vod_play_url"])
@patch.object(Spider, "_get_json") @patch.object(Spider, "_get_json")
def test_detail_tianquan(self, mock_json): def test_detail_xingxing(self, mock_get_json):
mock_json.return_value = { mock_get_json.return_value = {
"book_name": "甜剧1号", "book_pic": "http://pic/t.jpg", "desc": "剧情简介", "data": [
"duration": "60分钟", "author": "作者", "time": "2025-01-01", {
"chapterName": "第1集",
"shortPlayList": [
{"chapterShortPlayVoList": [{"shortPlayUrl": "http://img.novel.wsljf.xyz/video.mp4"}]}
],
}
]
}
result = self.spider.detailContent(["星星@420424102051841@@穿越诡计爱上你@@简介"])
vod = result["list"][0]
self.assertEqual(vod["vod_name"], "穿越诡计爱上你")
self.assertEqual(vod["vod_play_from"], "星星短剧")
self.assertIn("第1集$https://img.novel.wsljf.xyz/video.mp4", vod["vod_play_url"])
@patch.object(Spider, "_get_json")
def test_detail_hongguo(self, mock_get_json):
mock_get_json.return_value = {
"book_name": "甜剧1号",
"book_pic": "http://pic/t.jpg",
"desc": "剧情简介",
"duration": "60分钟",
"author": "作者",
"time": "2025-01-01",
"data": [ "data": [
{"title": "第1集", "video_id": "v1"}, {"title": "第1集", "video_id": "v1"},
{"title": "第2集", "video_id": "v2"}, {"title": "第2集", "video_id": "v2"},
], ],
} }
result = self.spider.detailContent(["甜圈@b1"]) result = self.spider.detailContent(["红果@b1"])
vod = result["list"][0] vod = result["list"][0]
self.assertEqual(vod["vod_name"], "甜剧1号") self.assertEqual(vod["vod_name"], "甜剧1号")
self.assertEqual(vod["vod_play_from"], "甜圈短剧") self.assertEqual(vod["vod_play_from"], "红果短剧")
self.assertIn("第1集$v1", vod["vod_play_url"]) self.assertIn("第1集$v1", vod["vod_play_url"])
self.assertIn("第2集$v2", vod["vod_play_url"]) self.assertIn("第2集$v2", vod["vod_play_url"])
@patch.object(Spider, "_get_json")
def test_detail_weiguan(self, mock_json):
mock_json.return_value = {
"data": [
{"title": "逆袭", "playOrder": 1, "playSetting": {"super": "s1.mp4"}, "vertPoster": "p.jpg", "collectionCount": 100},
{"title": "逆袭", "playOrder": 2, "playSetting": {"super": "s2.mp4"}, "vertPoster": "p.jpg", "collectionCount": 100},
]
}
result = self.spider.detailContent(["围观@wg1"])
vod = result["list"][0]
self.assertEqual(vod["vod_name"], "逆袭")
self.assertEqual(vod["vod_remarks"], "共2集")
self.assertIn("围观短剧", vod["vod_play_from"])
@patch.object(Spider, "_get_text") @patch.object(Spider, "_get_text")
def test_detail_juwang(self, mock_text): def test_detail_duanjuwang(self, mock_get_text):
mock_text.return_value = """ mock_get_text.return_value = """
<html><body>
<h1>剧王短剧名</h1>
<img src="/poster.jpg" />
<div class="info-detail">剧情简介</div>
<div class="ep-list">
<a href="/play/1-1.html">第1集</a>
<a href="/play/1-2.html">第2集</a>
</div>
</body></html>
"""
result = self.spider.detailContent(["剧王@/show/123"])
vod = result["list"][0]
self.assertEqual(vod["vod_name"], "剧王短剧名")
self.assertEqual(vod["vod_content"], "剧情简介")
self.assertEqual(vod["vod_play_from"], "剧王短剧")
self.assertIn("第1集$/play/1-1.html", vod["vod_play_url"])
@patch.object(Spider, "_get_text")
def test_detail_duanjuwang(self, mock_text):
mock_text.return_value = """
<html><body> <html><body>
<h1>网盘短剧名</h1> <h1>网盘短剧名</h1>
<img class="lazy" data-original="/poster.jpg" /> <img class="lazy" data-original="/poster.jpg" />
@@ -180,7 +180,7 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
</div> </div>
</body></html> </body></html>
""" """
result = self.spider.detailContent(["短剧网@https://sm3.cc/detail/123"]) result = self.spider.detailContent(["短剧网@https://sm3.cc/?id=123"])
vod = result["list"][0] vod = result["list"][0]
self.assertEqual(vod["vod_name"], "网盘短剧名") self.assertEqual(vod["vod_name"], "网盘短剧名")
self.assertEqual(vod["vod_content"], "此为推送网盘规则") self.assertEqual(vod["vod_content"], "此为推送网盘规则")
@@ -188,32 +188,18 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
self.assertIn("baidu", vod["vod_play_from"]) self.assertIn("baidu", vod["vod_play_from"])
self.assertIn("quark$https://pan.quark.cn/s/abc123", vod["vod_play_url"]) self.assertIn("quark$https://pan.quark.cn/s/abc123", vod["vod_play_url"])
@patch.object(Spider, "_get_text")
def test_detail_duanjuwang_no_netdisk_links(self, mock_text):
mock_text.return_value = """
<html><body>
<h1>无链接短剧</h1>
<div class="content"><p>暂无链接</p></div>
</body></html>
"""
result = self.spider.detailContent(["短剧网@https://sm3.cc/detail/456"])
vod = result["list"][0]
self.assertEqual(vod["vod_name"], "无链接短剧")
self.assertEqual(vod["vod_play_from"], "短剧网")
self.assertEqual(vod["vod_play_url"], "")
@patch.object(Spider, "_get_json") @patch.object(Spider, "_get_json")
def test_detail_failure_returns_error_vod(self, mock_json): def test_detail_failure_returns_error_vod(self, mock_get_json):
mock_json.side_effect = Exception("network error") mock_get_json.side_effect = Exception("network error")
result = self.spider.detailContent(["甜圈@missing"]) result = self.spider.detailContent(["红果@missing"])
vod = result["list"][0] vod = result["list"][0]
self.assertIn("详情加载失败", vod["vod_name"]) self.assertIn("详情加载失败", vod["vod_name"])
@patch.object(Spider, "_aggregate_search") @patch.object(Spider, "_aggregate_search")
def test_search_filters_by_keyword(self, mock_search): def test_search_filters_by_keyword(self, mock_search):
mock_search.return_value = [ mock_search.return_value = [
{"vod_id": "甜圈@1", "vod_name": "逆袭人生", "vod_pic": "", "vod_remarks": ""}, {"vod_id": "红果@1", "vod_name": "逆袭人生", "vod_pic": "", "vod_remarks": ""},
{"vod_id": "七猫@2", "vod_name": "霸道总裁", "vod_pic": "", "vod_remarks": ""}, {"vod_id": "锦鲤@2", "vod_name": "霸道总裁", "vod_pic": "", "vod_remarks": ""},
] ]
result = self.spider.searchContent("逆袭", False, "1") result = self.spider.searchContent("逆袭", False, "1")
self.assertEqual(len(result["list"]), 1) self.assertEqual(len(result["list"]), 1)
@@ -226,8 +212,8 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
mock_search.assert_not_called() mock_search.assert_not_called()
def test_filter_defaults(self): def test_filter_defaults(self):
self.assertEqual(self.spider.filter_defaults["七猫"]["area"], "0") self.assertEqual(self.spider.filter_defaults["锦鲤"]["area"], "")
self.assertEqual(self.spider.filter_defaults["围观"]["area"], "都市") self.assertEqual(self.spider.filter_defaults["星星"]["area"], "1287")
self.assertEqual(self.spider.filter_defaults["短剧网"]["area"], "1") self.assertEqual(self.spider.filter_defaults["短剧网"]["area"], "1")
def test_identify_disk(self): def test_identify_disk(self):
+168 -72
View File
@@ -36,6 +36,21 @@ XIFAN_FEEDS = (
"JS3QY6ER0P2cQSxAE_OGKSMIWNAMsYUZ3mJTnEpf-Rc" "JS3QY6ER0P2cQSxAE_OGKSMIWNAMsYUZ3mJTnEpf-Rc"
) )
XINGXING_HOST = "http://read.api.duodutek.com"
XINGXING_UA = (
"Mozilla/5.0 (Windows NT 6.1; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/50.0.2661.87 Safari/537.36"
)
XINGXING_COMMON_PARAMS = {
"productId": "2a8c14d1-72e7-498b-af23-381028eb47c0",
"vestId": "2be070e0-c824-4d0e-a67a-8f688890cadb",
"channel": "oppo19",
"osType": "android",
"version": "20",
"token": "202509271001001446030204698626",
}
class Spider(BaseSpider): class Spider(BaseSpider):
def __init__(self): def __init__(self):
@@ -59,30 +74,22 @@ class Spider(BaseSpider):
"search": "/v3/search", "search": "/v3/search",
"login": "https://u.shytkjgs.com/user/v1/account/login", "login": "https://u.shytkjgs.com/user/v1/account/login",
}, },
"星星": {
"host": XINGXING_HOST,
"url1": "/novel-api/app/pageModel/getResourceById",
"url2": "/novel-api/basedata/book/getChapterList",
},
"西饭": { "西饭": {
"host": "https://xifan-api-cn.youlishipin.com", "host": "https://xifan-api-cn.youlishipin.com",
"url1": "/xifan/drama/portalPage", "url1": "/xifan/drama/portalPage",
"url2": "/xifan/drama/getDuanjuInfo", "url2": "/xifan/drama/getDuanjuInfo",
"search": "/xifan/search/getSearchList", "search": "/xifan/search/getSearchList",
}, },
"围观": { # "锦鲤": {
"host": "https://api.drama.9ddm.com", # "host": "https://api.jinlidj.com",
"url1": "/drama/home/shortVideoTags", # "search": "/api/search",
"url2": "/drama/home/shortVideoDetail", # "url2": "/api/detail",
"search": "/drama/home/search", # },
},
"剧王": {
"host": "https://djw1.com",
"url1": "/all/",
"search": "/search",
},
"七星": {
"host": "https://app.whjzjx.cn",
"url1": "/v1/theater/home_page?theater_class_id",
"url2": "/v2/theater_parent/detail",
"search": "/v3/search",
"login": "https://u.shytkjgs.com/user/v1/account/login",
},
"红果": { "红果": {
"host": "https://api-v2.cenguigui.cn", "host": "https://api-v2.cenguigui.cn",
"url1": "/api/duanju/api.php?classname", "url1": "/api/duanju/api.php?classname",
@@ -96,10 +103,9 @@ class Spider(BaseSpider):
self.classes = [ self.classes = [
{"type_id": "七猫", "type_name": "七猫短剧"}, {"type_id": "七猫", "type_name": "七猫短剧"},
{"type_id": "星芽", "type_name": "星芽短剧"}, {"type_id": "星芽", "type_name": "星芽短剧"},
{"type_id": "星星", "type_name": "星星短剧"},
{"type_id": "西饭", "type_name": "西饭短剧"}, {"type_id": "西饭", "type_name": "西饭短剧"},
# {"type_id": "围观", "type_name": "围观短剧"}, {"type_id": "锦鲤", "type_name": "锦鲤短剧"},
# {"type_id": "剧王", "type_name": "剧王短剧"},
{"type_id": "七星", "type_name": "七星短剧"},
{"type_id": "红果", "type_name": "红果短剧"}, {"type_id": "红果", "type_name": "红果短剧"},
{"type_id": "短剧网", "type_name": "短剧网"}, {"type_id": "短剧网", "type_name": "短剧网"},
] ]
@@ -125,44 +131,20 @@ class Spider(BaseSpider):
("剧场", "1"), ("热播", "2"), ("新剧", "3"), ("剧场", "1"), ("热播", "2"), ("新剧", "3"),
("阳光剧场", "5"), ("星选好剧", "7"), ("会员专享", "8"), ("阳光剧场", "5"), ("星选好剧", "7"), ("会员专享", "8"),
])], ])],
"星星": [self._filter_area("分类", [
("甜宠", "1287"), ("逆袭", "1288"), ("热血", "1289"),
("现代", "1290"), ("古代", "1291"),
])],
"西饭": [self._filter_area("分类", [ "西饭": [self._filter_area("分类", [
("全部", ""), ("都市", "68@都市"), ("青春", "68@青春"), ("全部", ""), ("都市", "68@都市"), ("青春", "68@青春"),
("现代言情", "81@现代言情"), ("豪门", "81@豪门"), ("现代言情", "81@现代言情"), ("豪门", "81@豪门"),
("大女主", "80@大女主"), ("逆袭", "79@逆袭"), ("大女主", "80@大女主"), ("逆袭", "79@逆袭"),
("打脸虐渣", "79@打脸虐渣"), ("穿越", "81@穿越"), ("打脸虐渣", "79@打脸虐渣"), ("穿越", "81@穿越"),
])], ])],
"围观": [self._filter_area("分类", [ "锦鲤": [self._filter_area("分类", [
("都市", "都市"), ("逆袭", "逆袭"), ("家庭", "家庭"), ("全部", ""), ("推荐", "1"), ("霸总", "2"), ("战神", "3"),
("复仇", "复仇"), ("古装", "古装"), ("甜宠", "甜宠"), ("神医", "4"), ("虐恋", "5"), ("萌宝", "6"), ("逆袭", "7"),
("悬疑", "悬疑"), ("爱情", "爱情"), ("虐恋", "虐恋"), ("穿越", "8"), ("古装", "9"), ("重生", "10"),
("动漫", "动漫"), ("重生", "重生"), ("奇幻", "奇幻"),
("总裁", "总裁"), ("穿越", "穿越"), ("萌宝", "萌宝"),
("乡村", "乡村"), ("修仙", "修仙"), ("异能", "异能"),
("战神", "战神"), ("职场", "职场"), ("年代", "年代"),
("正能量", "正能量"), ("神豪", "神豪"), ("喜剧", "喜剧"),
("青春", "青春"), ("宫廷", "宫廷"), ("武侠", "武侠"),
("神医", "神医"), ("科幻", "科幻"), ("赘婿", "赘婿"),
])],
"剧王": [self._filter_area("分类", [
("逆袭", "/tag/逆袭/"), ("霸总", "/tag/霸总/"),
("战神", "/tag/战神/"), ("穿越", "/tag/穿越/"),
("重生", "/tag/重生/"), ("赘婿", "/tag/赘婿/"),
("甜宠", "/tag/甜宠/"), ("虐恋", "/tag/虐恋/"),
("古装", "/tag/古装/"), ("都市", "/tag/都市/"),
("神医", "/tag/神医/"), ("玄幻", "/tag/玄幻/"),
("修仙", "/tag/修仙/"), ("女频", "/tag/女频/"),
("男频", "/tag/男频/"), ("情感", "/tag/情感/"),
("复仇", "/tag/复仇/"), ("现代", "/tag/现代/"),
("萌宝", "/tag/萌宝/"), ("虐渣", "/tag/虐渣/"),
("系统", "/tag/系统/"), ("打脸", "/tag/打脸/"),
("奇幻", "/tag/奇幻/"), ("家庭", "/tag/家庭/"),
("亲情", "/tag/亲情/"), ("言情", "/tag/言情/"),
("年代", "/tag/年代/"), ("悬疑", "/tag/悬疑/"),
("豪门", "/tag/豪门/"),
])],
"七星": [self._filter_area("分类", [
("七星剧场", "1"), ("七星新剧", "3"), ("七星热播", "2"),
("七星星选", "7"), ("七星阳光", "5"),
])], ])],
"红果": [self._filter_area("分类", [ "红果": [self._filter_area("分类", [
("逆袭", "逆袭"), ("霸总", "霸总"), ("现代言情", "现代言情"), ("逆袭", "逆袭"), ("霸总", "霸总"), ("现代言情", "现代言情"),
@@ -196,9 +178,8 @@ class Spider(BaseSpider):
])], ])],
} }
self.filter_defaults = { self.filter_defaults = {
"七猫": {"area": "0"}, "星芽": {"area": "1"}, "西饭": {"area": ""}, "七猫": {"area": "0"}, "星芽": {"area": "1"}, "星星": {"area": "1287"},
"围观": {"area": "都市"}, "剧王": {"area": "/tag/逆袭/"}, "西饭": {"area": ""}, "锦鲤": {"area": ""}, "红果": {"area": "逆袭"},
"七星": {"area": "1"}, "红果": {"area": "逆袭"},
"短剧网": {"area": "1"}, "短剧网": {"area": "1"},
} }
@@ -274,14 +255,10 @@ class Spider(BaseSpider):
if "红果" in flag: if "红果" in flag:
res = self._get_json("https://api-v2.cenguigui.cn/api/duanju/api.php?video_id=" + id) 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")} return {"parse": 0, "jx": 0, "url": res.get("url")}
if "星芽" in flag or "七猫" in flag or "西饭" in flag: if "锦鲤" in flag:
return self._play_jinli(id)
if "星芽" in flag or "星星" in flag or "七猫" in flag or "西饭" in flag:
return {"parse": 0, "jx": 0, "url": id} return {"parse": 0, "jx": 0, "url": id}
if "七星" in flag:
return {"parse": 0, "jx": 0, "url": id}
if "围观" in flag:
return self._play_weiguan(id)
if "剧王" in flag:
return self._play_juwang(id)
if "短剧网" in flag: if "短剧网" in flag:
return {"parse": 0, "jx": 0, "url": id} return {"parse": 0, "jx": 0, "url": id}
return {"parse": 0, "jx": 0, "url": id} return {"parse": 0, "jx": 0, "url": id}
@@ -397,6 +374,19 @@ class Spider(BaseSpider):
p.update(extra) p.update(extra)
return p return p
def _xingxing_headers(self):
return {"User-Agent": XINGXING_UA}
def _xingxing_url(self, path, params=None):
query = urlencode(dict(XINGXING_COMMON_PARAMS, **(params or {})))
return f"{XINGXING_HOST}{path}?{query}"
def _normalize_xingxing_play_url(self, value):
play_url = self._s(value).strip()
if play_url.startswith("http://img.novel.wsljf.xyz/"):
return "https://" + play_url[len("http://"):]
return play_url
# --- category per platform --- # --- category per platform ---
def _fetch_category(self, tid, page, area): def _fetch_category(self, tid, page, area):
@@ -406,10 +396,9 @@ class Spider(BaseSpider):
handler = { handler = {
"七猫": self._cat_qimao, "七猫": self._cat_qimao,
"星芽": self._cat_xingya, "星芽": self._cat_xingya,
"星星": self._cat_xingxing,
"西饭": self._cat_xifan, "西饭": self._cat_xifan,
"围观": self._cat_weiguan, "锦鲤": self._cat_jinli,
"剧王": self._cat_juwang,
"七星": self._cat_qixing,
"红果": self._cat_tianquan, "红果": self._cat_tianquan,
"短剧网": self._cat_duanjuwang, "短剧网": self._cat_duanjuwang,
}.get(tid) }.get(tid)
@@ -447,6 +436,25 @@ class Spider(BaseSpider):
}) })
return items return items
def _cat_xingxing(self, plat, page, area):
url = self._xingxing_url(
plat["url1"],
{"resourceId": area, "pageNum": str(page), "pageSize": "10"},
)
res = self._get_json(url, headers=self._xingxing_headers())
items = []
for it in (res.get("data") or {}).get("datalist") or []:
book_id = self._s(it.get("id")).strip()
name = self._s(it.get("name")).strip()
intro = self._s(it.get("introduction")).strip()
items.append({
"vod_id": f"星星@{book_id}@@{name}@@{intro}",
"vod_name": name,
"vod_pic": self._s(it.get("icon")).strip(),
"vod_remarks": f"{self._s(it.get('heat')).strip()}万播放" if self._s(it.get("heat")).strip() else "",
})
return items
def _cat_xifan(self, plat, page, area): def _cat_xifan(self, plat, page, area):
type_id, type_name = (area.split("@", 1) + [""])[:2] type_id, type_name = (area.split("@", 1) + [""])[:2]
params = self._xifan_params({ params = self._xifan_params({
@@ -469,6 +477,19 @@ class Spider(BaseSpider):
}) })
return items return items
def _cat_jinli(self, plat, page, area):
body = {"page": page, "limit": 24, "type_id": area, "year": "", "keyword": ""}
res = self._post_json(f"{plat['host']}{plat['search']}", body=body)
items = []
for it in (res.get("data") or {}).get("list") or []:
items.append({
"vod_id": f"锦鲤@{it.get('vod_id', '')}",
"vod_name": self._s(it.get("vod_name")),
"vod_pic": self._s(it.get("vod_pic")),
"vod_remarks": f"{it.get('vod_total', 0)}",
})
return items
def _cat_weiguan(self, plat, page, area): def _cat_weiguan(self, plat, page, area):
body = { body = {
"audience": "全部受众", "page": page, "pageSize": 30, "audience": "全部受众", "page": page, "pageSize": 30,
@@ -592,10 +613,9 @@ class Spider(BaseSpider):
handler = { handler = {
"七猫": self._detail_qimao, "七猫": self._detail_qimao,
"星芽": self._detail_xingya, "星芽": self._detail_xingya,
"星星": self._detail_xingxing,
"西饭": self._detail_xifan, "西饭": self._detail_xifan,
"围观": self._detail_weiguan, "锦鲤": self._detail_jinli,
"剧王": self._detail_juwang,
"七星": self._detail_qixing,
"红果": self._detail_tianquan, "红果": self._detail_tianquan,
"短剧网": self._detail_duanjuwang, "短剧网": self._detail_duanjuwang,
}.get(platform) }.get(platform)
@@ -641,6 +661,32 @@ class Spider(BaseSpider):
"vod_play_url": play_urls, "vod_play_url": play_urls,
} }
def _detail_xingxing(self, plat, detail_id, raw_id):
book_id, name, intro = (detail_id.split("@@", 2) + ["", ""])[:3]
url = self._xingxing_url(plat["url2"], {"bookId": book_id})
res = self._get_json(url, headers=self._xingxing_headers())
episodes = []
for index, chapter in enumerate(res.get("data") or [], start=1):
chapter_name = self._s(chapter.get("chapterName")).strip() or f"{index}"
play_url = ""
for play_group in chapter.get("shortPlayList") or []:
for play in play_group.get("chapterShortPlayVoList") or []:
play_url = self._normalize_xingxing_play_url(play.get("shortPlayUrl"))
if play_url:
break
if play_url:
break
if play_url:
episodes.append(f"{chapter_name}${play_url}")
return {
"vod_id": raw_id,
"vod_name": name,
"vod_content": intro,
"vod_remarks": f"{len(episodes)}" if episodes else "",
"vod_play_from": "星星短剧",
"vod_play_url": "#".join(episodes),
}
def _detail_xifan(self, plat, detail_id, raw_id): def _detail_xifan(self, plat, detail_id, raw_id):
duanju_id, source = detail_id.split("#", 1) if "#" in detail_id else (detail_id, "") duanju_id, source = detail_id.split("#", 1) if "#" in detail_id else (detail_id, "")
params = self._xifan_params({ params = self._xifan_params({
@@ -667,6 +713,23 @@ class Spider(BaseSpider):
"vod_play_url": play_urls or "暂无播放地址$0", "vod_play_url": play_urls or "暂无播放地址$0",
} }
def _detail_jinli(self, plat, detail_id, raw_id):
url = f"{plat['host']}{plat['url2']}/{detail_id}"
res = self._get_json(url)
data = res.get("data") or {}
player = data.get("player") or {}
play_urls = "#".join(f"{name}${url}" for name, url in player.items())
return {
"vod_id": raw_id,
"vod_name": self._s(data.get("vod_name")),
"vod_pic": self._s(data.get("vod_pic")),
"vod_remarks": self._s(data.get("vod_remarks")),
"vod_content": self._s(data.get("vod_blurb")),
"type_name": self._s(data.get("vod_class")),
"vod_play_from": "锦鲤短剧",
"vod_play_url": play_urls,
}
def _detail_weiguan(self, plat, detail_id, raw_id): def _detail_weiguan(self, plat, detail_id, raw_id):
url = f"{plat['host']}{plat['url2']}?oneId={detail_id}&page=1&pageSize=1000" url = f"{plat['host']}{plat['url2']}?oneId={detail_id}&page=1&pageSize=1000"
res = self._get_json(url) res = self._get_json(url)
@@ -862,10 +925,9 @@ class Spider(BaseSpider):
handler = { handler = {
"七猫": self._search_qimao, "七猫": self._search_qimao,
"星芽": self._search_xingya, "星芽": self._search_xingya,
"星星": self._search_xingxing,
"西饭": self._search_xifan, "西饭": self._search_xifan,
"围观": self._search_weiguan, "锦鲤": self._search_jinli,
"剧王": self._search_juwang,
"七星": self._search_qixing,
"红果": self._search_tianquan, "红果": self._search_tianquan,
"短剧网": self._search_duanjuwang, "短剧网": self._search_duanjuwang,
}.get(platform_id) }.get(platform_id)
@@ -902,6 +964,9 @@ class Spider(BaseSpider):
}) })
return items return items
def _search_xingxing(self, plat, keyword, page):
return []
def _search_xifan(self, plat, keyword, page): def _search_xifan(self, plat, keyword, page):
params = self._xifan_params({ params = self._xifan_params({
"reqType": "search", "reqType": "search",
@@ -921,6 +986,19 @@ class Spider(BaseSpider):
}) })
return items return items
def _search_jinli(self, plat, keyword, page):
body = {"page": page, "limit": 30, "type_id": "", "year": "", "keyword": keyword}
res = self._post_json(f"{plat['host']}{plat['search']}", body=body)
items = []
for it in (res.get("data") or {}).get("list") or []:
items.append({
"vod_id": f"锦鲤@{it.get('vod_id', '')}",
"vod_name": self._s(it.get("vod_name")),
"vod_pic": self._s(it.get("vod_pic")),
"vod_remarks": f"锦鲤短剧 | {it.get('vod_total', 0)}",
})
return items
def _search_weiguan(self, plat, keyword, page): def _search_weiguan(self, plat, keyword, page):
body = { body = {
"audience": "", "page": page, "pageSize": 30, "audience": "", "page": page, "pageSize": 30,
@@ -1056,3 +1134,21 @@ class Spider(BaseSpider):
), ),
}, },
} }
def _play_jinli(self, play_id):
html_text = self._get_text(play_id, headers={"referer": "https://www.jinlidj.com/"})
trimmed = html_text.strip()
if trimmed[:1] in ("{", "["):
try:
data = json.loads(trimmed)
video_url = data.get("url") or (data.get("data") or {}).get("url") or ""
if video_url:
return {"parse": 0, "jx": 0, "url": video_url}
except (TypeError, ValueError):
pass
match = re.search(r'https?://[^\'"\\s]+\\.(?:m3u8|mp4)(?:\\?[^\'"\\s]*)?', html_text, re.I)
if match:
return {"parse": 0, "jx": 0, "url": match.group(0)}
match = re.search(r'url\\s*:\\s*[\'"]([^\'"]+)[\'"]', html_text)
video_url = match.group(1) if match else play_id
return {"parse": 0, "jx": 0, "url": video_url}