說(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è)
蟹棒的目標(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)如下
使用快捷鍵 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 上
在 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');
結(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 元素
此刻蟹棒車(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');
哈哈落剪,這才是我們需要看到的嘛,年輕司機(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
不好意思稠肘,車(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í)候再具體解釋
好了,系好安全帶巴粪,我們要如何獲得 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)
這里會(huì)列出當(dāng)前網(wǎng)頁(yè)加載的所有資源派哲,我們可以看到在列表的最后一項(xiàng)臼氨,存在一個(gè)名為 contentFrame 的子節(jié)點(diǎn),點(diǎn)擊打開(kāi)此節(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)
我們看到 discover 頁(yè)面同樣是加載了一個(gè) html 文檔洁仗,可是這個(gè)文檔是不是我們需要的文檔呢?驗(yàn)證看看性锭,在 discover 中按下快捷鍵 Ctrl + f , 輸入 m-cvrlst , 如果不出意外
哈哈赠潦,這正是我們需要的,怎么知道 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/
恭喜凡资,第一個(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
更多有關(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)之中打印出如下界面 (截圖不完整)
到了這里證明請(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é)果如下
同樣,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ù)器豁延,你懂的 !
很復(fù)雜昙篙?不用緊張,蟹棒的車(chē)有保險(xiǎn)诱咏,哈哈苔可,簡(jiǎn)友們需要 Chrome 擴(kuò)展程序JSONView (Chrome 應(yīng)用商店,自備梯子袋狞,免費(fèi)的就用 Lantern焚辅,滿足簡(jiǎn)單的需求)
回頭看看我們定義過(guò)的單個(gè)推薦列表的對(duì)象結(jié)構(gòu),這樣看是不是很簡(jiǎn)單明了苟鸯,我們?cè)賮?lái)看看訪問(wèn)失敗的情況同蜻,斷掉網(wǎng)絡(luò)
舉一反三,根據(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ù)器哦
效果很明顯泛释,我們將通過(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))
年輕司機(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)如下
我們能看到在頁(yè)面已經(jīng)返回的情況下歌曲的table是沒(méi)有渲染出來(lái)的,在仔細(xì)看看頁(yè)面怪得,刷新的過(guò)程中能看到歌曲列表位置出現(xiàn)“加載中..”中的 loading 提示,但是卑硫,Look徒恋,這是什么
蟹棒把這部分內(nèi)容Copy出來(lái)做了一下 Json解析
很明顯,這正是我們需要的東西欢伏,但是這個(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ī))
總結(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,謝謝