3
最近一直在学英语,因此写了个爬虫爬取歌单并下载,然后随时都可以听。
GitHub地址:https://github.com/leeseean/nodejs-crawler。

页面分析

要用爬虫下载音频,那自然是要找到音频链接了。而网站的音频链接没有直接暴露出来,因此需要分析找出获取音频链接的办法。
进入喜马拉雅官网主页,随便找一个主播的页面进入,我这里找的是[英语主播Emily](http://www.ximalaya.com/29101549/album/2801092)的主页,然后里面有她的播单,F12打开一看这些歌单都没有直接写音频链接。但是还是有一些规律的,每个音频都有一个ID,后面点击这个音频的时候,会有一个AJAX请求,请求的链接就包含这个ID,这个请求会返回音频的真实链接,这样就拿到了音频链接。如图

clipboard.png

点击音频发出AJAX请求,请求返回数据里面包含真实音频链接,如图

clipboard.png

写爬虫

需求分析完了,那现在当然是写爬虫了,首先爬取主播页,拿到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等一类参数。



all2005
3.4k 声望36 粉丝