轻松批量爬上万首音乐

轻松批量爬上万首音乐

点点

2021-05-08 20:10 阅读 243 喜欢 0

     从豆瓣FM抓音乐主要是看网站的音乐获取方式,有的可能是异步有的可能是同步,当然大部分还是异步的,有些可能数据要求比较严格,会有一些校验等等会很麻烦,不过还好,找到了一个比较简单的请求接口,可以获取下一首歌曲。

构思

    抓音乐也可以对音乐进行分析,所以准备把从豆瓣上获得的所有信息都存储起来,这里使用的是Mysql数据库,结构也比较简单,有四个表:音乐、歌手、频道、专辑。

      由于接口是下一首,而且是随机的,所以有很大的几率是重复的,需要在处理过程中进行去重,而且有时候一个200首歌的频道,随机半天也不一定会把所有的歌曲全部拿下来,不过考虑到会一直抓取,而且本身歌曲数量就比较多了,也没必要太过完美。

大体的思路就是:

获得豆瓣上的频道ID,可以循环,然后排除即可。 根据获得的频道ID,然后去随机获得音乐 获得音乐,去重,然后保存歌曲、歌手、专辑信息即可。 重复获取音乐,如果连续20+次获得都是重复的歌曲,或者当前频道的歌曲已经达到了90%,那么就换下一个频道。 如果频道不符合,则继续下一个频道,设定一个频道范围。比如:1-200.

module

superagent : https://www.npmjs.com/package/superagent simple-mysql-query : https://www.npmjs.com/package/simple-mysql-query

代码

/*持续抓取豆瓣的音乐并保存到数据库*/

 //https://douban.fm/j/v2/songlist/470992/?kbps=192
