找不到網(wǎng)易云音樂(lè)的開(kāi)放API ,跟蟹棒一起寫(xiě)一個(gè)嗎串慰?

說(shuō)來(lái)慚愧偏塞,距離蟹棒在簡(jiǎn)書(shū)發(fā)表第一篇文章已經(jīng)時(shí)隔兩月,蟹棒一直沒(méi)有要寫(xiě)什么博客的題材邦鲫,也再一次的讓蟹棒為自己肚子里丁點(diǎn)的墨水發(fā)愁灸叼,蟹棒的寫(xiě)作與思考功力還需修煉,勤奮才能修得正果 !

致謝

蟹棒在簡(jiǎn)書(shū)的第一篇文章是和簡(jiǎn)友們分享如何搭建自己的個(gè)人網(wǎng)站,半個(gè)小時(shí)擼出自己的個(gè)人博客怜姿,至此該文章已經(jīng)收獲簡(jiǎn)友們 2868 次瀏覽慎冤,169 次喜歡,102 個(gè)評(píng)論沧卢,32 個(gè)粉絲蚁堤,蟹棒受寵若驚,也再次感謝簡(jiǎn)友們的支持與鼓勵(lì)但狭,謝謝你們給蟹棒的信心和決心披诗,謝謝!

關(guān)于

為什么會(huì)突然想到寫(xiě)一個(gè)這樣的博客呢立磁?

之前蟹棒打算獨(dú)立完成一個(gè)音樂(lè)播放類(lèi)的APP呈队,苦于一直沒(méi)有音樂(lè)資源,自己又沒(méi)有強(qiáng)大的后臺(tái)數(shù)據(jù)庫(kù)來(lái)支持唱歧,所以就百度谷歌各種音樂(lè)平臺(tái)的API宪摧,發(fā)現(xiàn)這些API要不就是無(wú)法訪問(wèn),要不就是資源不全颅崩,根本無(wú)法完成一個(gè)完整的流程几于,所以蟹棒才萌生要自己寫(xiě)一個(gè)API的想法,資源我們從網(wǎng)易云音樂(lè)的官網(wǎng)爬數(shù)據(jù)就可以了沿后,當(dāng)然這種數(shù)據(jù)源是不合法的沿彭,所以簡(jiǎn)友們可自己用作私人開(kāi)發(fā),不可以拿來(lái)商用哦


不要告訴別人哦

因?yàn)檫@次的爬蟲(chóng)是用NodeJS寫(xiě)的尖滚,所以希望看這篇文章的簡(jiǎn)友們要對(duì)NodeJS有一定了解喉刘,當(dāng)然你可以在了解完蟹棒的邏輯之后使用其他語(yǔ)言完成

坐穩(wěn)了沒(méi)有,蟹棒開(kāi)車(chē)了

迅捷蟹漆弄,我們走

哈哈睦裳,話不多說(shuō),言歸正傳撼唾。

我們先來(lái)看看 網(wǎng)易云音樂(lè)的主頁(yè)

網(wǎng)易云音樂(lè)主頁(yè)

蟹棒的目標(biāo)先是拿下默認(rèn)首頁(yè)里熱門(mén)推薦的八個(gè)歌單廉邑,首先我們要新建一個(gè)NodeJs項(xiàng)目

// 創(chuàng)建文件夾
mkdir <your project name>
// 初始化NodeJs
npm init
// 完成 npm init 配置選項(xiàng)添加 package.json
// 安裝需要用到的模塊
npm install --save express superagent cheerio

express 我們用來(lái)完成API的路由訪問(wèn)配置,superagent 用來(lái)完成對(duì)網(wǎng)易云音樂(lè)網(wǎng)站頁(yè)面的訪問(wèn)券坞,cheerio 用來(lái)處理數(shù)據(jù)訪問(wèn)返回的HTML數(shù)據(jù),三個(gè)模塊的 api 大家可以到他們各自的官網(wǎng)掃蕩一番

想要爬取網(wǎng)頁(yè)上的數(shù)據(jù)肺素,就必須對(duì)網(wǎng)頁(yè)上的HTML結(jié)構(gòu)有一定的了解恨锚,而蟹棒研究網(wǎng)易云的主頁(yè)結(jié)構(gòu)之后,發(fā)現(xiàn)頁(yè)面結(jié)構(gòu)比想象的要復(fù)雜一點(diǎn)倍靡,不過(guò)你只要問(wèn)問(wèn)的抓住蟹棒的車(chē)把猴伶,蟹棒開(kāi)的很穩(wěn)

