早年在 QQ 音乐收藏了600+首歌,后来因为各种原因转用Spotify。很想把歌单导出,但网路上现有的脚本基本都失效了,歌单转换平台 Soundiiz 也并不支持 QQ 音乐这个平台。
有人建议通过下载列表内的所有歌获取(可进一步处理的)歌曲列表,不过笔者现在人在海外,并不能下载,且很多当年 QQ 音乐有版权的歌现在也没版权了。 笔者首先尝试用Fiddler Everywhere抓取QQ音乐Linux 和 Windows 客户端的数据包,(可能)限于个人能力,并不成功(但在 Windows 客户端可以抓取到收听历史的json)。 现在是2022年9月,QQ音乐网页端的每个播放列表只能显示前10首歌。但可以注意到,QQ音乐网页端允许用户取消收藏任意歌曲,而且每次取消收藏后页面会显示新的前10首歌。因此思路是:每次获取当前页面的10首歌后,进行10次取消收藏操作,然后再获取当前页面的10首歌,如此重复。 但如此的话,程序运行结束,收藏的歌单就被清空了,怎么办?事实上QQ音乐提供了一键恢复功能(至少在Android端可用)。另外也可以提前把所有歌批量导出到一个新的歌单里进行备份。 代码并不困难,用 Tampermonkey 在 Firefox 运行脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
|
function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); }
const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } });
observer.observe(document.body, { childList: true, subtree: true }); }); }
function delay(time) { return new Promise(resolve => setTimeout(resolve, time)); }
(async function() { 'use strict'; await waitForElm('#like_song_box > div.mod_songlist > ul.songlist__list'); const all_names = []; const all_artists = []; let loop = true; while (loop) { [...document.getElementsByClassName('songlist__song_txt')].forEach(item => item.remove()); const names = [...document.getElementsByClassName('songlist__songname')] .map(item => item.innerText.replace('\n播放', '').replace('\n添加到歌单\nVIP下载', '')); const artists = [...document.getElementsByClassName('songlist__artist')] .map(item => item.innerText.split(' /')); all_names.push(...names); all_artists.push(...artists); console.log([all_names, all_artists]);
for (let i = 0; i < 10; i++) { const buttons = document.getElementsByClassName('songlist__delete'); if (buttons.length === 0) { console.log('Ending loop'); loop = false; break; } buttons[0].click(); await delay(1000); document.querySelector( 'body > div:nth-child(7) > div > div.yqq-dialog-wrap > div > div.yqq-dialog-content > div > div > div.popup__ft > button.upload_btns__item.mod_btn' ).click() await delay(2000); } } })();
|
安装此脚本后,登陆QQ音乐网页端并前往 https://y.qq.com/n/ryqq/profile/like/song
就可以了。输出在浏览器开发者工具的Console里面,右击输出然后copy object
,复制到任何文本文件即可(这里命名为songs.json
)。因为脚本写的很简单,所以有可能出现出错的情况。那样的话可能要多复制几次,最后手动合并一下(会导致一些歌重复)。 接下来是导入到Spotify了。这一步应该可以用Soundiiz实现,不过我还是利用Spotify API写了一个脚本,因为歌比较多(Soundiiz似乎有导入歌曲数量上限限制)。
- 手动在Spotify创建一个新歌单。
- 去这里获取playlist id。先点击绿色的
get token
,然后给所需的scope打勾,即可获取token。接着点击try it
,右侧就有请求结果,找到对应的播放列表的id
这个key就可以了。 - 去这里获取一个可以添加歌曲到播放列表的token,方法同上。
- 把token和playlist id替换到下面的代码里面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import json import requests from tqdm import tqdm
token = 'YOUR TOKEN' playlist_id = 'YOUR PLAYLIST ID'
with open('songs.json') as f: tracks, artists = json.load(f) artists = [a[0] for a in artists]
headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': f'Bearer {token}', }
def get_search_items(q): params = ( ('q', q), ('type', 'track'), ('market', 'US'), ) response = requests.get('https://api.spotify.com/v1/search', headers=headers, params=params) try: return response.json()['tracks']['items'] except KeyError: return []
for track, artist in tqdm(list(zip(tracks, artists))): items = get_search_items(f'{track} artist:{artist}') if len(items) == 0: items = get_search_items(f'{track} {artist}') if len(items) == 0: print('Unable to find: ', track, artist) continue uri = items[0]['uri'] params = ( ('uris', uri), ) response = requests.post(f'https://api.spotify.com/v1/playlists/{playlist_id}/tracks', headers=headers, params=params)
|
有一些歌可能不能找到,在命令行会报错,尝试手动添加一下。也会有一些歌添加的版本可能不太对,手动删掉或替换掉(一般是因为找不到同样版本才会这样)。