前提:最近一直在看node,平时碎觉喜欢听盗墓笔记啥的有声小说,然后突然就就想试着写个爬虫自己下载下来,虽然有点多此一举,但是就当学习练练手了,在这里记录一下!
没有express基础的,请先行了解
技术:vue-cli,iview,axios
ps:因为准备以后开发其他的demo都放到一起,用一个server去转发接口,所以准备搭建一个vue项目,方便以后加入其他的demo,如果你只是为了实现本文的功能,前端页面大可不必搭我这套,可以自选方法实现,具体实现效果如下图
打开喜马拉雅的PC官网,随意选一个免费的小说点进去,打开浏览器控制后台,观察http请求,如下图
通过分析http请求,我们可以拿到api,在经过你不咋严密的观察后,你可以很容易分析出一些关键的字段
首先,我们先从请求参数分析,看下图:
可以看出albumId是识别每一本小说的唯一标识,这个字段我们将来就用于查询数据,后面的pageNum,pageSize很容观察出是用来做分页的。
接下来看响应的字段,在又经过你不咋严密的观察后,你会很容易的发现,下图的字段,肯定就是每条音频的详细api了
展开之后,在最后一次经过你不咋严密的观察后,你会发现很巧,你要的数据都在这里
想我这种英语四级都没过的人都看出来了,src:音频真实链接,tranckName:音频每集的名字,albumName:小说名
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,到现在我们的数据已经拿到了,喝杯茶水泡点枸杞休息一下,然后继续~
//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端口')
})
//获取数据列表
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'
})
})
});
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关心的最后产生的值。
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写的接口就可以啦
源码链接戳此处
如有不足,欢迎指正~