首先訪問(wèn) 網(wǎng)易云音樂(lè)的主頁(yè),打開(kāi)調(diào)試窗口(F12),點(diǎn)擊 Elements 選項(xiàng)他挎,這時(shí)候你就可以看到主頁(yè)的HTML主要結(jié)構(gòu)如下

主頁(yè)代碼結(jié)構(gòu)

使用快捷鍵 Ctrl + shift + c 進(jìn)入 select 模式筝尾,這時(shí)候我們鼠標(biāo)移動(dòng)到頁(yè)面上任何一個(gè)元素上,Element 就會(huì)自動(dòng)定位到元素的代碼位置上办桨,按照方法筹淫,我們定位第一個(gè)歌單的位置

選擇元素
<a title="愿有歲月可回首,且以深情共白頭" href="/playlist?id=316387203" class="msk" data-res-id="316387203" data-res-type="13" data-res-action="log" data-res-data="recommendclick|0|featured|user-playlist"></a>

不難發(fā)現(xiàn)呢撞,我們需要的資源這里都可以看到损姜,title 顯示的歌單的名稱,href 表示它所指向的位置殊霞,data-res-id 是歌單id摧阅,data-res-type = 13 這個(gè)屬性表明類(lèi)型,13 代表什么類(lèi)型我們不得而知绷蹲,在這里我們并不關(guān)心棒卷,data-res-action 與 data-res-data 我們都不知道具體用途,所以我們只拿自己需要的資源

// 定義我們的歌單對(duì)象結(jié)構(gòu)
{
    id: '歌單ID',
    title: '歌單名稱',
    href: '歌單指向',
    type: '類(lèi)型',
    cover: '歌單封面圖片'
}
  

我們?cè)賮?lái)分析怎么從HTML代碼中獲得想要的八個(gè)歌單的信息祝钢,從代碼中我們觀察到在 a 鏈接外層 存在 一個(gè) li 標(biāo)簽比规,而 li 的外層正是我們期望見(jiàn)到的 ul 列表標(biāo)簽,我們將 Element 聚焦到 ul 上

列表標(biāo)簽

在 ul 中存在八個(gè) li 太颤,感興趣的簡(jiǎn)友可以依次打開(kāi)看一下代碼苞俘,正是我們上面決定拿到的八個(gè)歌單信息,研究完歌單信息在代碼中的詳細(xì)位置龄章,我們來(lái)看一下如何在HTML代碼中獲得這個(gè)ul列表吃谣,ul 標(biāo)簽存在一個(gè)名為 m-cvrlst 的 class , 我們可以點(diǎn)擊 Console 選項(xiàng),輸入如下代碼

document.getElementsByClassName('m-cvrlst');
運(yùn)行結(jié)果

結(jié)果并不如蟹棒所料做裙,在確定 class 名稱沒(méi)有輸錯(cuò)的情況下岗憋,我們得到一個(gè)空數(shù)組,很明顯我們?cè)陧?yè)面中觀察到確實(shí)存在 class 名為 m-cvrlst 的 ul 元素 锚贱,可以代碼并沒(méi)有獲得期望結(jié)果仔戈,細(xì)心的簡(jiǎn)友此刻已經(jīng)回去研究代碼結(jié)構(gòu),還沒(méi)下車(chē)的簡(jiǎn)友們跟著蟹棒司機(jī)一起回去研究研究

當(dāng)我們聚焦到 ul 元素上時(shí)拧廊,可以在調(diào)試窗口最下面看到此元素對(duì)應(yīng)的層級(jí)結(jié)構(gòu)监徘,不難發(fā)現(xiàn),在 ul 的父級(jí)元素列表中吧碾,存在一個(gè) id 為 g_iframe 的 iframe 元素

IFrame元素

此刻蟹棒車(chē)上的年輕司機(jī)們都露出了不明覺(jué)厲的笑容凰盔,我們點(diǎn)擊紅色箭頭指向的位置定位到該 iframe 元素

定位元素

我們會(huì)發(fā)現(xiàn) iframe 中也同樣存在一個(gè) document 對(duì)象,說(shuō)明此 iframe 加載了一個(gè)另外的 url 倦春,了解了原來(lái)如此之后户敬,我們繼續(xù)在 Console 面板中執(zhí)行如下代碼

g_iframe.contentDocument.getElementsByClassName('m-cvrlst');
執(zhí)行結(jié)果

