前提:最近一直在看node,平时碎觉喜欢听盗墓笔记啥的有声小说,然后突然就就想试着写个爬虫自己下载下来,虽然有点多此一举,但是就当学习练练手了,在这里记录一下!

没有express基础的,请先行了解

确定需求
  • 1,拿到xmly的数据,分析api
  • 2,用node开发接口
  • 3,构建前端页面

一,前端页面构建

技术:vue-cli,iview,axios
ps:因为准备以后开发其他的demo都放到一起,用一个server去转发接口,所以准备搭建一个vue项目,方便以后加入其他的demo,如果你只是为了实现本文的功能,前端页面大可不必搭我这套,可以自选方法实现,具体实现效果如下图

1-1

二,拿到xmly的数据,分析api,用node发起请求拿到数据

2.1 找到xmly音频的真实链接

打开喜马拉雅的PC官网,随意选一个免费的小说点进去,打开浏览器控制后台,观察http请求,如下图
在这里插入图片描述
在这里插入图片描述

通过分析http请求,我们可以拿到api,在经过你不咋严密的观察后,你可以很容易分析出一些关键的字段

首先,我们先从请求参数分析,看下图:
在这里插入图片描述
可以看出albumId是识别每一本小说的唯一标识,这个字段我们将来就用于查询数据,后面的pageNum,pageSize很容观察出是用来做分页的。

接下来看响应的字段,在又经过你不咋严密的观察后,你会很容易的发现,下图的字段,肯定就是每条音频的详细api了
在这里插入图片描述
展开之后,在最后一次经过你不咋严密的观察后,你会发现很巧,你要的数据都在这里
在这里插入图片描述
想我这种英语四级都没过的人都看出来了,src:音频真实链接,tranckName:音频每集的名字,albumName:小说名

2.2 用node写一个简单的爬虫,抓取数据

需要用到的模块如下图:
在这里插入图片描述

superagent是nodejs里一个非常方便的客户端请求代理模块,他很大的特色就是链式调用,这里我用到superagent向https://www.ximalaya.com/revision/play/album发送get请求,拼上之前我们提到的请求参数

app.get('/', function (req, res, next) {
	let _res = res;
    let params = {
        albumId:'123131',
        pageNum:'1',
        sort:'-1',
        pageSize:'30'
    }

    let param = qs.stringify(params)

    superagent
        .get('https://www.ximalaya.com/revision/play/album?' + param)
        .then(res => {
            _res.send(res.body.data.tracksAudioPlay)
        })
        .catch(err => {
           _res.send(err)
        })
})

app.listen(5050,function(){
    console.log('正在监听5050端口')
})

启动node服务,在浏览器上输入http://localhost:5050/,应该就可以看到你请求到数据了,如下图:
在这里插入图片描述
OK,到现在我们的数据已经拿到了,喝杯茶水泡点枸杞休息一下,然后继续~

三,用node开发接口

3.1 确定我们的接口需求:
  1. /getXmlyList:实现通过albumId查询,获取音频的list,传给前端
  2. /downloadSingle:实现批量下载,将下载结果返回给前端
3.2 创建app.js 编写node服务:
//app.js
const express = require('express')
const querystring=require('querystring');
const path = require('path');
const api = require('./route/api')


let app = express();


//使用router中间件
app.use('/xmly',api)
//路由
app.get('/', function (req, res, next) {
    res.sendFile(path.join(__dirname + '/html/index.html'));
});
//监听端口
app.listen(5050,function(){
    console.log('正在监听5050端口')
})

3.3 创建route/api.js 编写api:
3.3.1 /getXmlyList 接口:
//获取数据列表
router.get('/getXmlyList',(req,res) => {

    let _res = res;
    let params = {
        albumId:req.query.albumId,//get请求的参数在req.query里
        pageNum:'1',
        sort:'-1',
        pageSize:'30'
    }

    let param = qs.stringify(params)
	//这里使用superagent代理发送get请求喜马拉雅的接口获取数据
    superagent
        .get('https://www.ximalaya.com/revision/play/album?' + param)
        .then(res => {
            //这里请求成功将数据响应给前端
            _res.json({
                status: 1,
                msg: 'success',
                list: res.body.data.tracksAudioPlay
            })
        })
        .catch(err => {
      		 //响应获取数据失败
            _res.json({
                status: -1,
                msg: 'error'
            })
        })
});
3.3.2 /downloadSingle 接口:

1.使用fs.createWriteStream()写入文件:

var downLoadMp3 = function(dir,name,filePath){
    request(dir).pipe(fs.createWriteStream( filePath + name +'.mp3')).on('close',function(){
        console.log('saved' + name)
    })
}

