idiotbaka

编写程序来批量将网易云音乐歌单导入到QQ音乐收藏
最近想从网易云音乐转移到其他音乐平台,发现QQ音乐和虾米音乐都有“导入外部歌单”的功能。但是实际使用后发现,几百首...
扫描右侧二维码阅读全文
26
2020/05

编写程序来批量将网易云音乐歌单导入到QQ音乐收藏

timg.jpg
最近想从网易云音乐转移到其他音乐平台,发现QQ音乐和虾米音乐都有“导入外部歌单”的功能。
但是实际使用后发现,几百首歌曲的网易云歌单,导入到其他平台,只能成功导入10首歌。

不甘心的我,于是隔天又试了一次,发现虾米音乐还是存在这个问题,
而QQ音乐则直接提示了“暂时不支持导入此平台歌单”。
QQ截图20200529132100.png
看来是网易云进行了相关策略,来防止其他平台批量导入,平台竞争,用户倒霉,坑!
找了一圈发现没有什么解决方案的情况下,总不能一首一首的手动导入吧。所以我决定自己造个轮子。
这里只将网易云歌单导入到了QQ音乐收藏。

准备工作

  1. 一台电脑
  2. 能打开开发者工具的浏览器(Chrome等)
  3. 能运行PHP代码的环境

获取歌单

首先第一步要做的是,将网易云音乐的歌单中的歌曲导出。
所以我写了一个批量获取歌单歌曲的js脚本,运行起来方便,有浏览器就行。执行后输出歌曲长字符串。
首先使用浏览器打开想要导出的歌单页面,应该是这样的页面:
1.png
然后右键点击网页,选择检查(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截图20200527000607.png

获取QQ音乐的提交Cookie

接下来,需要获取QQ音乐的提交用Cookie,来让代码处理提交工作。
首先打开QQ音乐网页版,登录账号,并且点进去任意一首歌的详情页,按下F12或右键选择检查(N)打开控制台,
点击Network选项卡的XHR
QQ截图20200529153854.png
然后回到QQ音乐歌曲页,点击收藏。
如果不出意外的话,Network选项卡的XHR页面中会多出一个HTTP请求,请求的名称是fcg_music_add2songdir
我们点击查看这个请求中的请求头(Headers)。
QQ截图20200527001410.png
复制里面的Cookie(“cookie: ”后面的内容全部复制),和提交表单的loginUin的值(“loginUin: ”后面的内容全部复制),并且保存好。

执行脚本

接下来,就可以开始运行PHP脚本来批量收藏歌曲了。
首先在能运行PHP程序的目录下创建三个文件,分别是songrun.phpTencentMusicAPI.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,如果提示以下内容,则说明成功了,慢慢等待收藏完成吧!
QQ截图20200527000725.png

这可比手动导入要快多啦!
QQ截图20200529155336.png

注意事项

  1. 因为是采用搜索+收藏的方式,所以可能会出现收藏的歌曲和网易云的版本不一致的情况。实际测试发现,600首歌曲中有十几首歌曲是一致的,不过歌手不同。
  2. 网易云的网页进行了很多混淆处理,来防止歌单被爬取。如果发现javascript运行无法获得歌单,可以尝试自己研究研究代码,然后手动修复。
  3. PHP运行需要启用curl模块,如果代码运行出错了首先检查一下是否是curl引起的。
  4. 扒一扒虾米音乐的搜索和收藏接口,理论上也能导入。这里就不写了。
Last modification:May 29th, 2020 at 04:13 pm
本文采用 知识共享署名 4.0 国际许可协议进行许可
可自由转载、引用,但需署名作者且注明文章出处
If you think my article is useful to you, please feel free to appreciate

Leave a Comment