1. 程式人生 > >node.js 實現爬蟲批量下載喜馬拉雅音訊

node.js 實現爬蟲批量下載喜馬拉雅音訊

前提:最近一直在看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寫的介面就可以啦
在這裡插入圖片描述在這裡插入圖片描述

原始碼連結戳此處
如有不足,歡迎指正~