2.使用async异步批量下载:

将文件下载在根目录的./mp3路径下

关于async的map操作,详见:async_demo/map.js,对集合中的每一个元素,执行某个异步操作,得到结果。所有的结果将汇总到最终的callback里。与forEach的区别是,forEach只关心操作不管最后的值,而map关心的最后产生的值。

  1. 并行执行。async.map同时对集合中所有元素进行操作,结果汇总到最终callback里。如果出错,则立刻返回错误以及已经执行完的任务的结果,未执行完的占个空位
  2. 顺序执行。async.mapSeries对集合中的元素一个一个执行操作,结果汇总到最终callback里。如果出错,则立刻返回错误以及已经执行完的结果,未执行的被忽略。
router.get('/downloadSingle',function(req, res, next){
    let mp3List = JSON.parse(req.query.paramJson)
    //检查是否存在./mp3路径如果不存在创建./mp3目录后再下载,如存在直接下载
    fs.exists('./mp3', function (exists) {
        // console.log(exists ? "it's there" : "no file!");
        if(!exists) {
        	//创建./mp3目录
            fs.mkdir('./mp3',0777, function (err) {
                if (err) {
                    throw err;
                }else {
                	//使用async异步批量下载
                    async.mapSeries(mp3List,function(item, callback){
                        setTimeout(function(){
                            downLoadMp3(item.dir,item.name,'./mp3/')
                            callback(null, item);
                        },400);
                    }, function(err, results){
                        res.json({
                            status: 1,
                            msg: 'saved!'
                        })
                    });
                }
            });

            
        }else {
            async.mapSeries(mp3List,function(item, callback){
                setTimeout(function(){
                    downLoadMp3(item.dir,item.name,'./mp3/')
                    callback(null, item);
                },400);
            }, function(err, results){
                res.json({
                    status: 1,
                    msg: 'saved!'
                })
            });
        }
    });
    
});

var downLoadMp3 = function(dir,name,filePath){
    request(dir).pipe(fs.createWriteStream( filePath + name +'.mp3')).on('close',function(){
        console.log('saved' + name)
    })
}

完整api.js代码如下:

const express = require('express');
const superagent = require('superagent');
const qs = require('querystring');
const router = express.Router();
const path = require('path')
const fs = require('fs')
const request = require('request')
const async = require('async')


//获取数据列表
router.get('/getXmlyList',(req,res) => {

    let _res = res;
    console.log(req.query)
    let params = {
        albumId:req.query.albumId,
        pageNum:'1',
        sort:'-1',
        pageSize:'300'
    }

    let param = qs.stringify(params)

    superagent
        .get('https://www.ximalaya.com/revision/play/album?' + param)
        .then(res => {
            // _res.send(res.body.data.tracksAudioPlay);
            // console.log(typeof res.body)
            _res.json({
                status: 1,
                msg: 'success',
                list: res.body.data.tracksAudioPlay
            })
        })
        .catch(err => {
            res.json({
                status: -1,
                msg: 'error'
            })
        })
});

//下载音频
router.get('/downloadSingle',function(req, res, next){
    let mp3List = JSON.parse(req.query.paramJson)
    fs.exists('./mp3', function (exists) {
        // console.log(exists ? "it's there" : "no file!");
        if(!exists) {
            fs.mkdir('./mp3',0777, function (err) {
                if (err) {
                    throw err;
                }else {
                    async.mapSeries(mp3List,function(item, callback){
                        setTimeout(function(){
                            downLoadMp3(item.dir,item.name,'./mp3/')
                            callback(null, item);
                        },400);
                    }, function(err, results){
                        res.json({
                            status: 1,
                            msg: 'saved!'
                        })
                    });
                }
            });

            
        }else {
            async.mapSeries(mp3List,function(item, callback){
                setTimeout(function(){
                    downLoadMp3(item.dir,item.name,'./mp3/')
                    callback(null, item);
                },400);
            }, function(err, results){
                res.json({
                    status: 1,
                    msg: 'saved!'
                })
            });
        }
    });
    
});


var downLoadMp3 = function(dir,name,filePath){
    request(dir).pipe(fs.createWriteStream( filePath + name +'.mp3')).on('close',function(){
        console.log('saved' + name)
    })
}

module.exports = router;

到此为止我们的node接口也解决了~,代码不多仔细分析分析相信所有人都能看懂的,接下来就启动node服务,然后在前端请求node写的接口就可以啦
在这里插入图片描述在这里插入图片描述

源码链接戳此处
如有不足,欢迎指正~

Logo

前往低代码交流专区

更多推荐