diff --git a/py/tests/test_世纪音乐.py b/py/tests/test_世纪音乐.py index b6d666c..4963f9c 100644 --- a/py/tests/test_世纪音乐.py +++ b/py/tests/test_世纪音乐.py @@ -74,6 +74,54 @@ SEARCH_HTML = """ """ +RANK_HTML = """ + + + +""" + +SONG_HTML = """ +夜曲_世纪音乐 +

夜曲

+
周杰伦
+
+ +""" + +MV_DETAIL_HTML = """ +晴天MV_世纪音乐 +

晴天MV

+
周杰伦
+
+ +""" + +PLAYLIST_DETAIL_HTML = """ + +

周董歌单

+
+ + +""" + +SINGER_DETAIL_HTML = """ + +

周杰伦

+

华语男歌手

+
+ + +""" + class TestSJMusicSpider(unittest.TestCase): def setUp(self): @@ -145,6 +193,26 @@ class TestSJMusicSpider(unittest.TestCase): {"page": 1, "limit": 0, "total": 0, "list": []}, ) + @patch.object(Spider, "fetch") + def test_detail_content_builds_rank_song_mv_playlist_and_singer(self, mock_fetch): + mock_fetch.side_effect = [ + SimpleNamespace(status_code=200, text=RANK_HTML), + SimpleNamespace(status_code=200, text=SONG_HTML), + SimpleNamespace(status_code=200, text=MV_DETAIL_HTML), + SimpleNamespace(status_code=200, text=PLAYLIST_DETAIL_HTML), + SimpleNamespace(status_code=200, text=SINGER_DETAIL_HTML), + ] + rank_vod = self.spider.detailContent(["rank:rise"])["list"][0] + song_vod = self.spider.detailContent(["song:123"])["list"][0] + mv_vod = self.spider.detailContent(["mv:456"])["list"][0] + playlist_vod = self.spider.detailContent(["playlist:top100"])["list"][0] + singer_vod = self.spider.detailContent(["singer:jay"])["list"][0] + self.assertEqual(rank_vod["vod_play_url"], "搁浅$music:321#简单爱$music:322") + self.assertEqual(song_vod["vod_play_url"], "周杰伦 - 夜曲$music:123") + self.assertEqual(mv_vod["vod_play_url"], "晴天MV$vplay:456:1080") + self.assertEqual(playlist_vod["vod_play_url"], "安静$music:401#晴天$music:402") + self.assertEqual(singer_vod["vod_play_url"], "青花瓷$music:501#稻香$music:502") + if __name__ == "__main__": unittest.main() diff --git a/py/世纪音乐.py b/py/世纪音乐.py index 61d1577..b603c78 100644 --- a/py/世纪音乐.py +++ b/py/世纪音乐.py @@ -101,6 +101,30 @@ class Spider(BaseSpider): page = int(pg) return {"page": page, "limit": len(items), "total": len(items), "list": items} + def _decode_vod_id(self, vod_id): + raw = str(vod_id or "").strip() + if ":" not in raw: + return "", "" + prefix, value = raw.split(":", 1) + if prefix == "rank": + return prefix, f"/list/{value}.html" + if prefix == "song": + return prefix, f"/mp3/{value}.html" + if prefix == "mv": + return prefix, f"/mp4/{value}.html" + if prefix == "playlist": + return prefix, f"/playlist/{value}.html" + if prefix == "singer": + return prefix, f"/singer/{value}.html" + return "", "" + + def _encode_play_id(self, kind, value): + if kind == "music": + return f"music:{value}" + if kind == "vplay": + return f"vplay:{value}:1080" + return "" + def _parse_list_cards(self, html, expected_prefixes): root = self._load_html(html) items = [] @@ -126,6 +150,21 @@ class Spider(BaseSpider): ) return items + def _build_episode_rows(self, html): + root = self._load_html(html) + rows = [] + for node in root.xpath("//*[contains(@class,'play_list')]//li"): + href = "".join(node.xpath(".//a[1]/@href")).strip() + song_id = self._extract_site_id(href, "mp3") + if not song_id: + continue + rows.append( + self._clean_text("".join(node.xpath(".//a[1]//text()"))) + + "$" + + self._encode_play_id("music", song_id) + ) + return rows + def homeContent(self, filter): items = self._parse_home_items(self._fetch_html("/")) return {"class": list(self.classes), "filters": self._build_filters(), "list": items} @@ -175,3 +214,74 @@ class Spider(BaseSpider): ["song:", "mv:", "playlist:", "singer:"], ) return self._page_result(items, pg) + + def detailContent(self, ids): + vod_id = str((ids or [""])[0] or "").strip() + kind, path = self._decode_vod_id(vod_id) + if not path: + return {"list": []} + html = self._fetch_html(path) + root = self._load_html(html) + title = self._clean_text("".join(root.xpath("//h1[1]//text()"))) + pic = self._build_url("".join(root.xpath("(//img[1]/@src)[1]"))) + if kind == "rank": + return { + "list": [ + { + "vod_id": vod_id, + "vod_name": title or "排行榜", + "vod_pic": pic, + "vod_remarks": "", + "vod_content": "", + "vod_play_from": self.name, + "vod_play_url": "#".join(self._build_episode_rows(html)), + } + ] + } + if kind == "song": + singer = self._clean_text("".join(root.xpath("//*[contains(@class,'play_singer')]//a[1]//text()"))) + display = f"{singer} - {title}" if singer else title + return { + "list": [ + { + "vod_id": vod_id, + "vod_name": title, + "vod_pic": pic, + "vod_remarks": "", + "vod_content": "", + "vod_actor": singer, + "vod_play_from": self.name, + "vod_play_url": display + "$" + self._encode_play_id("music", vod_id.split(":", 1)[1]), + } + ] + } + if kind == "mv": + return { + "list": [ + { + "vod_id": vod_id, + "vod_name": title, + "vod_pic": pic, + "vod_remarks": "", + "vod_content": "", + "vod_play_from": self.name, + "vod_play_url": title + "$" + self._encode_play_id("vplay", vod_id.split(":", 1)[1]), + } + ] + } + if kind in ("playlist", "singer"): + content = self._clean_text("".join(root.xpath("//*[contains(@class,'info')]//p[1]//text()"))) + return { + "list": [ + { + "vod_id": vod_id, + "vod_name": title, + "vod_pic": pic, + "vod_remarks": "", + "vod_content": content, + "vod_play_from": self.name, + "vod_play_url": "#".join(self._build_episode_rows(html)), + } + ] + } + return {"list": []}