node爬取网易云歌曲

5,286 阅读2分钟

起因:老爸让我下载几千首歌曲给他在车上播放,感觉手动下载,就算批量下载也要时间,索性写个爬虫自动下载吧。。

对于这个爬虫小项目,选择了node+koa2,初始化项目koa2 projectName(需要先全局安装koa-generator),然后进入项目文件,npm install && npm start,其中依赖用到了superagent, cheerio, async, fs, path

打开网易云网页版,点击歌单页面,我选择了华语分类,右键查看框架源码,获取真实url,找到id为m-pl-container的html结构,这就是这次需要爬取的歌单列表,直接用superagent请求url,只能爬取到第一页的数据,需要async来并发爬取

static getPlayList(){
	const pageUrlList = this.getPageUrl();

	return new Promise((resolve, reject) => {
		asy.mapLimit(pageUrlList, 1, (url, callback) => {
			this.requestPlayList(url, callback);
		}, (err, result) => {
			if(err){
				reject(err);
			}

			resolve(result);
		})
	})
}

其中const asy = require('async'),因为用到async/await,所以区分下,requestPlayList是superagent发起的请求

static requestPlayList(url, callback){
	superagent.get(url).set({
		'Connection': 'keep-alive'
	}).end((err, res) => {
		if(err){
			console.info(err);
			callback(null, null);
			return;
		}

		const $ = cheerio.load(res.text);
		let curList = this.getCurPalyList($);
		callback(null, curList);  
	})
}

getCurPalyList是获取页面上的信息,传入$用于dom操作

static getCurPalyList($){
	let list = [];

	$('#m-pl-container li').each(function(i, elem){
		let _this = $(elem);
		list.push({
			name: _this.find('.dec a').text(),
			href: _this.find('.dec a').attr('href'),
			number: _this.find('.nb').text()
		});
	});

	return list;
}

至此,歌单列表爬取完成,接下来要爬取歌曲列表

static async getSongList(){
	const urlCollection = await playList.getPlayList();

	let urlList = [];
	for(let item of urlCollection){
		for(let subItem of item){
			urlList.push(baseUrl + subItem.href);
		}
	}

	return new Promise((resolve, reject) => {
		asy.mapLimit(urlList, 1, (url, callback) => {
			this.requestSongList(url, callback);
		}, (err, result) => {
			if(err){
				reject(err);
			}

			resolve(result);
		})
	})
}

requestSongList的使用跟上面playList的差不多,因此不再重复。上面代码获取到歌曲列表后,需要下载到本地

static async downloadSongList(){
	const songList = await this.getSongList();

	let songUrlList = [];
	for(let item of songList){
		for(let subItem of item){
			let id = subItem.url.split('=')[1];
			songUrlList.push({
				name: subItem.name,
				downloadUrl: downloadUrl + '?id=' + id + '.mp3'
			});
		}
	}

	if(!fs.existsSync(dirname)){
		fs.mkdirSync(dirname);
	}
	
	return new Promise((resolve, reject) => {
		asy.mapSeries(songUrlList, (item, callback) => {
			setTimeout(() => {
				this.requestDownload(item, callback);
				callback(null, item);
			}, 5e3);
		}, (err, result) => {
			if(err){
				reject(err);
			}

			resolve(result);
		})
	})
}

其中requestDownload是请求downloadUrl并下载保存到本地

static requestDownload(item, callback){
	let stream = fs.createWriteStream(path.join(dirname, item.name + '.mp3'));

	superagent.get(item.downloadUrl).set({
		'Connection': 'keep-alive'
	}).pipe(stream).on('error', (err) => {
		console.info(err);   // error处理,爬取错误时,打印错误并继续向下执行
	})
}

到此,爬虫小程序完成。该项目爬取歌单列表-->歌曲列表-->下载到本地,当然也可以直接找到某位歌手的主页,修改传入songList的url,直接下载该歌手的热门歌曲。