最近想从网易云音乐转移到其他音乐平台,发现QQ音乐和虾米音乐都有“导入外部歌单”的功能。
但是实际使用后发现,几百首歌曲的网易云歌单,导入到其他平台,只能成功导入10首歌。
不甘心的我,于是隔天又试了一次,发现虾米音乐还是存在这个问题,
而QQ音乐则直接提示了“暂时不支持导入此平台歌单”。
看来是网易云进行了相关策略,来防止其他平台批量导入,平台竞争,用户倒霉,坑!
找了一圈发现没有什么解决方案的情况下,总不能一首一首的手动导入吧。所以我决定自己造个轮子。
这里只将网易云歌单导入到了QQ音乐收藏。
准备工作
- 一台电脑
- 能打开开发者工具的浏览器(Chrome等)
- 能运行PHP代码的环境
获取歌单
首先第一步要做的是,将网易云音乐的歌单中的歌曲导出。
所以我写了一个批量获取歌单歌曲的js脚本,运行起来方便,有浏览器就行。执行后输出歌曲长字符串。
首先使用浏览器打开想要导出的歌单页面,应该是这样的页面:
然后右键点击网页,选择检查(N)
,在Console
中执行以下javascript代码:
(注意:网易云音乐用的是iframe嵌套的网页,所以不要用F12
的方式打开控制台)
var h = document.createElement("script");
h.src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(h, s);
执行后,再次粘贴以下代码执行:
var r = '';
$('.m-table tbody').find('tr').each(function() {
r += $(this).find('td').eq(1).find('b').attr('title') + ' - ' + $(this).find('td').eq(3).find('span').attr('title') + ';';
});
console.log(r);
如果不出意外的话,会输出很长的字符串歌曲名,可以点击Copy
(Chrome浏览器)来快速复制,
然后将复制后的结果保存到单独的一个文本文件中。
获取QQ音乐的提交Cookie
接下来,需要获取QQ音乐的提交用Cookie
,来让代码处理提交工作。
首先打开QQ音乐网页版,登录账号,并且点进去任意一首歌的详情页,按下F12
或右键选择检查(N)
打开控制台,
点击Network
选项卡的XHR
。
然后回到QQ音乐歌曲页,点击收藏。
如果不出意外的话,Network
选项卡的XHR
页面中会多出一个HTTP请求,请求的名称是fcg_music_add2songdir
。
我们点击查看这个请求中的请求头(Headers)。
复制里面的Cookie
(“cookie: ”后面的内容全部复制),和提交表单的loginUin
的值(“loginUin: ”后面的内容全部复制),并且保存好。
执行脚本
接下来,就可以开始运行PHP脚本来批量收藏歌曲了。
首先在能运行PHP程序的目录下创建三个文件,分别是song
,run.php
和TencentMusicAPI.php
。
(这里,我使用了Github上开源的程序来调用搜索QQ音乐歌曲的API)
然后将之前保存好的歌曲名粘贴到song
中。
在TencentMusicAPI.php
中,粘贴以下代码:
<?php
/*!
* Tencent(QQ) Music Api
* https://i-meto.com
* Version 20161203
*
* Copyright 2016, METO
* Released under the MIT license
*/
class TencentMusicAPI{
// General
protected $_USERAGENT='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.30 Safari/537.36';
protected $_COOKIE='qqmusic_uin=12345678; qqmusic_key=12345678; qqmusic_fromtag=30; ts_last=y.qq.com/portal/player.html;';
protected $_REFERER='http://y.qq.com/portal/player.html';
// CURL
protected function curl($url,$data=null){
$curl=curl_init();
curl_setopt($curl,CURLOPT_URL,$url);
if($data){
if(is_array($data))$data=http_build_query($data);
curl_setopt($curl,CURLOPT_POSTFIELDS,$data);
curl_setopt($curl,CURLOPT_POST,1);
}
curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl,CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl,CURLOPT_REFERER,$this->_REFERER);
curl_setopt($curl,CURLOPT_COOKIE,$this->_COOKIE);
curl_setopt($curl,CURLOPT_USERAGENT,$this->_USERAGENT);
$result=curl_exec($curl);
curl_close($curl);
return $result;
}
// main function
public function search($s,$limit=30,$offset=0,$type=1){
$url='http://c.y.qq.com/soso/fcgi-bin/search_cp?';
$data=array(
'p'=>$offset+1,
'n'=>$limit,
'w'=>$s,
'aggr'=>1,
'lossless'=>1,
'cr'=>1,
);
return substr($this->curl($url.http_build_query($data)),9,-1);
}
public function artist($artist_mid){
$url='http://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg?';
$data=array(
'singermid'=>$artist_mid,
'order'=>'listen',
'begin'=>0,
'num'=>30,
);
return substr($this->curl($url.http_build_query($data)),0,-1);
}
public function album($album_mid){
$url='http://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?';
$data=array(
'albummid'=>$album_mid,
);
return substr($this->curl($url.http_build_query($data)),1);
}
public function detail($song_mid){
$url='http://c.y.qq.com/v8/fcg-bin/fcg_play_single_song.fcg?';
$data=array(
'songmid'=>$song_mid,
'format'=>'json',
);
return $this->curl($url.http_build_query($data));
}
private function genkey(){
$this->_GUID=rand(1,2147483647)*(microtime()*1000)%10000000000;
$data=$this->curl('https://c.y.qq.com/base/fcgi-bin/fcg_musicexpress.fcg?json=3&guid='.$this->_GUID);
$this->_KEY=json_decode(substr($data,13,-2),1)['key'];
//$this->_CDN=json_decode(substr($data,13,-2),1)['sip'][0];
$this->_CDN='http://dl.stream.qqmusic.qq.com/';
}
public function url($song_mid){
self::genkey();
$url='http://c.y.qq.com/v8/fcg-bin/fcg_play_single_song.fcg?';
$data=array(
'songmid'=>$song_mid,
'format'=>'json',
);
$data=$this->curl($url.http_build_query($data));
$data=json_decode($data,1)['data'][0]['file'];
$type=array(
'size_320mp3'=>array('M800','mp3'),
'size_128mp3'=>array('M500','mp3'),
'size_96aac'=>array('C400','m4a'),
'size_48aac'=>array('C200','m4a'),
);
$url=array();
foreach($type as $key=>$vo){
if($data[$key])$url[substr($key,5)]=$this->_CDN.$vo[0].$data['media_mid'].'.'.$vo[1].
'?vkey='.$this->_KEY.'&guid='.$this->_GUID.'&fromtag=30';
}
return json_encode($url);
}
public function playlist($playlist_id){
$url='http://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?';
$data=array(
'disstid'=>$playlist_id,
'utf8'=>1,
'type'=>1,
);
return substr($this->curl($url.http_build_query($data)),13,-1);
}
public function lyric($song_mid){
$url='http://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric.fcg?';
$data=array(
'songmid'=>$song_mid,
'nobase64'=>'1',
);
return substr($this->curl($url.http_build_query($data)),18,-1);
}
}
然后在run.php中,粘贴以下代码,并将之前获取的$cookie
和$loginUin
填写进去:
<?php
require_once 'TencentMusicAPI.php';
$cookie = ''; // 这里填写你的cookie
$loginUin = ''; // 这里填写你的loginUin
$api = new TencentMusicAPI();
$url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_music_add2songdir.fcg?g_tk=760311301&g_tk_new_20200303=760311301';
$header = [
':authority: c.y.qq.com',
':method: POST',
':path: /splcloud/fcgi-bin/fcg_music_add2songdir.fcg?g_tk=760311301&g_tk_new_20200303=760311301',
':scheme: https',
'accept: */*',
'accept-encoding: gzip, deflate, br',
'accept-language: zh-CN,zh;q=0.9,ja;q=0.8,en;q=0.7',
'content-type: application/x-www-form-urlencoded; charset=UTF-8',
'cookie: '.$cookie,
'origin: https://y.qq.com',
'referer: https://y.qq.com/n/yqq/song/102424538_num.html',
'sec-fetch-dest: empty',
'sec-fetch-mode: cors',
'sec-fetch-site: same-site',
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
];
$filename = "song";
$handle = fopen($filename, 'r');
$contents = fread($handle, filesize($filename));
fclose($handle);
$music_list = explode(';', $contents);
foreach ($music_list as $music_name) {
if(!$music_name) {
continue;
}
$result = $api->search($music_name);
$result = json_decode($result, true);
if(isset($result['data']['song']['list'])) {
if(count($result['data']['song']['list']) > 0) {
$mid = $result['data']['song']['list'][0]['songmid'];
$post_result = php_curl($url, $header, 1, [
'loginUin' => $loginUin,
'hostUin' => 0,
'format' => 'json',
'inCharset' => 'utf8',
'outCharset' => 'utf-8',
'notice' => 0,
'platform' => 'yqq.post',
'needNewCode' => 0,
'uin' => $loginUin,
'midlist' => $mid,
'typelist' => 13,
'dirid' => 201,
'addtype' => '',
'formsender' => 4,
'source' => 153,
'r2' => 0,
'r3' => 1,
'utf8' => 1,
'g_tk' => 760311301
]);
$post_result = json_decode($post_result, true);
echo 'ADD: '.$music_name.': '.$post_result['msg'].PHP_EOL;
}
else {
echo 'NOT FOUND: '.$music_name.PHP_EOL;
}
}
else {
echo 'NOT FOUND: '.$music_name.PHP_EOL;
}
sleep(1);
}
function php_curl($url, $header = [], $is_post = 0, $postdata = []) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//curl_setopt ($ch, CURLOPT_HEADER, 0);
// 如果是POST方法
if ($is_post) {
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($postdata));
}
// 如果定义了请求头
if($header) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
$output = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
return $output;
}
检查无误后,在终端执行php run.php
,如果提示以下内容,则说明成功了,慢慢等待收藏完成吧!
这可比手动导入要快多啦!
注意事项
- 因为是采用搜索+收藏的方式,所以可能会出现收藏的歌曲和网易云的版本不一致的情况。实际测试发现,600首歌曲中有十几首歌曲是一致的,不过歌手不同。
- 网易云的网页进行了很多混淆处理,来防止歌单被爬取。如果发现javascript运行无法获得歌单,可以尝试自己研究研究代码,然后手动修复。
- PHP运行需要启用curl模块,如果代码运行出错了首先检查一下是否是curl引起的。
- 扒一扒虾米音乐的搜索和收藏接口,理论上也能导入。这里就不写了。