最近突然想把以前在全民K歌录的所有歌曲都存下来,因此就想找找网上有没有合适的爬虫代码。翻了一圈,Github 上的几个项目都是五六年前的了,有些年代感了;CSDN 和知乎上的两位大佬分享的代码,如今也都没办法正确执行了。
但好在 CSDN 和知乎的两位大佬写了比较详细的思路和代码编写过程,结合咱也略懂一点点爬虫知识,所以花了几个小时,在前人的基础上,咱改出了一个可用的爬虫方案。
受时间所限,代码写的比较草率,GET请求具体携带的参数咱也没完全弄清楚,所以就不提交到 GitHub 了,哈哈。
需要的朋友直接拿去使用就行。感谢两位原作者:user_from_future、陶陶name
import os,re
import json
import requests
from bs4 import BeautifulSoup
# 解析 Cookies
def parse_cookies(cookies: str):
cookies_dict = {}
for c in cookies.replace(' ', '').split(';'):
try:
cookies_dict[c.split('=')[0]] = c.split('=')[1]
except IndexError:
cookies_dict[c.split('=')[0]] = ''
if "" in cookies_dict:
del cookies_dict[""]
return cookies_dict
# 处理标题
def cleanUGCListTitle(ugclist):
def clean_filename(filename):
# 定义非法字符
illegal_chars = r'[\\/:*?"<>|\r\n]+'
# 匹配 "
illegal_chars += r'|"'
illegal_chars += r'|''
# 替换非法字符
cleaned_filename = re.sub(illegal_chars, '', filename)
return cleaned_filename
for item in ugclist:
item['title'] = clean_filename(item['title'])
return ugclist
# 获取用户基本信息
cookie = input('请输入有效的 Cookie(XHR中选一):')
header = {"Cookie": cookie,
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1",
"Referer": "https://static-play.kg.qq.com/"}
uid = parse_cookies(cookie)['muid']
print(f'\n获取到 Cookie 中的用户 UID:{uid}')
inp = input('请输入需要查询的 UID,否则获取用户自身:')
if len(inp) > 10:
uid = inp
totalCount = 0 # 可以获取到的歌曲总数
songList = [] # 全部歌曲列表
user_information = {} # 用户基本信息
res = requests.get(f'https://kg.qq.com/node/personal?uid={uid}', cookies={"cookie": cookie})
# 遍历获取所有单曲信息
if res.ok:
for script in BeautifulSoup(res.text, 'lxml').find_all('script'):
if "window.__DATA__" in script.text:
user_information = json.loads(script.text[script.text.find('{'): script.text.rfind('};') + 1])["data"]
# 没有cookies == 公开的歌曲 | 有cookies == 账户所有的歌曲 || 能够被获取到的歌曲数目
totalCount = user_information["ugc_total_count"]
print(f'当前用户总歌曲数:{totalCount}')
if not os.path.exists(f'{user_information["kgnick"]}/media'):
os.makedirs(f'{user_information["kgnick"]}/media')
num = 15 # 单次获取最大15首
n = 1 # 页数
while n:
url = f'https://node.kg.qq.com/fcgi-bin/kg_ugc_get_homepage?'
params = {
"outCharset": "utf-8",
"from": "1",
"nocache": "1724248787425", # 不知道是个啥参数,从kg_ugc_get_homepage里面解析出来的
"format": "json",
"type": "get_uinfo",
"start": n,
"num": num,
"share_uid": uid,
"g_tk": "5381",
"g_tk_openkey": "1970486037" # 同样不知道是个啥参数,可能和身份鉴别有关,也是从kg_ugc_get_homepage里面解析出来的
}
res = requests.get(url, params=params, headers = header)
if res.ok:
try:
start_brace_index = res.text.find('{')
end_brace_index = res.text.rfind('}')
if start_brace_index != -1 and end_brace_index != -1:
song_information = json.loads(res.text[start_brace_index:end_brace_index + 1])["data"]
print(song_information)
if not song_information["ugclist"]:
break
# 处理一下 ugclist 中的非法标题,并加入所有歌曲列表中
songList += cleanUGCListTitle(song_information["ugclist"])
n += 1
else:
print("响应文本中找不到有效的 JSON 格式")
except json.JSONDecodeError as e:
print(f"JSON 解析错误: {e}")
# break
break
else:
print('未发现歌曲!')
# 遍历保存单曲文件
if user_information:
open(f'{user_information["kgnick"]}/{user_information["kgnick"]}_{uid}.json', 'w', encoding='utf-8').write(json.dumps(songList, indent=4, ensure_ascii=False))
for i, song in enumerate(songList):
res = requests.get(f'https://node.kg.qq.com/play?s={song["shareid"]}', cookies={"cookie": cookie})
if res.ok:
for script in BeautifulSoup(res.text, 'lxml').find_all('script'):
if "window.__DATA__" in script.text:
media_information = json.loads(script.text[script.text.find('{'): script.text.rfind('};') + 1])["detail"]
if media_information["playurl_video"] != '':
play_url = media_information["playurl_video"]
file_extension = ".mp4"
elif media_information["playurl"] != '':
play_url = media_information["playurl"]
file_extension = ".m4a"
else:
print('当前歌曲下载失败,可能是全民K歌的歌曲文件获取规则已变化,请修改爬虫代码!')
break
res = requests.get(play_url, stream=True)
if res.ok:
print(f'\r正在下载:{user_information["kgnick"]}/media/{song["title"]}_{song["shareid"]}{file_extension}\n【当前:{str(i + 1).zfill(len(str(totalCount)))}/总共:{totalCount}】', end='')
open(f'{user_information["kgnick"]}/media/{song["title"]}_{song["shareid"]}{file_extension}', 'wb').write(res.content)
break
else:
print('未发现媒体链接!')
print()
input('下载完成!回车结束程序~')
大部分代码其实就是 user_from_future 大佬的方案,只不过我精简掉了 自动获取 Cookie、下载专辑 的功能,毕竟咱只是想保存自己的全部歌曲嘛,这些功能就用不上了。
相比于 user_from_future 的源代码,这里我首先完善了全部歌曲的获取方式。说白了还是他所提到的“查看更多”那个 kg_ugc_get_homepage 的 GET 请求,只不过如今全民K歌那边,加了禁止空 referer 的限制
这个问题困扰了我一晚上,哈哈哈,还好咱建网站十年了,略懂这些,最后还是把这个小问题排查出来了。实测对这个请求,随便携带一个 referer 都能获取成功,这里保险起见,咱还是携带了它自己的域名。
同时,PC 端全民个人主页只能显示出 8 首歌曲,但如果用开发者选项改成移动端 UA,就能无限拉出全部歌曲了。每下拉一次,都会发出一次 GET 请求(kg_ugc_get_homepage)
这个请求的返回结果里,就有每次请求返回的 10 首歌。只不过我没有弄清楚请求携带的 nocache 以及 g_tk_openkey 的意义,所以这里就直接按照我自己的结果写了。如果不能用,你可以将代码中的这部分内容,修改为你的请求中的对应参数。
同时注意,这个请求是可以返回设为私密歌曲的结果的,前提是你得使用自己的 Cookie 去获取自己的歌曲。否则只能返回其他人公开的歌曲。
同时,我注意到有些作品是视频形式的,它的视频文件 URL 与音频文件的 URL 所在的键值对不太一样,因此在代码中我区分了二者,使其能够保存所有的全民K歌作品。鉴于 Windows 的命名规范,代码中也加入了标题规范化的功能,但仅仅只是将非法字符去掉,会导致部分歌曲的命名很奇怪。如有更好的做法,可以分享出来,谢谢。
Cookie 的获取方法:用电脑登录 kg.qq.com,然后点击右上角的扫码登录,使用 App 完成扫码登录后,点击顶部的“个人中心”,然后 F12 进入浏览器开发者选项,切换“网络”Tab,接着点一下网页底部的“更多歌曲请前往 App 查看”,这时开发者选项中就多出了一个 kg_ugc_get_homepage 的请求结果,将它的 Cookie 完整复制出来就行了(务必要完整复制出来)
使用上述 Python 爬虫脚本前,请确保你的电脑上安装了 requests、beautifulsoup4、lxml 这三个库,如果没有安装,请先使用pip install {库名}
来安装