起因:老爸讓我下載幾千首歌曲給他在車上播放,感覺手動下載,就算批量下載也要時間,索性寫個爬蟲自動下載吧。。
對於這個爬蟲小專案,選擇了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,直接下載該歌手的熱門歌曲。