哈哈落剪,這才是我們需要看到的嘛,年輕司機(jī)們松了一口氣的同時(shí)尿庐,又在考慮另外一個(gè)問(wèn)題忠怖,如何通過(guò)代碼獲得 iframe 里面的 HTML 結(jié)構(gòu),iframe 加載網(wǎng)頁(yè)是在當(dāng)前網(wǎng)頁(yè)從服務(wù)器返回之后再去加載抄瑟,也就是說(shuō) iframe 內(nèi)嵌網(wǎng)頁(yè)的加載要晚于當(dāng)前請(qǐng)求的網(wǎng)頁(yè)凡泣,從上面iframe 的截圖我們看到 iframe 的 src 屬性指向 about:blank,也就是說(shuō) iframe 內(nèi)部的網(wǎng)頁(yè)跳轉(zhuǎn)是通過(guò) Js 代碼控制锐借,所以 iframe 內(nèi)嵌網(wǎng)頁(yè)的加載速度只會(huì)比預(yù)料的速度更晚问麸,所以我們直接請(qǐng)求 http://music.163.com 的時(shí)候,iframe中的內(nèi)容還沒(méi)有加載

光說(shuō)不練假把式钞翔,寫(xiě)幾句代碼驗(yàn)證一下严卖,該動(dòng)動(dòng)我們剛開(kāi)始建立的 NodeJs 項(xiàng)目了

新建 test.js 文件,寫(xiě)入如下代碼

  
// 加載 express 模塊
var app = require('express')();
// 加載 superagent 模塊
var request = require('superagent');
// 加載 cheerio 模塊
var cheerio = require('cheerio');

// 指定訪問(wèn)路由
app.get('/', function(req, res){

    // 請(qǐng)求網(wǎng)易云音樂(lè)主頁(yè)
    request.get('http://music.163.com')
        .end(function(err, _response){

            if (!err) {

                // 如果沒(méi)有發(fā)生錯(cuò)誤, 獲得的html就是網(wǎng)頁(yè)返回的HTML結(jié)構(gòu)
                var html = _response.text;
                // cheeio 初始化完成之后與 jQuery 用法相差無(wú)幾
                var $ = cheerio.load(html);
                // 打印 iframe
                console.log( 'iframe內(nèi)部結(jié)構(gòu):' + $('#g_iframe').html() );

                res.send('Hello');

            } else {
                return next(err);
            }

        });

});

// 監(jiān)聽(tīng)3000 端口
app.listen(3000, function(){
    console.log('Server start!');
});
 // cmd 執(zhí)行如下命令布轿,執(zhí)行完畢不要關(guān)閉控制臺(tái)窗口

cd <your project name>

node test

// 如果成功輸出 "Server start ! "哮笆,證明操作正確,其他情況均為錯(cuò)誤情況
// 瀏覽器訪問(wèn)汰扭,觀察控制臺(tái)輸出
localhost:3000
輸出結(jié)果

不好意思稠肘,車(chē)開(kāi)的快了點(diǎn),大家穩(wěn)住萝毛,看不懂代碼的年輕司機(jī)先不需要懂這段代碼的意思项阴,只要按照步驟驗(yàn)證我們剛剛的猜想,也就我們?cè)谡?qǐng)求網(wǎng)易云音樂(lè)主頁(yè)的時(shí)候 iframe 并沒(méi)有加載笆包,所以我們是沒(méi)有辦法獲得iframe里面的內(nèi)容的环揽,當(dāng)前打印的iframe內(nèi)部 dom 結(jié)構(gòu)為空,關(guān)于以上代碼庵佣,蟹棒到了具體寫(xiě)API的時(shí)候再具體解釋

不好意思歉胶,這波車(chē)開(kāi)的有點(diǎn)快

好了,系好安全帶巴粪,我們要如何獲得 iframe 中的內(nèi)容呢通今,我們必須先要摸清楚iframe在頁(yè)面加載之后去加載了什么網(wǎng)頁(yè) url , 當(dāng)然這個(gè)我們不需要去研究源碼肛根,Chrome 調(diào)試窗口給了我們很方便的工具辫塌,打開(kāi)調(diào)試窗口(F12),點(diǎn)擊 Sources 選項(xiàng)

Sources 選項(xiàng)窗口

這里會(huì)列出當(dāng)前網(wǎng)頁(yè)加載的所有資源派哲,我們可以看到在列表的最后一項(xiàng)臼氨,存在一個(gè)名為 contentFrame 的子節(jié)點(diǎn),點(diǎn)擊打開(kāi)此節(jié)點(diǎn)

contentFrame 節(jié)點(diǎn)

