短剧优选

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 unittest
from importlib.machinery import SourceFileLoader
from pathlib import Path
from unittest.mock import patch, MagicMock
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
MODULE = SourceFileLoader("duanjuyx_spider", str(ROOT / "短剧优选.py")).load_module()
@@ -13,164 +13,164 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
def setUp(self):
Spider._instance = None
self.spider = Spider()
self.spider.init()
self.spider.xingya_headers = {"authorization": "token"}
def test_home_content_has_active_platforms(self):
content = self.spider.homeContent(False)
ids = [c["type_id"] for c in content["class"]]
self.assertIn("七猫", ids)
self.assertIn("", ids)
self.assertIn("西饭", ids)
self.assertIn("七星", ids)
self.assertIn("短剧网", ids)
self.assertEqual(ids, ["七猫", "星芽", "星星", "西饭", "锦鲤", "红果", "短剧网"])
self.assertNotIn("", ids)
self.assertNotIn("围观", ids)
def test_home_content_has_filters(self):
content = self.spider.homeContent(False)
self.assertIn("七猫", content["filters"])
self.assertIn("星星", content["filters"])
self.assertIn("锦鲤", content["filters"])
self.assertIn("短剧网", content["filters"])
qm_filter = content["filters"]["七猫"][0]
self.assertEqual(qm_filter["key"], "area")
self.assertTrue(len(qm_filter["value"]) > 50)
self.assertEqual(content["filters"]["锦鲤"][0]["key"], "area")
def test_home_video_content_returns_empty(self):
self.assertEqual(self.spider.homeVideoContent(), {"list": []})
def test_player_tianquan(self):
result = self.spider.playerContent("甜圈短剧", "vid123", {})
@patch.object(Spider, "_get_json")
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.assertIn("video_id=vid123", result["url"])
self.assertEqual(result["url"], "https://media.example.com/play.mp4")
def test_player_xingya_passthrough(self):
result = self.spider.playerContent("星芽短剧", "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):
result = self.spider.playerContent("短剧网", "push://pan.quark.cn/abc", {})
self.assertEqual(result["parse"], 0)
self.assertEqual(result["url"], "push://pan.quark.cn/abc")
@patch.object(Spider, "_get_text")
def test_player_juwang_extracts_m3u8(self, mock_text):
mock_text.return_value = 'var data = {"wwm3u8": "https:\\/\\/cdn.example.com\\/video.m3u8"};'
result = self.spider.playerContent("剧王短剧", "https://djw1.com/play/123", {})
self.assertEqual(result["url"], "https://cdn.example.com/video.m3u8")
self.assertEqual(result["parse"], 0)
@patch.object(Spider, "_post_json")
def test_category_jinli(self, mock_post_json):
mock_post_json.return_value = {
"data": {
"list": [
{"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")
def test_category_tianquan(self, mock_json):
mock_json.return_value = {
def test_category_xingxing(self, mock_get_json):
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": [
{"book_id": "b1", "title": "逆袭人生", "cover": "http://pic/1.jpg", "sub_title": "60集"},
{"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(result["list"][0]["vod_id"], "甜圈@b1")
self.assertEqual(result["list"][0]["vod_name"], "逆袭人生")
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")
self.assertEqual(result["list"][0]["vod_id"], "红果@b1")
self.assertEqual(result["page"], 1)
@patch.object(Spider, "_get_text")
def test_category_duanjuwang(self, mock_text):
mock_text.return_value = """
def test_category_duanjuwang(self, mock_get_text):
mock_get_text.return_value = """
<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" />
</li>
"""
result = self.spider.categoryContent("短剧网", "1", False, {"area": "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_name"], "短剧名(60集完结)")
self.assertEqual(result["list"][0]["vod_remarks"], "")
self.assertEqual(result["pagecount"], 2)
self.assertEqual(result["list"][0]["vod_id"], "短剧网@https://sm3.cc/?id=123")
self.assertEqual(result["page"], 1)
@patch.object(Spider, "_get_json")
def test_category_empty_result(self, mock_json):
mock_json.return_value = {"data": []}
result = self.spider.categoryContent("甜圈", "2", False, {"area": "逆袭"})
self.assertEqual(result["list"], [])
self.assertEqual(result["pagecount"], 2)
def test_detail_jinli(self, mock_get_json):
mock_get_json.return_value = {
"data": {
"vod_name": "重生逆袭遇良人",
"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")
def test_detail_tianquan(self, mock_json):
mock_json.return_value = {
"book_name": "甜剧1号", "book_pic": "http://pic/t.jpg", "desc": "剧情简介",
"duration": "60分钟", "author": "作者", "time": "2025-01-01",
def test_detail_xingxing(self, mock_get_json):
mock_get_json.return_value = {
"data": [
{
"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": [
{"title": "第1集", "video_id": "v1"},
{"title": "第2集", "video_id": "v2"},
],
}
result = self.spider.detailContent(["甜圈@b1"])
result = self.spider.detailContent(["红果@b1"])
vod = result["list"][0]
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("第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")
def test_detail_juwang(self, mock_text):
mock_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 = """
def test_detail_duanjuwang(self, mock_get_text):
mock_get_text.return_value = """
<html><body>
<h1>网盘短剧名</h1>
<img class="lazy" data-original="/poster.jpg" />
@@ -180,7 +180,7 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
</div>
</body></html>
"""
result = self.spider.detailContent(["短剧网@https://sm3.cc/detail/123"])
result = self.spider.detailContent(["短剧网@https://sm3.cc/?id=123"])
vod = result["list"][0]
self.assertEqual(vod["vod_name"], "网盘短剧名")
self.assertEqual(vod["vod_content"], "此为推送网盘规则")
@@ -188,32 +188,18 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
self.assertIn("baidu", vod["vod_play_from"])
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")
def test_detail_failure_returns_error_vod(self, mock_json):
mock_json.side_effect = Exception("network error")
result = self.spider.detailContent(["甜圈@missing"])
def test_detail_failure_returns_error_vod(self, mock_get_json):
mock_get_json.side_effect = Exception("network error")
result = self.spider.detailContent(["红果@missing"])
vod = result["list"][0]
self.assertIn("详情加载失败", vod["vod_name"])
@patch.object(Spider, "_aggregate_search")
def test_search_filters_by_keyword(self, mock_search):
mock_search.return_value = [
{"vod_id": "甜圈@1", "vod_name": "逆袭人生", "vod_pic": "", "vod_remarks": ""},
{"vod_id": "七猫@2", "vod_name": "霸道总裁", "vod_pic": "", "vod_remarks": ""},
{"vod_id": "红果@1", "vod_name": "逆袭人生", "vod_pic": "", "vod_remarks": ""},
{"vod_id": "锦鲤@2", "vod_name": "霸道总裁", "vod_pic": "", "vod_remarks": ""},
]
result = self.spider.searchContent("逆袭", False, "1")
self.assertEqual(len(result["list"]), 1)
@@ -226,8 +212,8 @@ class TestDuanjuYouxuanSpider(unittest.TestCase):
mock_search.assert_not_called()
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")
def test_identify_disk(self):
+168 -72
View File
@@ -36,6 +36,21 @@ XIFAN_FEEDS = (
"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):
def __init__(self):
@@ -59,30 +74,22 @@ class Spider(BaseSpider):
"search": "/v3/search",
"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",
"url1": "/xifan/drama/portalPage",
"url2": "/xifan/drama/getDuanjuInfo",
"search": "/xifan/search/getSearchList",
},
"围观": {
"host": "https://api.drama.9ddm.com",
"url1": "/drama/home/shortVideoTags",
"url2": "/drama/home/shortVideoDetail",
"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.jinlidj.com",
# "search": "/api/search",
# "url2": "/api/detail",
# },
"红果": {
"host": "https://api-v2.cenguigui.cn",
"url1": "/api/duanju/api.php?classname",
@@ -96,10 +103,9 @@ class Spider(BaseSpider):
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": "短剧网"},
]
@@ -125,44 +131,20 @@ class Spider(BaseSpider):
("剧场", "1"), ("热播", "2"), ("新剧", "3"),
("阳光剧场", "5"), ("星选好剧", "7"), ("会员专享", "8"),
])],
"星星": [self._filter_area("分类", [
("甜宠", "1287"), ("逆袭", "1288"), ("热血", "1289"),
("现代", "1290"), ("古代", "1291"),
])],
"西饭": [self._filter_area("分类", [
("全部", ""), ("都市", "68@都市"), ("青春", "68@青春"),
("现代言情", "81@现代言情"), ("豪门", "81@豪门"),
("大女主", "80@大女主"), ("逆袭", "79@逆袭"),
("打脸虐渣", "79@打脸虐渣"), ("穿越", "81@穿越"),
])],
"围观": [self._filter_area("分类", [
("都市", "都市"), ("逆袭", "逆袭"), ("家庭", "家庭"),
("复仇", "复仇"), ("古装", "古装"), ("甜宠", "甜宠"),
("悬疑", "悬疑"), ("爱情", "爱情"), ("虐恋", "虐恋"),
("动漫", "动漫"), ("重生", "重生"), ("奇幻", "奇幻"),
("总裁", "总裁"), ("穿越", "穿越"), ("萌宝", "萌宝"),
("乡村", "乡村"), ("修仙", "修仙"), ("异能", "异能"),
("战神", "战神"), ("职场", "职场"), ("年代", "年代"),
("正能量", "正能量"), ("神豪", "神豪"), ("喜剧", "喜剧"),
("青春", "青春"), ("宫廷", "宫廷"), ("武侠", "武侠"),
("神医", "神医"), ("科幻", "科幻"), ("赘婿", "赘婿"),
])],
"剧王": [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("分类", [
("全部", ""), ("推荐", "1"), ("霸总", "2"), ("战神", "3"),
("神医", "4"), ("虐恋", "5"), ("萌宝", "6"), ("逆袭", "7"),
("穿越", "8"), ("古装", "9"), ("重生", "10"),
])],
"红果": [self._filter_area("分类", [
("逆袭", "逆袭"), ("霸总", "霸总"), ("现代言情", "现代言情"),
@@ -196,9 +178,8 @@ class Spider(BaseSpider):
])],
}
self.filter_defaults = {
"七猫": {"area": "0"}, "星芽": {"area": "1"}, "西饭": {"area": ""},
"围观": {"area": "都市"}, "剧王": {"area": "/tag/逆袭/"},
"七星": {"area": "1"}, "红果": {"area": "逆袭"},
"七猫": {"area": "0"}, "星芽": {"area": "1"}, "星星": {"area": "1287"},
"西饭": {"area": ""}, "锦鲤": {"area": ""}, "红果": {"area": "逆袭"},
"短剧网": {"area": "1"},
}
@@ -274,14 +255,10 @@ class Spider(BaseSpider):
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:
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}
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:
return {"parse": 0, "jx": 0, "url": id}
return {"parse": 0, "jx": 0, "url": id}
@@ -397,6 +374,19 @@ class Spider(BaseSpider):
p.update(extra)
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 ---
def _fetch_category(self, tid, page, area):
@@ -406,10 +396,9 @@ class Spider(BaseSpider):
handler = {
"七猫": self._cat_qimao,
"星芽": self._cat_xingya,
"星星": self._cat_xingxing,
"西饭": self._cat_xifan,
"围观": self._cat_weiguan,
"剧王": self._cat_juwang,
"七星": self._cat_qixing,
"锦鲤": self._cat_jinli,
"红果": self._cat_tianquan,
"短剧网": self._cat_duanjuwang,
}.get(tid)
@@ -447,6 +436,25 @@ class Spider(BaseSpider):
})
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):
type_id, type_name = (area.split("@", 1) + [""])[:2]
params = self._xifan_params({
@@ -469,6 +477,19 @@ class Spider(BaseSpider):
})
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):
body = {
"audience": "全部受众", "page": page, "pageSize": 30,
@@ -592,10 +613,9 @@ class Spider(BaseSpider):
handler = {
"七猫": self._detail_qimao,
"星芽": self._detail_xingya,
"星星": self._detail_xingxing,
"西饭": self._detail_xifan,
"围观": self._detail_weiguan,
"剧王": self._detail_juwang,
"七星": self._detail_qixing,
"锦鲤": self._detail_jinli,
"红果": self._detail_tianquan,
"短剧网": self._detail_duanjuwang,
}.get(platform)
@@ -641,6 +661,32 @@ class Spider(BaseSpider):
"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):
duanju_id, source = detail_id.split("#", 1) if "#" in detail_id else (detail_id, "")
params = self._xifan_params({
@@ -667,6 +713,23 @@ class Spider(BaseSpider):
"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):
url = f"{plat['host']}{plat['url2']}?oneId={detail_id}&page=1&pageSize=1000"
res = self._get_json(url)
@@ -862,10 +925,9 @@ class Spider(BaseSpider):
handler = {
"七猫": self._search_qimao,
"星芽": self._search_xingya,
"星星": self._search_xingxing,
"西饭": self._search_xifan,
"围观": self._search_weiguan,
"剧王": self._search_juwang,
"七星": self._search_qixing,
"锦鲤": self._search_jinli,
"红果": self._search_tianquan,
"短剧网": self._search_duanjuwang,
}.get(platform_id)
@@ -902,6 +964,9 @@ class Spider(BaseSpider):
})
return items
def _search_xingxing(self, plat, keyword, page):
return []
def _search_xifan(self, plat, keyword, page):
params = self._xifan_params({
"reqType": "search",
@@ -921,6 +986,19 @@ class Spider(BaseSpider):
})
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):
body = {
"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}