//根据歌单进行查找

      const superagent = require('superagent');
     const query = require('simple-mysql-query');
     const tool = require('../util');
          class DouBan {
constructor ( opts ){
    opts = opts || {}
    this.ids = [];
    this.requestOpt = {
        'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Encoding':'gzip, deflate, sdch',
        'Accept-Language':'zh-CN,zh;q=0.8',
        'Cache-Control':'no-cache',
        'Connection':'keep-alive',
        'Cookie':'bid=RveZRi0m5ds; _ga=GA1.2.1305540715.1533201218; flag="ok"; ac="1533776043"; _gid=GA1.2.228834671.1533776081; _gat=1',
        'Host':'fm.douban.com',
        'Pragma':'no-cache',
        'Upgrade-Insecure-Requests':'1',
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2729.4 Safari/537.3'
    };
    /*channel*/
    this.channelUrl = 'https://fm.douban.com/j/v2/channel_info?id=';
    this.channelStart = 1;//开始channel
    this.channelEnd = 300;//结束channel
    this.channelPer = 3000;//从3000中随机,单位ms
    this.currentChannel = null;//可用的频道,每次启动的时候都自动检查一遍channel,然后更新并缓存。

    //频道相关的歌曲查询

    //请求频率
    this.existNum = 0;//如果连续50次都是已存在的歌,就切换频道
    this.channelError = 0;//超过5次获取失败,则切换频道
    this.maxErrorNum = 50;//最大失败数量

    //启动数据抓取
    this.log('启动数据抓取工具:------------------------',1);
    this.findChannel();
    this.findByChannel();

}
log(msg,iserror){
    tool.log('豆瓣:',msg,iserror);
}
//获得不同程度的频率
getPer (flag){
    if(flag == 0){
        return 30 * 1000;//失败20s再继续
    }else if(flag == 1){
        return  5000 + Math.random() * 20000;
    }else if(flag == 2){
        return 10 * 1000;
    }
}
//获得当前的channel所在的频道查询music的url
getMusicChannelUrl () {
    var that = this;
    return 'https://douban.fm/j/v2/playlist?channel='+that.currentChannel+'&kbps=128&client=s%3Amainsite%7Cy%3A3.0&app_name=radio_website&version=100&type=s&sid=382400&pt=&pb=128&apikey=';
}

//根据频道查找歌曲
findByChannel () {
    this.log('开始根据频道查询歌曲')
    var that = this;
    that.existNum ++ ;
    if(that.currentChannel){
        //1.查询歌曲信息
        superagent.get(that.getMusicChannelUrl())
        .set(that.requestOpt)
        .end( (err,res) => {
            if(err){
                that.channelError ++ ;
                that.log('根据当前可用频道获取歌曲失败,20s后重新请求',0)
                //如果报错,应该是被禁止或者网络超时
                that.log(err,0);
                //20s后重新发起
                setTimeout(function(){
                    that.findByChannel();
                    if(that.channelError > 5){
                        that.channelError = 0;
                        that.findChannel();    
                    }
                },that.getPer(0))
            }else{
                that.channelError = 0;
                that.log('获得可用歌曲,检查歌曲信息',1)
                //获得歌曲信息
                var resobj = JSON.parse(res.text);
                if(resobj.song && resobj.song.length > 0){
                    var song = resobj.song[0];
                    //1.根据歌曲ID检查歌曲是否存在,同时查询当前频道内的歌曲数量和频道歌曲总数
                    var songId = song.sid;
                    query({sql : 'select count(1) as num from music_music where sid=? ',params : [songId]})
                    .then( rs => {
                        var rst = rs[0];
                        if(rst[0].num > 0){//
                            return 0;
                        }else{
                            //检查专辑
                            if(song.release && song.release.id){
                                return query({sql : 'select count(1) as num from music_album where id=?',params : [song.release.id]});
                            }else{
                                return 1;
                            }
                        }
                    }).then( rs => {
                        //处理专辑
                        if(rs=== 0){//忽略该歌曲
                            return 0;
                        }else if(rs == 1){
                            //该歌曲没有专辑
                            return 1;
                        }else{
                            var rst = rs[0];
                            if(rst[0].num > 0){
                                return 1;
                            }else{
                                //插入专辑记录
                                return query({sql : 'insert into music_album (id,link,ssid) values (?,?,?) ',params : [song.release.id,song.release.link,song.release.ssid]});
                            }
                        }
                    }).then( rs=> {
                        //处理歌手
                        if(rs === 0){
                            return 0;//忽略该歌曲
                        }else{
                            //查询歌手是否存在
                            var singers = song.singers;
                            if(singers.length > 0){
                                var sql = 'select * from music_singer where id in (';
                                var params = [];
                                for(var i=0;i<singers.length;i++){
                                    var sing = singers[i];
                                    sql += '?' + (i == singers.length -1 ? '' : ',');
                                    params.push(sing.id);
                                }
                                sql +=')'
                                return query({
                                    sql : sql,
                                    params : params
                                });
                            }else{
                                return 1;
                            }
                        }
                    }).then(rs => {
                        if(rs === 0){
                            return 0;
                        }else if(rs ===1 ){
                            return 1;
                        }else{
                            //处理歌手信息
                            var singers = rs[0] || [];
                            var hasId = {};
                            singers.forEach(function(item){
                                hasId[item.id] = true;
                            });
                            var saveSingers = [];
                            song.singers.forEach(function(item){
                                if(hasId[item.id] !== true){
                                    //不存在
                                    saveSingers.push({
                                        id : item.id,
                                        name : item.name,
                                        "name_usual":item.name_usual,
                                        region : item.region.join(','),
                                        avatar : item.avatar,
                                        genre : item.genre.join('__')
                                    });
                                }
                            })
                            if(saveSingers.length > 0){
                                var mmm = {};
                                var sql = 'insert into music_singer (id,name,avatar,name_usual,region,genre) values ';
                                var params = [];
                                for(var i=0;i<saveSingers.length;i++){
                                    var singer = saveSingers[i];
                                    if(!mmm[singer.id]){
                                        mmm[singer.id] = true;//过滤重复数据
                                        sql += (params.length > 0 ? ',' : '') + '(?,?,?,?,?,?)';
                                        params = params.concat([singer.id,singer.name,singer.avatar,singer.name_usual,singer.region,singer.genre]);
                                    }
                                }
                                return query({sql : sql,params : params})
                            }else{
                                return 1;//没有要保存的歌手信息,或者已经都保存过了
                            }
                        }
                    }).then(rs=>{
                        //开始保存歌曲
                        if(rs === 0){
                            that.log('歌曲重复,当前频道:'+that.channelNow,0);
                            return 0;
                        }else{
                            that.existNum = 0;
                            that.log('保存歌曲:'+song.title,0);
                            var sql = 'insert into music_music (sid,ssid,aid,album,albumtitle,artist,file_ext,kbps,length,picture,public_time,sha256,title,url,albumid,singerid,channelid) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)';

                            var singerId= '';
                            var singers = song.singers;
                            if(singers && singers.length >0){
                                singerId = singers.map(item => {
                                    return item.id;
                                }).join(',');
                            }
                            var params = [
                                song.sid,
                                song.ssid,
                                song.aid,
                                song.album,
                                song.albumtitle,
                                song.artist,
                                song.file_ext,
                                song.kbps,
                                song.length,
                                song.picture,
                                song.public_time,
                                song.sha256,
                                song.title,
                                song.url,
                                song.release && song.release.id ? song.release.id : '',
                                singerId,
                                that.currentChannel
                            ];
                            return query({sql : sql,params : params});
                        }
                    }).then(rs=>{
                        //处理歌曲数量信息
                        return query([
                        {
                            sql : 'select count(1) as num from music_music where channelid=? ',
                            params : [that.currentChannel]
                        },{
                            sql : 'select song_num as num from music_channel where channelId=?',
                            params : [that.currentChannel]
                        }
                        ]);
                    }).then(rs=>{
                        //判断歌曲数量和已存数量
                        var rst1 = rs[0],rst2 = rs[1];
                        var num1 = rst1[0].num || 0,num2 = rst2[0].num || 0;
                        num1 = parseInt(num1,10);
                        num2 = parseInt(num2,10);
                        if((num1 > num2 - 50 && num2 > 100) || that.existNum > that.maxErrorNum){
                            that.existNum = 0;
                            //更换channel
                            that.log('当前频道歌曲数量过少,更换频道',1)
                            that.currentChannel = null;
                            that.findChannel();
                            setTimeout(function(){
                                that.findByChannel();
                            },that.getPer(1))
                        }else{
                            that.log('本次查询完毕,5s后继续重新请求当前频道',1)
                            setTimeout(function(){
                                that.findByChannel();
                            },that.getPer(1))
                        }
                    }).catch(err=>{
                        that.log(err);
                        that.log('保存歌曲信息过程中出错',0);
                        setTimeout(function(){
                            that.findByChannel();
                        },that.getPer(0))
                    })
                }else{
                    that.log('获得歌曲,但无信息,重新请求',0);
                    setTimeout(function(){
                        if(that.existNum > 10){
                            that.existNum = 0;
                            that.findChannel();
                        }
                        that.findByChannel();
                    },that.getPer(1));
                }
            }
        })
    }else{
        that.log('未获得可用频道,5s后重新请求',0)
        //当前没有可用频道,则5秒后,重新查询
        setTimeout( () => {
            that.findByChannel();
        },that.getPer(1));
    }
}

//从不同类型里面持续检索歌曲,并请求,请求频率要低

updateChannel ( channelObj ){
    var that = this;
    const sqlA = {
        sql : 'select * from music_channel where channelId=?',
        params : [channelObj.channelId]
    };
    return query(sqlA).then(function(rs){
        var rst = rs[0];
        if(rst.length == 0){//不存在,插入
            var sqlB = {
                sql : 'insert into music_channel (channelId,name,intro,cover,song_num) values (?,?,?,?,?)',
                params : [channelObj.channelId,channelObj.name,channelObj.intro,channelObj.cover,channelObj.song_num]
            };
            that.currentChannel = channelObj.channelId;//更新当前可用channelID
            that.log('该频道是新频道,设为可用频道')
            return query(sqlB);
        }else{
            //判断下num
            var obj = rst[0];
            if(channelObj.song_num > obj.song_num){
                var sqlC = {
                    sql : 'update music_channel set song_num=? where channelId=?',
                    params : [channelObj.song_num,channelObj.channelId]
                };
                that.currentChannel = channelObj.channelId;//更新可用ID。
                that.log('当前频道为旧频道,但有歌曲添加,设为可用频道')
                return query(sqlC);
            }else{
                //
                that.currentChannel = channelObj.channelId;//更新可用ID。
                that.log('当前频道为旧频道,无变化,设为可用频道')
                return true;//
            }
        }
    });
}

/*从0 - 300 检查可用channel*/
findChannel () {
    this.log('开始重新查找可用频道')
    const that = this;
    if(!that.channelNow){
        that.channelNow = that.channelStart;    
    }
    if(that.channelNow >= that.channelEnd){
        //结束查询channel
        that.channelNow = that.channelStart;
    }
    superagent.get(that.channelUrl+that.channelNow).set(that.requestOpt).end(function(err,res){
        that.channelNow++;
        if(err){
            that.log('查找可用频道报错')
            that.findChannel();
        }else{
            const resObj = JSON.parse(res.text);
            const data = resObj.data;
            if(data && data.channels && data.channels.length > 0){
                const item = data.channels[0];
                const channelObj = {
                    name : item.name,
                    cover : item.cover,
                    intro : item.intro,
                    song_num : item.song_num,
                    channelId : item.id
                };
                that.log('获得可用频道准备更新数据库')
                //更新到数据库中,并判断当前频道是否可采集,如果可采集则停止,否则进入下一个循环。
                that.updateChannel(channelObj);
            }else{
                that.findChannel();
            }
        }
    })
}
     }
     module.exports = DouBan;