對(duì)比上下兩張截圖狮辽,我們發(fā)現(xiàn)他們加載的都是同一張頁(yè)面一也,但是訪問(wèn)的地址卻是不同的,感興趣的簡(jiǎn)友可以仔細(xì)對(duì)比這兩張截圖中具體加載的內(nèi)容喉脖,現(xiàn)在回到 iframe 椰苟, 現(xiàn)在我們基本確定,contentFrame 節(jié)點(diǎn)中加載的內(nèi)容正是 iframe 中加載url 指向的網(wǎng)址树叽,我們可以驗(yàn)證一下舆蝴,打開(kāi)contentFrame 節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn),在這個(gè)節(jié)點(diǎn)中存在一個(gè) discover 頁(yè)面题诵,點(diǎn)擊打開(kāi)

加載的頁(yè)面

我們看到 discover 頁(yè)面同樣是加載了一個(gè) html 文檔洁仗,可是這個(gè)文檔是不是我們需要的文檔呢?驗(yàn)證看看性锭,在 discover 中按下快捷鍵 Ctrl + f , 輸入 m-cvrlst , 如果不出意外

搜索結(jié)果

哈哈赠潦,這正是我們需要的,怎么知道 discover 頁(yè)面到底指向哪一個(gè) url 呢草冈,鼠標(biāo)懸浮到 discover 頁(yè)面上她奥,就可以看到 http://music.163.com/discover 的url (因?yàn)榻貓D截不到,所以年輕司機(jī)們要自己多多觀察一下了)怎棱,Get it !

有了 url 哩俭,我們就可以正式寫(xiě)我們的爬蟲(chóng)程序了,在我們的 NodeJs 項(xiàng)目中拳恋,新建 index.js

// 初始化 express
var app = require('express')();

/**
 * 開(kāi)啟路由
 * 第一個(gè)參數(shù)指定路由地址,當(dāng)前指向的是 localhost:3000/
 * 如果需要其他路由,可以這樣定義,比如 需要我們的獲取推薦歌單的路由 /recommendLst
 * app.get('/recommendLst', function(req, res){});
 */
app.get('/', function(req, res){

    // 向請(qǐng)求 localhost:3000/ 的地址返回 Hello World 字符串
    res.send('Hello World !');

});

/**
 * 開(kāi)啟express服務(wù),監(jiān)聽(tīng)本機(jī)3000端口
 * 第二個(gè)參數(shù)是開(kāi)啟成功后的回調(diào)函數(shù)
 */
var server = app.listen(3000, function(){
    // 如果 express 開(kāi)啟成功,則會(huì)執(zhí)行這個(gè)方法
    var port = server.address().port;

    console.log(`Express app listening at http://localhost:${port}`);
});
// 在控制臺(tái)執(zhí)行
node index
// 瀏覽器訪問(wèn)
http://localhost:3000/
node index
localhost:3000

恭喜凡资,第一個(gè)express Hello world 程序運(yùn)行成功,具體實(shí)現(xiàn)可以參考express官網(wǎng)給出的 Hello world 示例

使用 superagent 訪問(wèn) discover 頁(yè)面

到了這里谬运,相信簡(jiǎn)友對(duì)蟹棒的套路已經(jīng)輕車(chē)熟路了隙赁,不多說(shuō)了,上路

我們先用 superagent 訪問(wèn)我們的 localhost:3000 , 如果不出意外吩谦,我們獲得的正是 localhost:3000/ 返回的 Hello World 字符串

// 初始化 superagent 模塊
var request = require('superagent');

app.get('/test', function(req, res){

    request.get('http://localhost:3000/')
        .end(function(err, _response){

            if (!err) {

                // 如果獲取過(guò)程中沒(méi)有發(fā)生錯(cuò)誤
                var result = '獲取到的數(shù)據(jù):'+_response.text;

                console.log(result);
                res.send(result);


            } else {
                console.log('Get data error !');
            }

        });

});
// 我們?cè)谛薷姆?wù)端代碼之后一定要重啟服務(wù)才會(huì)看到效果
node index
// 瀏覽器訪問(wèn)
http://localhost:3000/test
控制臺(tái)界面
localhost:3000/test 界面

更多有關(guān)于 superagent 的 api 在 這里

接下來(lái)我們使用 superagent 的 get 函數(shù)來(lái)訪問(wèn) discover 頁(yè)面鸳谜,我們將開(kāi)放一個(gè) localhost:3000/recommendLst 的 api 返回推薦列表數(shù)據(jù)

