node.js 实现简单爬虫批量下载喜马拉雅音频
前提:最近一直在看node,平时碎觉喜欢听盗墓笔记啥的有声小说,然后突然就就想试着写个爬虫自己下载下来,虽然有点多此一举,但是就当学习练练手了,在这里记录一下!没有express基础的,请先行了解确定需求1,拿到xmly的数据,分析api2,用node开发接口3,构建前端页面一,前端页面构建技术:vue-cli,iview,axiosps:因为准备以后开发其他的demo都放到一...
前提:最近一直在看node,平时碎觉喜欢听盗墓笔记啥的有声小说,然后突然就就想试着写个爬虫自己下载下来,虽然有点多此一举,但是就当学习练练手了,在这里记录一下!
没有express基础的,请先行了解
确定需求
- 1,拿到xmly的数据,分析api
- 2,用node开发接口
- 3,构建前端页面
一,前端页面构建
技术:vue-cli,iview,axios
ps:因为准备以后开发其他的demo都放到一起,用一个server去转发接口,所以准备搭建一个vue项目,方便以后加入其他的demo,如果你只是为了实现本文的功能,前端页面大可不必搭我这套,可以自选方法实现,具体实现效果如下图
二,拿到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 确定我们的接口需求:
- /getXmlyList:实现通过albumId查询,获取音频的list,传给前端
- /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关心的最后产生的值。
- 并行执行。async.map同时对集合中所有元素进行操作,结果汇总到最终callback里。如果出错,则立刻返回错误以及已经执行完的任务的结果,未执行完的占个空位
- 顺序执行。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写的接口就可以啦
源码链接戳此处
如有不足,欢迎指正~
更多推荐
所有评论(0)