以上所有全部用于学习、研究使用,若有侵权请联系本站管理员删除。

转载请注明出处: http://sdxlp.cn/article/payinrue.html


如果对你有用的话,请赏给作者一个馒头吧 ...或帮点下页面底部的广告,感谢!!

赞赏支持
提交评论
评论信息(请文明评论)
暂无评论,快来快来写想法...
推荐
提供一个比较简单的小工具,或许能给你个灵感呢? 富文本内容存储在数据库,在页面展示的时候,需要向富文本添加一些字符串(比如说:ABCD ,又或者分数等)。
为实现华为手机与电脑的文件传输与共享,以下需要在手机和电脑上所做的设置是一次性的,在最初使用前设置好即可,以后使用时无需再次设置。
小程序需要用到用户的手机号码,看了下API 以及相关的demo,基本都是服务端进行解密的,问题是需要的参数并没有用到secret,只需要 session_key / iv /encryptedData 即可,那完全可以在客户端进行处理啊。
近期有大量的iPhone13用户称自己遇到了iPhone出现粉屏的情况,并且是出现很多次的那种,询问客服之后也没有解决方法,所以很多用户想要知道iPhone13粉屏重启怎么办,下面就让点点给小伙伴们介绍一下。
微信中打开Word、Excel等文档,会保存在一个目录下。但是,微信把这个目录隐藏很深,路径不易记住,所以,我把这个路径记下来。 在安卓系统中可以安装ES文件浏览器来获得。如果找不到这个路径,或者显示为空,也可以转发到另一台安卓机上的微信号,在那台打开后再保存下来。
腾讯公司的微信、QQ都是现在十分常用的社交软件,很多的功能,都是可以相互使用的,可以在工作与生活中,增加很多的便利,有些新用户不知道如何将手机微信表情包发送到QQ.
如果在安装nginx时不运行或安装nginx发现少了一些需要的模块的话,需要重新安装,那么如何来操作呢?
有时我们登录qq时,会看到账号已被冻结的风险提示,这时候我们就没有办法正常登录qq账号了,那么,我们的qq号为什么会被冻结?如果发现qq号突然被冻结了我们要怎么去解决这个问题呢?