// express 開(kāi)放 /recommendLst API
app.get('/recommendLst', function(req, res){

    // 使用 superagent 訪問(wèn) discover 頁(yè)面
    request.get('http://music.163.com/discover')
        .end(function(err, _response){

            if (!err) {

                // 請(qǐng)求成功
                var dom = _response.text;

                console.log(dom);
                res.send('get success');

            } else {
                console.log('Get data error !');
            }

        });

});
// 重新啟動(dòng)服務(wù)
node index
// 瀏覽器訪問(wèn)
http://localhost:3000/recommendLst

如果你的控制臺(tái)之中打印出如下界面 (截圖不完整)

控制臺(tái)輸出

到了這里證明請(qǐng)求 discover 頁(yè)面成功 !

使用 cheerio 處理返回的 HTML

一腳剎車(chē),蟹棒先休息一會(huì)式廷,簡(jiǎn)友們喘口氣喝口水咐扭,請(qǐng)站好扶穩(wěn),系好安全帶滑废,車(chē)子將再次啟動(dòng) .....

先使用 cherrio 來(lái)處理一下簡(jiǎn)單的 html 蝗肪, Look

// 加載 cheerio 模塊
var cheerio = require('cheerio');

app.get('/testCheerio', function(req, res){

    var $ = cheerio.load('<h1 id="test">這是一段示例文字</h1>');

    $('#test').css('color','red');

    res.send( $.html() );

});

照例

// 重新啟動(dòng)服務(wù)
node index
// 瀏覽器訪問(wèn)
http://localhost:3000/testCheerio

運(yùn)行結(jié)果如下

運(yùn)行結(jié)果

同樣,cheerio 的更多api請(qǐng)點(diǎn)擊 這里

現(xiàn)在使用 cheerio 來(lái)處理 superagent 請(qǐng)求過(guò)來(lái)的 HTML

// express 開(kāi)放 /recommendLst API
app.get('/recommendLst', function(req, res){

    // 初始化返回對(duì)象
    var resObj = {
        code: 200,
        data: []
    };

    // 使用 superagent 訪問(wèn) discover 頁(yè)面
    request.get('http://music.163.com/discover')
        .end(function(err, _response){

            if (!err) {

                // 請(qǐng)求成功
                var dom = _response.text;

                // 使用 cheerio 加載 dom
                var $ = cheerio.load(dom);
                // 定義我們要返回的數(shù)組
                var recommendLst = [];
                // 獲得 .m-cvrlst 的 ul 元素
                $('.m-cvrlst').eq(0).find('li').each(function(index, element){

                    // 獲得 a 鏈接
                    var cvrLink = $(element).find('.u-cover').find('a');
                    console.log(cvrLink.html());
                    // 獲得 cover 歌單封面
                    var cover = $(element).find('.u-cover').find('img').attr('src');
                    // 組織單個(gè)推薦歌單對(duì)象結(jié)構(gòu)
                    var recommendItem = {
                        id: cvrLink.attr('data-res-id'),
                        title: cvrLink.attr('title'),
                        href: 'http://music.163.com' + cvrLink.attr('href'),
                        type: cvrLink.attr('data-res-type'),
                        cover: cover
                    };
                    // 將單個(gè)對(duì)象放在數(shù)組中
                    recommendLst.push(recommendItem);

                });

                // 替換返回對(duì)象
                resObj.data = recommendLst;

            } else {
                resObj.code = 404;
                console.log('Get data error !');
            }

            // 響應(yīng)數(shù)據(jù)
            res.send( resObj );

        });

});

代碼很簡(jiǎn)單蠕趁,詳細(xì)擼完 superagent 和 cheerio 的使用方法的簡(jiǎn)友們不會(huì)被這段代碼嚇到薛闪,至此我們的獲取推薦首頁(yè)的API就已經(jīng)完成,我們可以看看請(qǐng)求效果俺陋,重啟服務(wù)器豁延,你懂的 !

訪問(wèn)結(jié)果

很復(fù)雜昙篙?不用緊張,蟹棒的車(chē)有保險(xiǎn)诱咏,哈哈苔可,簡(jiǎn)友們需要 Chrome 擴(kuò)展程序JSONView (Chrome 應(yīng)用商店,自備梯子袋狞,免費(fèi)的就用 Lantern焚辅,滿足簡(jiǎn)單的需求)

裝完 JSONView 的效果

回頭看看我們定義過(guò)的單個(gè)推薦列表的對(duì)象結(jié)構(gòu),這樣看是不是很簡(jiǎn)單明了苟鸯,我們?cè)賮?lái)看看訪問(wèn)失敗的情況同蜻,斷掉網(wǎng)絡(luò)

