import base64import csvimport htmlimport jsonimport osimport reimport subprocessfrom pathlib import Pathimport requestsBASE_DIR = Path(__file__).resolve().parentCSV_FILE = BASE_DIR / "酷狗客户端下载链接.csv"RANK_URL = "https://www.kugou.com/yy/rank/home"# None 表示发送整榜单;也可以改成 [5] 这种只测指定序号SELECTED_INDEXES = None# True: 每首发送后按回车继续;False: 自动间隔发送MANUAL_CONFIRM = Truesession = requests.Session()HEADERS = {"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) ""AppleWebKit/537.36 (KHTML, like Gecko) ""Chrome/124.0.0.0 Safari/537.36"),"Referer": "https://www.kugou.com/",}def get_text(url, referer=None):request_headers = HEADERS.copy()if referer:request_headers["Referer"] = refererresponse = session.get(url, headers=request_headers, timeout=15)response.raise_for_status()return response.textdef parse_rank_features(page_html):match = re.search(r"global\.features\s*=\s*(\[.*?\]);", page_html, re.S)if not match:raise RuntimeError("没有找到 global.features 榜单数据")return json.loads(match.group(1))def parse_rank_detail_urls(page_html):pattern = re.compile(r'<a\s+href="(https://www\.kugou\.com/mixsong/[a-zA-Z0-9]+\.html)"'r'\s+data-active="playDwn"\s+data-index="\d+"\s+class="pc_temp_songname"',re.S,)return pattern.findall(page_html)def get_rank_items():page_html = get_text(RANK_URL)songs = parse_rank_features(page_html)detail_urls = parse_rank_detail_urls(page_html)if len(detail_urls) != len(songs):print("警告:榜单歌曲数量和详情页链接数量不一致")print("songs:", len(songs))print("detail_urls:", len(detail_urls))print()items = []count = min(len(songs), len(detail_urls))for i in range(count):items.append({"rank_index": i + 1,"rank_song": songs[i],"detail_url": detail_urls[i],})return itemsdef parse_data_from_smarty(page_html):match = re.search(r"dataFromSmarty\s*=\s*", page_html)if not match:return Nonestart = page_html.find("[", match.end())if start == -1:return Nonesongs, _ = json.JSONDecoder().raw_decode(page_html[start:])return songs[0] if songs else Nonedef get_detail_song(detail_url):page_html = get_text(detail_url, referer=RANK_URL)return parse_data_from_smarty(page_html)def get_rank_song_name(rank_song):return html.unescape(rank_song.get("FileName") or "未知歌曲")def normalize_song(rank_index, rank_song, detail_url):rank_name = get_rank_song_name(rank_song)print(f"[第 {rank_index} 首] 榜单歌曲:{rank_name}")print("榜单 Hash:", rank_song.get("Hash"))print("真实详情页地址:", detail_url)detail_song = get_detail_song(detail_url)detail_hash = ""detail_name = ""detail_size = 0detail_duration = 0detail_privilege = 0detail_album_id = 0if detail_song:print("详情页解析:成功")detail_hash = detail_song.get("hash") or detail_song.get("Hash") or ""detail_name = (detail_song.get("audio_name")or detail_song.get("song_name")or "")detail_size = detail_song.get("filesize") or detail_song.get("size") or 0detail_duration = (detail_song.get("timelength")or detail_song.get("timeLen")or 0)detail_privilege = detail_song.get("privilege") or 0detail_album_id = detail_song.get("album_id") or 0else:print("详情页解析:失败,改用榜单数据")normalized = {"index": rank_index,"song_name": html.unescape(detail_name or rank_name),"hash": detail_hash or rank_song.get("Hash") or "","size": detail_size or rank_song.get("size") or 0,"duration": detail_duration or rank_song.get("timeLen") or 0,"privilege": detail_privilege or rank_song.get("privilege") or 0,"album_id": detail_album_id or rank_song.get("album_id") or 0,"detail_url": detail_url,"source": "detail+rank_fallback" if detail_song else "rank",}print("最终使用歌曲名:", normalized["song_name"])print("最终使用 Hash:", normalized["hash"])print("最终使用 Album ID:", normalized["album_id"])print("数据来源:", normalized["source"])print()return normalizeddef normalize_duration_ms(duration):duration = int(duration or 0)return duration if duration >= 10000 else duration * 1000def build_kugou_download_url(song):file_name = song["song_name"]if len(file_name) > 58:file_name = file_name[:58] + "..."control_data = {"Files": [{"filename": f"{file_name}.mp3","hash": song.get("hash", ""),"size": str(song.get("size") or 0),"duration": str(normalize_duration_ms(song.get("duration"))),"bitrate": "128","isfilehead": "100","privilege": str(song.get("privilege") or 0),"album_id": str(song.get("album_id") or 0),}]}json_text = json.dumps(control_data, ensure_ascii=False, separators=(",", ":"))encoded = base64.b64encode(json_text.encode("utf-8")).decode("utf-8")return "kugou://download?p=" + encodeddef open_kugou_url(client_url):try:os.startfile(client_url)except OSError:subprocess.run(["cmd", "/c", "start", "", client_url],check=True,shell=False,)def write_csv(rows):with open(CSV_FILE, "w", newline="", encoding="utf-8-sig") as file:writer = csv.DictWriter(file,fieldnames=["index","song_name","hash","album_id","size","duration","source","detail_url","client_download_url",],)writer.writeheader()writer.writerows(rows)def select_items(rank_items):if SELECTED_INDEXES is None:return rank_itemsselected_index_set = set(SELECTED_INDEXES)return [item for item in rank_items if item["rank_index"] in selected_index_set]def main():rank_items = get_rank_items()selected_items = select_items(rank_items)print("榜单可用歌曲数量:", len(rank_items))print("本次发送数量:", len(selected_items))print("指定序号:", "整榜单" if SELECTED_INDEXES is None else SELECTED_INDEXES)print("记录文件:", CSV_FILE)print()rows = []sent_count = 0for send_index, item in enumerate(selected_items, start=1):rank_index = item["rank_index"]rank_song = item["rank_song"]detail_url = item["detail_url"]try:song = normalize_song(rank_index, rank_song, detail_url)if not song["hash"]:print(f"[第 {rank_index} 首] 跳过:缺少 Hash")continueclient_url = build_kugou_download_url(song)rows.append({"index": song["index"],"song_name": song["song_name"],"hash": song["hash"],"album_id": song["album_id"],"size": song["size"],"duration": song["duration"],"source": song["source"],"detail_url": song["detail_url"],"client_download_url": client_url,})sent_count += 1print(f"[第 {rank_index} 首] 发送到酷狗客户端:{song['song_name']}")print("客户端链接:", client_url)print()open_kugou_url(client_url)if MANUAL_CONFIRM:input("请查看酷狗是否加入下载列表,按回车继续...")except KeyboardInterrupt:print()print("你手动停止了程序")breakexcept Exception as error:print(f"[第 {rank_index} 首] 失败:{error}")write_csv(rows)print()print("任务结束")print("发送数量:", sent_count)print("CSV记录数量:", len(rows))print("记录文件:", CSV_FILE)print()print("注意:如果协议能唤起酷狗但歌曲仍不进入下载列表,就是客户端权限、版权或下载券限制。")if __name__ == "__main__":main()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。