早年在 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 运行脚本:
| 12
 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替换到下面的代码里面。
| 12
 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 jsonimport 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)
 
 | 
有一些歌可能不能找到,在命令行会报错,尝试手动添加一下。也会有一些歌添加的版本可能不太对,手动删掉或替换掉(一般是因为找不到同样版本才会这样)。