訪問(wèn)失敗的情況

舉一反三,根據(jù)歌單id獲得歌單詳細(xì)信息

這個(gè)就需要我們重新研究dom結(jié)構(gòu)早处,相信經(jīng)過(guò)上面的過(guò)程湾蔓,簡(jiǎn)友們對(duì)這個(gè)已經(jīng)信心滿滿了,上車(chē)砌梆,出發(fā)

回到網(wǎng)易云首頁(yè)卵蛉,點(diǎn)擊任意一個(gè)歌單

需要的資源信息

從圖中,我們可以分析出么库,我們需要的資源有上面蟹棒括起來(lái)的所有信息傻丝,按照這樣,我們可以規(guī)定一個(gè)歌單的詳細(xì)信息對(duì)象結(jié)構(gòu)

{
    id: '歌單ID',
    title: '歌單名字',
    owner: '歌單的擁有人姓名,初級(jí)階段只考慮用戶名,不考慮用戶詳細(xì)信息',
    create_time: '創(chuàng)建時(shí)間',
    collection_count: '歌單被收藏?cái)?shù)量',
    share_count: '歌單被分享數(shù)量',
    comment_count: '評(píng)論數(shù)量',
    tags: ['標(biāo)簽'],
    desc: '歌單描述',
    song_count: '歌曲總數(shù)量',
    play_count: '總播放次數(shù)'
}

相信簡(jiǎn)友已經(jīng)有能力把這些數(shù)據(jù)一個(gè)個(gè)扒出來(lái)了诉儒,我們先來(lái)定義API

// 定義根據(jù)歌單id獲得歌單詳細(xì)信息的API
app.get('/playlist/:playlistId', function(req, res){

    var playlistId = req.params.playlistId;
    res.send(playlistId);

});

這種API定義方式是 express 的動(dòng)態(tài)參數(shù)匹配葡缰,/:playlistId 將匹配你輸入的動(dòng)態(tài)參數(shù),看看使用方式忱反,不要忘了重啟服務(wù)器哦

瀏覽器訪問(wèn)

效果很明顯泛释,我們將通過(guò)這種方式獲得需要獲取詳細(xì)信息的歌單ID,具體怎么找元素在哪一個(gè)位置温算,蟹棒就不帶著簡(jiǎn)友們做了怜校,相信簡(jiǎn)友們看了上面的教程,應(yīng)該對(duì)這個(gè)很熟悉了注竿,蟹棒就直接貼源碼了茄茁,蟹棒友情提醒,注意注釋

(Warning....車(chē)速正在提升巩割,請(qǐng)站好扶穩(wěn)裙顽,系好安全帶.....)

// 定義根據(jù)歌單id獲得歌單詳細(xì)信息的API
app.get('/playlist/:playlistId', function(req, res){

    // 獲得歌單ID
    var playlistId = req.params.playlistId;
    // 定義返回對(duì)象
    var resObj = {
        code: 200,
        data: {}
    };

    /**
     * 使用 superagent 請(qǐng)求
     * 在這里我們?yōu)槭裁匆?qǐng)求 http://music.163.com/playlist?id=${playlistId}
     * 簡(jiǎn)友們應(yīng)該還記得 網(wǎng)易云音樂(lè)首頁(yè)的 iframe
     * 應(yīng)該還記得去打開(kāi) 調(diào)試面板的 Sources 選項(xiàng)卡
     * 那么就可以看到在歌單頁(yè)面 iframe 到底加載了什么 url
     */
    request.get(`http://music.163.com/playlist?id=${playlistId}`)
        .end(function(err, _response){

            if (!err) {
                // 定義歌單對(duì)象
                var playlist = {
                    id: playlistId
                };

                // 成功返回 HTML, decodeEntities 指定不把中文字符轉(zhuǎn)為 unicode 字符
                // 如果不指定 decodeEntities 為 false , 例如 " 會(huì)解析為 "
                var $ = cheerio.load(_response.text,{decodeEntities: false});
                // 獲得歌單 dom
                var dom = $('#m-playlist');
                // 歌單標(biāo)題
                playlist.title = dom.find('.tit').text();
                // 歌單擁有者
                playlist.owner = dom.find('.user').find('.name').text();
                // 創(chuàng)建時(shí)間
                playlist.create_time =  dom.find('.user').find('.time').text();
                // 歌單被收藏?cái)?shù)量
                playlist.collection_count = dom.find('#content-operation').find('.u-btni-fav').attr('data-count');
                // 分享數(shù)量
                playlist.share_count = dom.find('#content-operation').find('.u-btni-share').attr('data-count');
                // 評(píng)論數(shù)量
                playlist.comment_count = dom.find('#content-operation').find('#cnt_comment_count').html();
                // 標(biāo)簽
                playlist.tags = [];
                dom.find('.tags').eq(0).find('.u-tag').each(function(index, element){
                    playlist.tags.push($(element).text());
                });
                // 歌單描述
                playlist.desc = dom.find('#album-desc-more').html();
                // 歌曲總數(shù)量
                playlist.song_count = dom.find('#playlist-track-count').text();
                // 播放總數(shù)量
                playlist.play_count = dom.find('#play-count').text();

                resObj.data = playlist;

            } else {
                resObj.code = 404 ;
                console.log('Get data error!');
            }

            res.send( resObj );

        });


});

