最近一直在学英语,因此写了个爬虫爬取歌单并下载,然后随时都可以听。
GitHub地址:https://github.com/leeseean/nodejs-crawler。
页面分析
要用爬虫下载音频,那自然是要找到音频链接了。而网站的音频链接没有直接暴露出来,因此需要分析找出获取音频链接的办法。
进入喜马拉雅官网主页,随便找一个主播的页面进入,我这里找的是[英语主播Emily](http://www.ximalaya.com/29101549/album/2801092)的主页,然后里面有她的播单,F12打开一看这些歌单都没有直接写音频链接。但是还是有一些规律的,每个音频都有一个ID,后面点击这个音频的时候,会有一个AJAX请求,请求的链接就包含这个ID,这个请求会返回音频的真实链接,这样就拿到了音频链接。如图
点击音频发出AJAX请求,请求返回数据里面包含真实音频链接,如图
写爬虫
需求分析完了,那现在当然是写爬虫了,首先爬取主播页,拿到ID,然后根据ID发送AJAX请求拿到真实音频地址。
用的模块有cheerio和request。
const request = require('request');
const fs = require('fs');
const cheerio = require('cheerio');
//这个时间后面设置cookie需要用到
const formatTime = (new Date()).toLocaleString().replace(/\//g, '-').replace(/[\u4E00-\u9FA5]/g, '');
直接用request(url,callcack)发现返回一个301重定向的页面,因此要设置uset-agent和cookie才能正确访问到页面。如图,
相应设置代码如下:
function setOptions({
url,
cookies,
contentType
}) {
const jar = request.jar();
jar.setCookie(request.cookie(cookies), url);
return {
url: url,
jar: jar,
method: 'GET',
headers: {
"Connection": "keep-alive",
"Content-Type": contentType,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36"
}
};
}
请求到页面到拿到所有音频的ID,并根据音频ID发送AJAX请求,代码如下:
function getListIdsCallback(error, response, body) {
if (!error && response.statusCode === 200) {
const listIds = [];//存放所有音频ID的数组
const $ = cheerio.load(body);
$('div.album_soundlist > ul > li > div > a.title').each((index, item) => {
listIds.push($(item).attr('href').split('/')[3]);
});
getAudioJson(listIds);//获取音频真实链接并下载音频
}
}
获取音频真实链接并下载音频
代码如下:
function getAudioJson(listIds) {
if (listIds.length === 0) {
return;
}
const id = listIds.shift();
request(setOptions({
url: `http://www.ximalaya.com/tracks/${id}.json`,
cookies: `_xmLog=xm_1510364052559_j9unrdjjmwt7gx; login_from=qq; nickname=All2005; login_type=QQ; 1&remember_me=y; 1&_token=96575028&ecb632710362104767080ce01362b33cc881; trackType=web; x_xmly_traffic=utm_source%3A%26utm_medium%3A%26utm_campaign%3A%26utm_content%3A%26utm_term%3A%26utm_from%3A; Hm_lvt_4a7d8ec50cfd6af753c4f8aee3425070=1510364053; Hm_lpvt_4a7d8ec50cfd6af753c4f8aee3425070=1510376453; _ga=GA1.2.1519968795.1510364053; _gat=1; 1_l_flag=96575028&ecb632710362104767080ce01362b33cc881_${formatTime}; msgwarn=%7B%22category%22%3A%22%22%2C%22newMessage%22%3A0%2C%22newNotice%22%3A0%2C%22newComment%22%3A0%2C%22newQuan%22%3A0%2C%22newFollower%22%3A0%2C%22newLikes%22%3A0%7D`,
contentType: 'text/html;charset=utf-8',
}), (error, response, body) => {
return getAudioJsonCallback(error, response, body, listIds);
});
}
function getAudioJsonCallback(error, response, body, listIds) {
if (!error && response.statusCode === 200) {
const JsonData = JSON.parse(body);
downloadFile(JsonData['play_path'], `${JsonData['id']}.m4a`, (e) => {
console.log(`下载完毕${JsonData['id']}`);
getAudioJson(listIds);
});
}
}
function downloadFile(url, filename, callback) {
const stream = fs.createWriteStream(filename);
request(url).pipe(stream).on('close', callback);
}
代码技巧
这里获取到listIds一开始是使用一个循环去下载,后面发现一下子同时开启几十个下载node根本吃不消,下下来的音频不完整,后面采用一个一个下载的办法,就完全解决的这个问题。相关代码如下:
const id = listIds.shift();//每次取出一个id去请求
downloadFile(JsonData['play_path'], `${JsonData['id']}.m4a`, (e) => {
console.log(`下载完毕${JsonData['id']}`);
getAudioJson(listIds);//下载完后再次启动请求
});
总结
先写到这了。爬虫的关键就是找出真实地址,然后抓页面的时候如果抓不到记得补充cookie,设置user-agent等一类参数。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。