爬取全民K歌指定用户的全部歌曲

/ 0评 / 0

最近突然想把以前在全民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)
全民K歌 XHR请求
这个请求的返回结果里,就有每次请求返回的 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 {库名}来安装

声明:本文使用 知识共享署名 4.0 国际许可协议 [CC BY-NC-SA 4.0] 进行授权

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注