執(zhí)行結(jié)果(依然是重啟服務(wù)器,然后瀏覽器訪問(wèn))

瀏覽器訪問(wèn)結(jié)果

年輕司機(jī)們輕輕搖了搖要昏掉的腦袋冒出一個(gè)問(wèn)題宣谈,我們?yōu)槭裁床恢苯釉谶@個(gè)接口中將歌單的所有歌曲也加載進(jìn)去呢愈犹,哈哈,如果簡(jiǎn)友們認(rèn)真看過(guò)iframe加載成功后的html文件的話闻丑,就不會(huì)有這個(gè)問(wèn)題了漩怎,我們一起看看這一塊

打開(kāi)調(diào)試窗口(F12)勋颖,點(diǎn)擊Sources選項(xiàng)卡,點(diǎn)擊contentFrame子節(jié)點(diǎn)勋锤,點(diǎn)擊playlist開(kāi)頭的文件牙言,文件結(jié)構(gòu)如下

iframe頁(yè)面

我們能看到在頁(yè)面已經(jīng)返回的情況下歌曲的table是沒(méi)有渲染出來(lái)的,在仔細(xì)看看頁(yè)面怪得,刷新的過(guò)程中能看到歌曲列表位置出現(xiàn)“加載中..”中的 loading 提示,但是卑硫,Look徒恋,這是什么

Look

蟹棒把這部分內(nèi)容Copy出來(lái)做了一下 Json解析

解析結(jié)果

很明顯,這正是我們需要的東西欢伏,但是這個(gè)JSON字符串的長(zhǎng)度非常長(zhǎng)入挣,如果我們要把它放在請(qǐng)求播放列表詳細(xì)信息處理的話,會(huì)非常耗時(shí)硝拧,蟹棒大概對(duì)該JSON字符串執(zhí)行了下面幾句代碼

// 當(dāng)前操作耗時(shí) 1s
var str = JSON.stringify('拷貝過(guò)來(lái)的字符串');
// 當(dāng)前操作耗時(shí) 1s
console.log(str.length);  // 75151
// 當(dāng)前步驟耗時(shí) 2s
var str = JSON.parse(str);

再加上我們肯定要對(duì)該JSON字符串做相應(yīng)的邏輯處理径筏,所以速度只會(huì)更慢,更好的辦法是在播放列表加載完成之后異步加載播放列表中的所有歌曲障陶,而我們要做的僅僅是返回整個(gè)JSON字符串滋恬,邏輯解析的問(wèn)題就交給前端Js代碼來(lái)處理,蟹棒再次貼一段代碼

// 定義根據(jù)歌單id獲得歌單所有歌曲列表的API
app.get('/song_list/:playlistId', function(req, res){

    // 獲得歌單ID
    var playlistId = req.params.playlistId;
    // 定義返回對(duì)象
    var resObj = {
        code: 200,
        data: []
    };

    request.get(`http://music.163.com/playlist?id=${playlistId}`)
        .end(function(err, _response){

            if (!err) {

                // 成功返回 HTML
                var $ = cheerio.load(_response.text,{decodeEntities: false});
                // 獲得歌單 dom
                var dom = $('#m-playlist');

                resObj.data = JSON.parse( dom.find('#song-list-pre-cache').find('textarea').html() );

            } else {
                resObj.code = 404 ;
                console.log('Get data error!');
            }

            res.send( resObj );

        });


});

重啟服務(wù)器抱究,瀏覽器的訪問(wèn)結(jié)果(蟹棒友情提示恢氯,如果電腦配置不是很好,請(qǐng)把JSONView擴(kuò)展程序禁用再瀏覽網(wǎng)頁(yè)鼓寺,否則瀏覽器可能當(dāng)機(jī))

