feat: add nsvod spider
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
import unittest
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MODULE = SourceFileLoader("nsvod_spider", str(ROOT / "耐视点播.py")).load_module()
|
||||
Spider = MODULE.Spider
|
||||
|
||||
|
||||
class TestNSVodSpider(unittest.TestCase):
|
||||
def setUp(self):
|
||||
Spider._instance = None
|
||||
self.spider = Spider()
|
||||
self.spider.init()
|
||||
|
||||
def test_home_content_exposes_fixed_classes(self):
|
||||
content = self.spider.homeContent(False)
|
||||
self.assertEqual(
|
||||
[(item["type_id"], item["type_name"]) for item in content["class"]],
|
||||
[
|
||||
("1", "电影"),
|
||||
("2", "连续剧"),
|
||||
("3", "综艺"),
|
||||
("4", "动漫"),
|
||||
("37", "Netflix"),
|
||||
("40", "纪录片"),
|
||||
],
|
||||
)
|
||||
|
||||
def test_build_url_and_extract_vod_id_handle_relative_paths(self):
|
||||
self.assertEqual(
|
||||
self.spider._build_url("/index.php/vod/detail/id/7.html"),
|
||||
"https://nsvod.me/index.php/vod/detail/id/7.html",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.spider._build_url("//img.example/poster.jpg"),
|
||||
"https://img.example/poster.jpg",
|
||||
)
|
||||
self.assertEqual(self.spider._extract_vod_id("/index.php/vod/detail/id/99.html"), "99")
|
||||
self.assertEqual(self.spider._extract_vod_id("/bad/path"), "")
|
||||
|
||||
def test_parse_cards_extracts_unique_videos(self):
|
||||
html = """
|
||||
<a href="/index.php/vod/detail/id/11.html" title="示例A">
|
||||
<img data-src="/a.jpg" />
|
||||
<span class="public-list-prb">更新至01集</span>
|
||||
</a>
|
||||
<a href="/index.php/vod/detail/id/12.html" title="示例B">
|
||||
<img src="/b.jpg" />
|
||||
<div class="public-list-subtitle">HD</div>
|
||||
</a>
|
||||
<a href="/index.php/vod/detail/id/11.html" title="示例A">
|
||||
<img data-src="/dup.jpg" />
|
||||
</a>
|
||||
"""
|
||||
self.assertEqual(
|
||||
self.spider._parse_cards(html),
|
||||
[
|
||||
{
|
||||
"vod_id": "11",
|
||||
"vod_name": "示例A",
|
||||
"vod_pic": "https://nsvod.me/a.jpg",
|
||||
"vod_remarks": "更新至01集",
|
||||
},
|
||||
{
|
||||
"vod_id": "12",
|
||||
"vod_name": "示例B",
|
||||
"vod_pic": "https://nsvod.me/b.jpg",
|
||||
"vod_remarks": "HD",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_home_video_content_reads_home_page(self, mock_request_html):
|
||||
mock_request_html.return_value = """
|
||||
<a href="/index.php/vod/detail/id/88.html" title="首页片">
|
||||
<img data-src="/home.jpg" />
|
||||
<span class="public-list-prb">热播</span>
|
||||
</a>
|
||||
"""
|
||||
result = self.spider.homeVideoContent()
|
||||
self.assertEqual(mock_request_html.call_args.args[0], "https://nsvod.me/")
|
||||
self.assertEqual(
|
||||
result,
|
||||
{
|
||||
"list": [
|
||||
{
|
||||
"vod_id": "88",
|
||||
"vod_name": "首页片",
|
||||
"vod_pic": "https://nsvod.me/home.jpg",
|
||||
"vod_remarks": "热播",
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_category_content_reads_category_page(self, mock_request_html):
|
||||
mock_request_html.return_value = """
|
||||
<a href="/index.php/vod/detail/id/21.html" title="分类片">
|
||||
<img data-src="/cate.jpg" />
|
||||
<span class="public-list-prb">更新中</span>
|
||||
</a>
|
||||
"""
|
||||
result = self.spider.categoryContent("2", "3", False, {})
|
||||
self.assertEqual(
|
||||
mock_request_html.call_args.args[0],
|
||||
"https://nsvod.me/index.php/vod/show/id/2.html?page=3",
|
||||
)
|
||||
self.assertEqual(result["page"], 3)
|
||||
self.assertEqual(result["limit"], 1)
|
||||
self.assertEqual(result["list"][0]["vod_id"], "21")
|
||||
self.assertNotIn("pagecount", result)
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_category_content_falls_back_to_home_sections(self, mock_request_html):
|
||||
mock_request_html.side_effect = [
|
||||
"<html></html>",
|
||||
"""
|
||||
<div class="title-h cor4">最新综艺</div>
|
||||
<a href="/index.php/vod/detail/id/31.html" title="综艺回退">
|
||||
<img data-src="/variety.jpg" />
|
||||
<span class="public-list-prb">第1期</span>
|
||||
</a>
|
||||
<div class="title-h cor4">最新纪录片</div>
|
||||
""",
|
||||
]
|
||||
result = self.spider.categoryContent("3", "1", False, {})
|
||||
self.assertEqual([item["vod_id"] for item in result["list"]], ["31"])
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_search_content_short_circuits_blank_keyword_and_parses_results(self, mock_request_html):
|
||||
self.assertEqual(self.spider.searchContent("", False, "1"), {"page": 1, "total": 0, "list": []})
|
||||
mock_request_html.assert_not_called()
|
||||
|
||||
mock_request_html.return_value = """
|
||||
<div class="module-search-item">
|
||||
<a class="video-serial" href="/index.php/vod/detail/id/45.html" title="搜索片">抢先版</a>
|
||||
<div class="module-item-pic"><img data-src="/search.jpg" alt="搜索片" /></div>
|
||||
</div>
|
||||
"""
|
||||
result = self.spider.searchContent("繁花", False, "2")
|
||||
self.assertEqual(
|
||||
mock_request_html.call_args.args[0],
|
||||
"https://nsvod.me/index.php/vod/search.html?wd=%E7%B9%81%E8%8A%B1",
|
||||
)
|
||||
self.assertEqual(result["total"], 1)
|
||||
self.assertEqual(
|
||||
result["list"][0],
|
||||
{
|
||||
"vod_id": "45",
|
||||
"vod_name": "搜索片",
|
||||
"vod_pic": "https://nsvod.me/search.jpg",
|
||||
"vod_remarks": "抢先版",
|
||||
},
|
||||
)
|
||||
|
||||
def test_parse_detail_page_extracts_metadata_and_play_groups(self):
|
||||
html = """
|
||||
<title>《耐视示例》在线观看</title>
|
||||
<div class="detail-pic"><img data-src="/poster-detail.jpg" /></div>
|
||||
<div>年份</em><span>2025</span></div>
|
||||
<div>地区</em><span>中国香港</span></div>
|
||||
<div>导演</em> 导演甲 </div>
|
||||
<div>主演</em> 演员甲 / 演员乙 </div>
|
||||
<div id="height_limit"><p>这是一段简介。</p></div>
|
||||
<div class="anthology-tab">
|
||||
<div class="swiper-slide">线路A 2集</div>
|
||||
<div class="swiper-slide">线路B 1集</div>
|
||||
</div>
|
||||
<div class="anthology-list">
|
||||
<div class="anthology-list-box">
|
||||
<a href="/index.php/vod/play/id/70/sid/1/nid/1.html">第1集</a>
|
||||
<a href="/index.php/vod/play/id/70/sid/1/nid/2.html">第2集</a>
|
||||
</div>
|
||||
<div class="anthology-list-box">
|
||||
<a href="/index.php/vod/play/id/70/sid/2/nid/1.html">正片</a>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
vod = self.spider._parse_detail_page("70", html)
|
||||
self.assertEqual(vod["vod_name"], "耐视示例")
|
||||
self.assertEqual(vod["vod_pic"], "https://nsvod.me/poster-detail.jpg")
|
||||
self.assertEqual(vod["vod_year"], "2025")
|
||||
self.assertEqual(vod["vod_area"], "中国香港")
|
||||
self.assertEqual(vod["vod_director"], "导演甲")
|
||||
self.assertEqual(vod["vod_actor"], "演员甲 / 演员乙")
|
||||
self.assertEqual(vod["vod_content"], "这是一段简介。")
|
||||
self.assertEqual(vod["vod_play_from"], "线路A$$$线路B")
|
||||
self.assertEqual(
|
||||
vod["vod_play_url"],
|
||||
"第1集$/index.php/vod/play/id/70/sid/1/nid/1.html#第2集$/index.php/vod/play/id/70/sid/1/nid/2.html$$$正片$/index.php/vod/play/id/70/sid/2/nid/1.html",
|
||||
)
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_detail_content_reads_detail_url_and_returns_single_vod(self, mock_request_html):
|
||||
mock_request_html.return_value = """
|
||||
<div class="slide-info-title">详情标题</div>
|
||||
<div class="anthology-list">
|
||||
<div class="anthology-list-box">
|
||||
<a href="/index.php/vod/play/id/80/sid/1/nid/1.html">正片</a>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
result = self.spider.detailContent(["80"])
|
||||
self.assertEqual(
|
||||
mock_request_html.call_args.args[0],
|
||||
"https://nsvod.me/index.php/vod/detail/id/80.html",
|
||||
)
|
||||
self.assertEqual(result["list"][0]["vod_id"], "80")
|
||||
self.assertEqual(result["list"][0]["vod_name"], "详情标题")
|
||||
self.assertEqual(result["list"][0]["vod_play_from"], "线路1")
|
||||
self.assertEqual(result["list"][0]["vod_play_url"], "正片$/index.php/vod/play/id/80/sid/1/nid/1.html")
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_player_content_returns_player_aaaa_url(self, mock_request_html):
|
||||
mock_request_html.return_value = """
|
||||
<script>
|
||||
var player_aaaa={"url":"https://cdn.example/direct.m3u8","encrypt":"0"}
|
||||
</script>
|
||||
"""
|
||||
result = self.spider.playerContent("线路A", "/index.php/vod/play/id/70/sid/1/nid/1.html", {})
|
||||
self.assertEqual(result["parse"], 0)
|
||||
self.assertEqual(result["jx"], 0)
|
||||
self.assertEqual(result["url"], "https://cdn.example/direct.m3u8")
|
||||
self.assertEqual(result["header"]["Referer"], "https://nsvod.me/index.php/vod/play/id/70/sid/1/nid/1.html")
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_player_content_falls_back_to_inline_m3u8(self, mock_request_html):
|
||||
mock_request_html.return_value = '<script>var now="https://cdn.example/fallback.m3u8?token=1"</script>'
|
||||
result = self.spider.playerContent("线路A", "/index.php/vod/play/id/71/sid/1/nid/1.html", {})
|
||||
self.assertEqual(result["url"], "https://cdn.example/fallback.m3u8?token=1")
|
||||
self.assertEqual(result["jx"], 0)
|
||||
|
||||
@patch.object(Spider, "_request_html")
|
||||
def test_player_content_returns_play_page_when_no_media_url_found(self, mock_request_html):
|
||||
mock_request_html.return_value = "<html><body>empty</body></html>"
|
||||
result = self.spider.playerContent("线路A", "/index.php/vod/play/id/72/sid/1/nid/1.html", {})
|
||||
self.assertEqual(result["parse"], 0)
|
||||
self.assertEqual(result["jx"], 1)
|
||||
self.assertEqual(result["playUrl"], "")
|
||||
self.assertEqual(result["url"], "https://nsvod.me/index.php/vod/play/id/72/sid/1/nid/1.html")
|
||||
self.assertEqual(result["header"]["Referer"], "https://nsvod.me/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
# coding=utf-8
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import quote, urljoin
|
||||
|
||||
from base.spider import Spider as BaseSpider
|
||||
|
||||
sys.path.append("..")
|
||||
|
||||
|
||||
class Spider(BaseSpider):
|
||||
def __init__(self):
|
||||
self.name = "耐视点播"
|
||||
self.host = "https://nsvod.me"
|
||||
self.headers = {
|
||||
"User-Agent": (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/120.0.0.0 Safari/537.36"
|
||||
),
|
||||
"Referer": self.host + "/",
|
||||
}
|
||||
self.classes = [
|
||||
{"type_id": "1", "type_name": "电影"},
|
||||
{"type_id": "2", "type_name": "连续剧"},
|
||||
{"type_id": "3", "type_name": "综艺"},
|
||||
{"type_id": "4", "type_name": "动漫"},
|
||||
{"type_id": "37", "type_name": "Netflix"},
|
||||
{"type_id": "40", "type_name": "纪录片"},
|
||||
]
|
||||
|
||||
def init(self, extend=""):
|
||||
return None
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def homeContent(self, filter):
|
||||
return {"class": self.classes}
|
||||
|
||||
def _stringify(self, value):
|
||||
return "" if value is None else str(value)
|
||||
|
||||
def _clean_text(self, text):
|
||||
raw = self._stringify(text).replace(" ", " ")
|
||||
raw = re.sub(r"<[^>]+>", " ", raw)
|
||||
return re.sub(r"\s+", " ", raw).strip()
|
||||
|
||||
def _build_url(self, path):
|
||||
raw = self._stringify(path).strip()
|
||||
if not raw:
|
||||
return ""
|
||||
if raw.startswith(("http://", "https://")):
|
||||
return raw
|
||||
if raw.startswith("//"):
|
||||
return "https:" + raw
|
||||
return urljoin(self.host + "/", raw)
|
||||
|
||||
def _extract_vod_id(self, href):
|
||||
matched = re.search(r"/index\.php/vod/detail/id/(\d+)\.html", self._stringify(href))
|
||||
return matched.group(1) if matched else ""
|
||||
|
||||
def _build_detail_url(self, vod_id):
|
||||
return self._build_url(f"/index.php/vod/detail/id/{self._stringify(vod_id).strip()}.html")
|
||||
|
||||
def _request_html(self, path_or_url, headers=None, referer=None):
|
||||
target = path_or_url if self._stringify(path_or_url).startswith("http") else self._build_url(path_or_url)
|
||||
merged_headers = dict(self.headers)
|
||||
if headers:
|
||||
merged_headers.update(headers)
|
||||
merged_headers["Referer"] = referer or self.headers["Referer"]
|
||||
response = self.fetch(target, headers=merged_headers, timeout=10)
|
||||
if response.status_code != 200:
|
||||
return ""
|
||||
return response.text or ""
|
||||
|
||||
def _parse_cards(self, html):
|
||||
items = []
|
||||
seen = set()
|
||||
for block, href, vod_id, title in re.findall(
|
||||
r'(<a[^>]*href="(/index\.php/vod/detail/id/(\d+)\.html)"[^>]*title="([^"]*)"[^>]*>[\s\S]*?</a>)',
|
||||
self._stringify(html),
|
||||
re.I,
|
||||
):
|
||||
if vod_id in seen or not title:
|
||||
continue
|
||||
seen.add(vod_id)
|
||||
pic_match = re.search(r'data-src="([^"]*)"', block, re.I) or re.search(r'src="([^"]*)"', block, re.I)
|
||||
remarks_match = re.search(r'public-list-prb[^>]*>([^<]*)</span>', block, re.I)
|
||||
if not remarks_match:
|
||||
remarks_match = re.search(r'public-list-subtitle[^>]*>([^<]*)</div>', block, re.I)
|
||||
items.append(
|
||||
{
|
||||
"vod_id": vod_id,
|
||||
"vod_name": title.strip(),
|
||||
"vod_pic": self._build_url(pic_match.group(1) if pic_match else ""),
|
||||
"vod_remarks": self._clean_text(remarks_match.group(1) if remarks_match else ""),
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {"list": self._parse_cards(self._request_html(self.host + "/"))}
|
||||
|
||||
def _extract_section_cards(self, html, tid):
|
||||
section_map = {
|
||||
"1": "最新电影",
|
||||
"2": "最新连续剧",
|
||||
"3": "最新综艺",
|
||||
"4": "最新动漫",
|
||||
"37": "最新Netflix",
|
||||
"40": "最新纪录片",
|
||||
}
|
||||
section_name = section_map.get(self._stringify(tid))
|
||||
if not section_name:
|
||||
return []
|
||||
markers = [
|
||||
"最新热播",
|
||||
"最新Netflix",
|
||||
"最新电影",
|
||||
"最新连续剧",
|
||||
"最新资讯",
|
||||
"最新动漫",
|
||||
"最新综艺",
|
||||
"最新纪录片",
|
||||
]
|
||||
body = self._stringify(html)
|
||||
start_tag = f'title-h cor4">{section_name}'
|
||||
start = body.find(start_tag)
|
||||
if start < 0:
|
||||
return []
|
||||
end = len(body)
|
||||
for name in markers:
|
||||
if name == section_name:
|
||||
continue
|
||||
pos = body.find(f'title-h cor4">{name}', start + len(start_tag))
|
||||
if pos > 0 and pos < end:
|
||||
end = pos
|
||||
return self._parse_cards(body[start:end])
|
||||
|
||||
def _parse_search_cards(self, html):
|
||||
items = []
|
||||
seen = set()
|
||||
for block in re.findall(
|
||||
r'(<div[^>]*class="[^"]*module-search-item[^"]*"[^>]*>[\s\S]*?</div>)',
|
||||
self._stringify(html),
|
||||
re.I,
|
||||
):
|
||||
href_match = re.search(r'class="[^"]*video-serial[^"]*"[^>]*href="([^"]*)"', block, re.I)
|
||||
title_match = re.search(r'class="[^"]*video-serial[^"]*"[^>]*title="([^"]*)"', block, re.I)
|
||||
pic_match = re.search(r'data-src="([^"]*)"', block, re.I) or re.search(r'src="([^"]*)"', block, re.I)
|
||||
remark_match = re.search(r'class="[^"]*video-serial[^"]*"[^>]*>([^<]*)</a>', block, re.I)
|
||||
vod_id = self._extract_vod_id(href_match.group(1) if href_match else "")
|
||||
if not vod_id or vod_id in seen:
|
||||
continue
|
||||
seen.add(vod_id)
|
||||
items.append(
|
||||
{
|
||||
"vod_id": vod_id,
|
||||
"vod_name": self._clean_text(title_match.group(1) if title_match else ""),
|
||||
"vod_pic": self._build_url(pic_match.group(1) if pic_match else ""),
|
||||
"vod_remarks": self._clean_text(remark_match.group(1) if remark_match else ""),
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
page = int(pg)
|
||||
url = self._build_url(f"/index.php/vod/show/id/{tid}.html?page={page}")
|
||||
items = self._parse_cards(self._request_html(url))
|
||||
if not items:
|
||||
items = self._extract_section_cards(self._request_html(self.host + "/"), tid)
|
||||
return {"page": page, "limit": len(items), "total": page * 20 + len(items), "list": items}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
page = int(pg)
|
||||
keyword = self._clean_text(key)
|
||||
if not keyword:
|
||||
return {"page": page, "total": 0, "list": []}
|
||||
url = self._build_url(f"/index.php/vod/search.html?wd={quote(keyword)}")
|
||||
items = self._parse_search_cards(self._request_html(url))
|
||||
return {"page": page, "total": len(items), "list": items}
|
||||
|
||||
def _strip_title(self, title):
|
||||
matched = re.search(r"《([^》]+)》", self._stringify(title))
|
||||
if matched:
|
||||
return matched.group(1)
|
||||
return self._clean_text(title)
|
||||
|
||||
def _parse_play_groups(self, html):
|
||||
source_names = []
|
||||
for raw in re.findall(r'<div[^>]*class="[^"]*swiper-slide[^"]*"[^>]*>([\s\S]*?)</div>', self._stringify(html), re.I):
|
||||
cleaned = re.sub(r"\d+\s*集?$", "", self._clean_text(raw)).strip()
|
||||
if cleaned:
|
||||
source_names.append(cleaned)
|
||||
|
||||
groups = []
|
||||
for index, block in enumerate(
|
||||
re.findall(r'(<div[^>]*class="[^"]*anthology-list-box[^"]*"[^>]*>[\s\S]*?</div>)', self._stringify(html), re.I)
|
||||
):
|
||||
episodes = []
|
||||
for href, title in re.findall(r'<a[^>]*href="([^"]*/index\.php/vod/play/[^"]*)"[^>]*>([\s\S]*?)</a>', block, re.I):
|
||||
episodes.append(f"{self._clean_text(title)}${href.replace('&', '&').strip()}")
|
||||
if episodes:
|
||||
groups.append((source_names[index] if index < len(source_names) else f"线路{index + 1}", "#".join(episodes)))
|
||||
|
||||
if not groups:
|
||||
episodes = []
|
||||
for href, title in re.findall(
|
||||
r'<a[^>]*href="([^"]*/index\.php/vod/play/[^"]*)"[^>]*>([\s\S]*?)</a>',
|
||||
self._stringify(html),
|
||||
re.I,
|
||||
):
|
||||
episodes.append(f"{self._clean_text(title)}${href.replace('&', '&').strip()}")
|
||||
if episodes:
|
||||
groups.append(("线路1", "#".join(episodes)))
|
||||
return groups
|
||||
|
||||
def _parse_detail_page(self, vod_id, html):
|
||||
body = self._stringify(html)
|
||||
name_match = re.search(r"<title>([^<]+)</title>", body, re.I)
|
||||
name = self._strip_title(name_match.group(1) if name_match else "")
|
||||
if not name:
|
||||
slide_match = re.search(r'slide-info-title[^>]*>([^<]+)<', body, re.I)
|
||||
name = self._clean_text(slide_match.group(1) if slide_match else "")
|
||||
|
||||
pic_match = re.search(r'detail-pic[\s\S]*?data-src="([^"]*)"', body, re.I) or re.search(
|
||||
r'detail-pic[\s\S]*?src="([^"]*)"',
|
||||
body,
|
||||
re.I,
|
||||
)
|
||||
year_match = re.search(r"年份</em>[\s\S]*?<span>(\d{4})</span>", body, re.I) or re.search(r"年份[\s\S]*?(\d{4})", body, re.I)
|
||||
area_match = re.search(r"地区</em>[\s\S]*?<span>([^<]+)</span>", body, re.I)
|
||||
director_match = re.search(r"导演</em>\s*([^<\n]+)", body, re.I)
|
||||
actor_match = re.search(r"主演</em>\s*([^<\n]+)", body, re.I)
|
||||
content_match = re.search(r'id="height_limit"[^>]*>([\s\S]*?)</div>', body, re.I)
|
||||
groups = self._parse_play_groups(body)
|
||||
|
||||
director = self._clean_text(director_match.group(1) if director_match else "")
|
||||
actor = self._clean_text(actor_match.group(1) if actor_match else "")
|
||||
return {
|
||||
"vod_id": self._stringify(vod_id),
|
||||
"vod_name": name,
|
||||
"vod_pic": self._build_url(pic_match.group(1) if pic_match else ""),
|
||||
"vod_year": year_match.group(1) if year_match else "",
|
||||
"vod_area": self._clean_text(area_match.group(1) if area_match else ""),
|
||||
"vod_director": "" if director == "未知" else director,
|
||||
"vod_actor": "" if actor == "未知" else actor,
|
||||
"vod_content": self._clean_text(content_match.group(1) if content_match else "") or "暂无简介",
|
||||
"vod_play_from": "$$$".join(item[0] for item in groups),
|
||||
"vod_play_url": "$$$".join(item[1] for item in groups),
|
||||
}
|
||||
|
||||
def detailContent(self, ids):
|
||||
vod_id = self._stringify(ids[0] if isinstance(ids, list) and ids else ids).strip()
|
||||
if not vod_id:
|
||||
return {"list": []}
|
||||
vod = self._parse_detail_page(vod_id, self._request_html(self._build_detail_url(vod_id)))
|
||||
if not vod.get("vod_play_from"):
|
||||
return {"list": []}
|
||||
return {"list": [vod]}
|
||||
|
||||
def _extract_player_data(self, html):
|
||||
matched = re.search(r"var\s+player_aaaa\s*=\s*(\{[\s\S]*?\})", self._stringify(html), re.I)
|
||||
if not matched:
|
||||
return {}
|
||||
try:
|
||||
return json.loads(matched.group(1))
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def _pick_direct_media_url(self, html):
|
||||
matched = re.search(r'(https?://[^"\'\s]+\.m3u8[^"\'\s]*)', self._stringify(html), re.I)
|
||||
return matched.group(1) if matched else ""
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
play_url = self._build_url(id)
|
||||
html = self._request_html(play_url, headers={"User-Agent": self.headers["User-Agent"]}, referer=self.host + "/")
|
||||
player_data = self._extract_player_data(html)
|
||||
if player_data.get("url"):
|
||||
return {
|
||||
"parse": 0,
|
||||
"jx": 0,
|
||||
"playUrl": "",
|
||||
"url": self._stringify(player_data.get("url")),
|
||||
"header": {"User-Agent": self.headers["User-Agent"], "Referer": play_url},
|
||||
}
|
||||
|
||||
media_url = self._pick_direct_media_url(html)
|
||||
if media_url:
|
||||
return {
|
||||
"parse": 0,
|
||||
"jx": 0,
|
||||
"playUrl": "",
|
||||
"url": media_url,
|
||||
"header": {"User-Agent": self.headers["User-Agent"], "Referer": play_url},
|
||||
}
|
||||
|
||||
return {
|
||||
"parse": 0,
|
||||
"jx": 1,
|
||||
"playUrl": "",
|
||||
"url": play_url,
|
||||
"header": dict(self.headers),
|
||||
}
|
||||
Reference in New Issue
Block a user