feat: add lumman detail parsing
This commit is contained in:
@@ -26,6 +26,25 @@ SAMPLE_LIST_HTML = """
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
SAMPLE_DETAIL_HTML = """
|
||||
<html><body>
|
||||
<h1 class="page-title">进击的巨人</h1>
|
||||
<div class="module-item-pic"><img class="lazyload" src="/upload/jjdr.jpg" /></div>
|
||||
<div class="video-info-items">状态:已完结</div>
|
||||
<div class="video-info-items">地区:日本</div>
|
||||
<div class="video-info-content">人类与巨人的战斗。</div>
|
||||
<a class="module-tab-item tab-item" href="#line1">在线播放</a>
|
||||
<a class="module-tab-item tab-item" href="#line2">云播</a>
|
||||
<div id="line1" class="module-player-list">
|
||||
<a href="/vod/play/1001-1-1.html">第1集</a>
|
||||
<a href="/vod/play/1001-1-2.html">第2集</a>
|
||||
</div>
|
||||
<div id="line2" class="module-player-list">
|
||||
<a href="/vod/play/1001-2-1.html">HD</a>
|
||||
</div>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
|
||||
class TestLuManManSpider(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -72,6 +91,34 @@ class TestLuManManSpider(unittest.TestCase):
|
||||
self.assertEqual(result["list"][0]["vod_name"], "海贼王")
|
||||
mock_html.assert_called_with("https://www.lmm85.com/vod/search/page/3/wd/%E6%B5%B7%E8%B4%BC.html")
|
||||
|
||||
@patch.object(Spider, "_get_html")
|
||||
def test_detail_content_parses_meta_and_playlists(self, mock_html):
|
||||
mock_html.return_value = SAMPLE_DETAIL_HTML
|
||||
result = self.spider.detailContent(["vod/detail/1001.html"])
|
||||
vod = result["list"][0]
|
||||
self.assertEqual(vod["vod_name"], "进击的巨人")
|
||||
self.assertEqual(vod["vod_pic"], "https://www.lmm85.com/upload/jjdr.jpg")
|
||||
self.assertEqual(vod["vod_content"], "人类与巨人的战斗。")
|
||||
self.assertEqual(vod["vod_remarks"], "状态:已完结 / 地区:日本")
|
||||
self.assertEqual(vod["vod_play_from"], "在线播放$$$云播")
|
||||
self.assertIn("第1集$vod/play/1001-1-1.html#第2集$vod/play/1001-1-2.html", vod["vod_play_url"])
|
||||
self.assertIn("HD$vod/play/1001-2-1.html", vod["vod_play_url"])
|
||||
|
||||
@patch.object(Spider, "_get_html")
|
||||
def test_detail_content_falls_back_to_direct_playlist_scan(self, mock_html):
|
||||
mock_html.return_value = """
|
||||
<html><body>
|
||||
<h1 class="page-title">测试</h1>
|
||||
<div class="module-player-list">
|
||||
<a href="/vod/play/1-1-1.html">正片</a>
|
||||
</div>
|
||||
</body></html>
|
||||
"""
|
||||
result = self.spider.detailContent(["vod/detail/1.html"])
|
||||
vod = result["list"][0]
|
||||
self.assertEqual(vod["vod_play_from"], "播放列表")
|
||||
self.assertEqual(vod["vod_play_url"], "正片$vod/play/1-1-1.html")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -52,6 +52,15 @@ class Spider(BaseSpider):
|
||||
matched = re.search(r"(/vod/detail/[^?#]+\.html)", raw)
|
||||
return matched.group(1).lstrip("/") if matched else raw.lstrip("/")
|
||||
|
||||
def _decode_vod_id(self, vod_id):
|
||||
raw = str(vod_id or "").strip().lstrip("/")
|
||||
return self._abs_url(raw)
|
||||
|
||||
def _encode_play_id(self, href):
|
||||
raw = str(href or "").strip()
|
||||
matched = re.search(r"(/vod/play/[^?#]+\.html)", raw)
|
||||
return matched.group(1).lstrip("/") if matched else raw.lstrip("/")
|
||||
|
||||
def _clean_text(self, text):
|
||||
return re.sub(r"\s+", " ", str(text or "")).strip()
|
||||
|
||||
@@ -112,3 +121,66 @@ class Spider(BaseSpider):
|
||||
url = f"{self.host}/vod/search/page/{page}/wd/{quote(keyword)}.html"
|
||||
items = self._parse_cards(self._get_html(url))
|
||||
return {"page": page, "total": len(items), "list": items[:10] if quick else items}
|
||||
|
||||
def _parse_play_groups(self, root):
|
||||
groups = []
|
||||
tabs = root.xpath("//*[contains(@class,'module-tab-item') and contains(@class,'tab-item')]")
|
||||
for index, tab in enumerate(tabs):
|
||||
name = self._clean_text("".join(tab.xpath(".//text()")))
|
||||
target = self._clean_text((tab.xpath("./@href") or [""])[0])
|
||||
if not name:
|
||||
continue
|
||||
playlist = []
|
||||
if target.startswith("#"):
|
||||
playlist = root.xpath(f"//*[@id='{target[1:]}']")
|
||||
if not playlist:
|
||||
playlist = root.xpath(f"(//*[contains(@class,'module-player-list')])[{index + 1}]")
|
||||
if not playlist:
|
||||
continue
|
||||
episodes = []
|
||||
for anchor in playlist[0].xpath(".//a[@href]"):
|
||||
ep_name = self._clean_text("".join(anchor.xpath(".//text()")))
|
||||
ep_id = self._encode_play_id((anchor.xpath("./@href") or [""])[0])
|
||||
if ep_name and ep_id:
|
||||
episodes.append(f"{ep_name}${ep_id}")
|
||||
if episodes:
|
||||
groups.append((name, "#".join(episodes)))
|
||||
if groups:
|
||||
return groups
|
||||
|
||||
fallback = []
|
||||
for anchor in root.xpath("//*[contains(@class,'module-player-list')]//a[@href]"):
|
||||
ep_name = self._clean_text("".join(anchor.xpath(".//text()")))
|
||||
ep_id = self._encode_play_id((anchor.xpath("./@href") or [""])[0])
|
||||
if ep_name and ep_id:
|
||||
fallback.append(f"{ep_name}${ep_id}")
|
||||
if fallback:
|
||||
return [("播放列表", "#".join(fallback))]
|
||||
return []
|
||||
|
||||
def detailContent(self, ids):
|
||||
raw_id = ids[0] if isinstance(ids, list) else ids
|
||||
html = self._get_html(self._decode_vod_id(raw_id))
|
||||
root = self.html(html)
|
||||
if root is None:
|
||||
return {"list": []}
|
||||
groups = self._parse_play_groups(root)
|
||||
remarks = [
|
||||
self._clean_text("".join(node.xpath(".//text()")))
|
||||
for node in root.xpath("//*[contains(@class,'video-info-items')]")
|
||||
]
|
||||
pic = (
|
||||
root.xpath("//*[contains(@class,'module-item-pic')]//img[1]/@data-src")
|
||||
or root.xpath("//*[contains(@class,'module-item-pic')]//img[1]/@src")
|
||||
or [""]
|
||||
)[0]
|
||||
vod = {
|
||||
"vod_id": str(raw_id),
|
||||
"vod_name": self._clean_text("".join(root.xpath("//*[contains(@class,'page-title')][1]//text()"))),
|
||||
"vod_pic": self._abs_url(pic),
|
||||
"vod_content": self._clean_text("".join(root.xpath("//*[contains(@class,'video-info-content')][1]//text()"))),
|
||||
"vod_remarks": " / ".join([value for value in remarks if value]),
|
||||
"vod_play_from": "$$$".join(name for name, _ in groups),
|
||||
"vod_play_url": "$$$".join(urls for _, urls in groups),
|
||||
}
|
||||
return {"list": [vod]}
|
||||
|
||||
Reference in New Issue
Block a user