瀏覽器訪問(wèn)結(jié)果

總結(jié)

好啦勋拟,目的地距離不遠(yuǎn),蟹棒的車(chē)也開(kāi)始減速了妈候,簡(jiǎn)友們能看到這里的敢靡,蟹棒感謝你們坐這趟車(chē),也希望蟹棒的文章可以幫到你苦银,我們一起來(lái)回顧一下所編寫(xiě)成功的API

  • 訪問(wèn)推薦歌單API
http://localhost:3000/recommendLst
  • 訪問(wèn)歌單詳細(xì)信息API
http://localhost:3000/playlist/:playlistId
  • 訪問(wèn)歌單所有歌曲列表的API
http://localhost:3000/song_list/:playlistId

蟹棒用挺長(zhǎng)的篇幅不僅細(xì)致分析了這三個(gè)API的邏輯思路啸胧,而且向簡(jiǎn)友們?cè)敿?xì)介紹了如何去分析一個(gè)網(wǎng)站的dom結(jié)構(gòu)并使用代碼來(lái)爬取數(shù)據(jù),是一篇API教程的同時(shí),又是一片NodeJs爬蟲(chóng)的新手教學(xué)(王婆賣(mài)瓜匈勋,自賣(mài)自夸镣典,哈哈,再裝一會(huì)....)柠辞,簡(jiǎn)友們有問(wèn)題歡迎給蟹棒留言或者私信,蟹棒看到即會(huì)回復(fù)

源碼

源碼蟹棒放到了 Github WangyiyunAPI主胧,以后蟹棒會(huì)持續(xù)更新API叭首,如果對(duì)你有幫助习勤,請(qǐng)給蟹棒一個(gè)Star,謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焙格,一起剝皮案震驚了整個(gè)濱河市图毕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌眷唉,老刑警劉巖予颤,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異冬阳,居然都是意外死亡蛤虐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)肝陪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)驳庭,“玉大人,你說(shuō)我怎么就攤上這事氯窍∷浅#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵狼讨,是天一觀的道長(zhǎng)贝淤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)政供,這世上最難降的妖魔是什么霹娄? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮鲫骗,結(jié)果婚禮上犬耻,老公的妹妹穿的比我還像新娘。我一直安慰自己执泰,他們只是感情好枕磁,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著术吝,像睡著了一般计济。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上排苍,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天沦寂,我揣著相機(jī)與錄音,去河邊找鬼淘衙。 笑死传藏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毯侦,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼哭靖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了侈离?” 一聲冷哼從身側(cè)響起试幽,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卦碾,沒(méi)想到半個(gè)月后铺坞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洲胖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年济榨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宾濒。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖屏箍,靈堂內(nèi)的尸體忽然破棺而出绘梦,到底是詐尸還是另有隱情,我是刑警寧澤赴魁,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布卸奉,位于F島的核電站,受9級(jí)特大地震影響颖御,放射性物質(zhì)發(fā)生泄漏榄棵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一潘拱、第九天 我趴在偏房一處隱蔽的房頂上張望疹鳄。 院中可真熱鬧,春花似錦芦岂、人聲如沸瘪弓。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)腺怯。三九已至,卻和暖如春川无,著一層夾襖步出監(jiān)牢的瞬間呛占,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工懦趋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晾虑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像走贪,于是被迫代替她去往敵國(guó)和親佛猛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,283評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)坠狡、插件继找、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,119評(píng)論 4 61
  • 接手職高二2班班主任至今整一周.這一周真可謂是忙了個(gè)不亦樂(lè)乎.多了一份責(zé)任,多了一分愛(ài)心.現(xiàn)在自己的工作量可以說(shuō)是...
    開(kāi)宗明義閱讀 321評(píng)論 0 1
  • 中秋月,月到中秋偏皎潔逃沿。偏皎潔婴渡,知他多少,陰晴圓缺凯亮。陰晴圓缺都休說(shuō)边臼,且喜人間好時(shí)節(jié)。好時(shí)節(jié)假消,愿得年年柠并,常見(jiàn)中秋月。...
    騎驢找馬追駱駝的獅子狗閱讀 700評(píng)論 3 1
  • 以前見(jiàn)過(guò)一個(gè)穿了很多內(nèi)褲 然后再眾人面前不停脫掉的瘋子 當(dāng)時(shí)五六歲 很害怕 這個(gè)世界上為什么有瘋子的存在 如果現(xiàn)在...
    Jade忘言閱讀 247評(píng)論 3 1