1 Star 0 Fork 0

code_fwp/python_spider

加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
kugou.py 8.63 KB
一键复制 编辑 原始数据 按行查看 历史
code_fwp 提交于 2026-05-26 00:24 +08:00 . 0526
import base64
import csv
import html
import json
import os
import re
import subprocess
from pathlib import Path
import requests
BASE_DIR = Path(__file__).resolve().parent
CSV_FILE = BASE_DIR / "酷狗客户端下载链接.csv"
RANK_URL = "https://www.kugou.com/yy/rank/home"
# None 表示发送整榜单;也可以改成 [5] 这种只测指定序号
SELECTED_INDEXES = None
# True: 每首发送后按回车继续;False: 自动间隔发送
MANUAL_CONFIRM = True
session = 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"] = referer
response = session.get(url, headers=request_headers, timeout=15)
response.raise_for_status()
return response.text
def 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 items
def parse_data_from_smarty(page_html):
match = re.search(r"dataFromSmarty\s*=\s*", page_html)
if not match:
return None
start = page_html.find("[", match.end())
if start == -1:
return None
songs, _ = json.JSONDecoder().raw_decode(page_html[start:])
return songs[0] if songs else None
def 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 = 0
detail_duration = 0
detail_privilege = 0
detail_album_id = 0
if 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 0
detail_duration = (
detail_song.get("timelength")
or detail_song.get("timeLen")
or 0
)
detail_privilege = detail_song.get("privilege") or 0
detail_album_id = detail_song.get("album_id") or 0
else:
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 normalized
def normalize_duration_ms(duration):
duration = int(duration or 0)
return duration if duration >= 10000 else duration * 1000
def 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=" + encoded
def 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_items
selected_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 = 0
for 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")
continue
client_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 += 1
print(f"[第 {rank_index} 首] 发送到酷狗客户端:{song['song_name']}")
print("客户端链接:", client_url)
print()
open_kugou_url(client_url)
if MANUAL_CONFIRM:
input("请查看酷狗是否加入下载列表,按回车继续...")
except KeyboardInterrupt:
print()
print("你手动停止了程序")
break
except 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()
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/codefwp/python_spider.git
git@gitee.com:codefwp/python_spider.git
codefwp
python_spider
python_spider
master

搜索帮助