文章概要
使用nodejs + cheerio + Promise(bluebird庫實(shí)現(xiàn))的nodejs課程數(shù)據(jù)進(jìn)行爬取。統(tǒng)計(jì)scott老師的所有課程的情況:每個(gè)課程的課程名装盯、課程介紹秽誊、鏈接地址、課程難度等級莹规。
關(guān)于cheerio的使用
- 請參考我的另外一篇文章 《cheerio 使用初步》
也可以參考我的另外一篇爬蟲實(shí)踐文章
準(zhǔn)備
- 對應(yīng)的DOM結(jié)構(gòu)如下
-
我希望的到的是如下
課程名稱: Node.js 異步優(yōu)化 課程介紹:本課程作為 Node.js 進(jìn)階提升系列的第一課,主要講解 Node.js 的異步代碼編程習(xí)慣以及異步代碼編程會帶來的潛在問題。通過本課程的學(xué)習(xí)窑滞,學(xué)員將學(xué)會如何將 Node.js 的異步代碼進(jìn)行改良優(yōu)化。 課程鏈接:4510人學(xué)習(xí) 學(xué)習(xí)人數(shù):http://www.jikexueyuan.com/course/2052.html
代碼 crawler.js
var http = require('http');
var cheerio = require('cheerio');
var Promise = require('bluebird');
// baseUrl + videoIds 是一個(gè)課程的
var baseUrl = 'http://www.imooc.com/learn/';
var videoIds = [348, 259, 197, 134, 75];
function filterHtml(html){
var $ = cheerio.load(html);// 使用 cheerio模塊裝載課程頁面,使用類似jquery方式處理
var course_infos = $('.course-infos');//課程信息
var title = course_infos.find('.hd').find('.l').text().trim();//課程標(biāo)題
var number = $($('.static-item.l')[1]).find('span').last().text().trim();//課程難度等級
var chapters = $('.chapter');//章節(jié)(每個(gè)章節(jié)包含若干小節(jié))
//期望返回的數(shù)據(jù)結(jié)構(gòu)哀卫,將每個(gè)html頁面處理成這個(gè)字面量對象
/*var courseData = {
title: title,//課程名稱
number: number,//課程學(xué)習(xí)人數(shù)
//課程的每個(gè)章節(jié):章節(jié)名稱巨坊,小章節(jié)數(shù)組。小章節(jié):小章節(jié)名稱此改,小章節(jié)鏈接
videos:[{
chapterTitle: '',
videos: [
title:'',
id:''
]
}]
}*/
var courseData = {
videos: [],
number: number,
title: title
}
//遍歷每個(gè)章節(jié)
chapters.each(function(item) {
var chapter = $(this);
var chapterTitle = chapter.find('strong').text().trim();//每個(gè)章節(jié)的標(biāo)題
var videos = chapter.find('.video').children('li');//每個(gè)小章節(jié)DOM(數(shù)組)
var chapterData = {
chapterTitle : chapterTitle,
videos: []
}
videos.each(function(item) {
var video = $(this).find('.J-media-item');//每個(gè)小節(jié)的a標(biāo)簽
var videoTitle = video.text().trim();
var id = video.attr('href').split('video/')[1].trim();
chapterData.videos.push({
title: videoTitle,
id:id
})
})
courseData.videos.push(chapterData)
})
return courseData;
}
function printInfo(info) {
info.forEach(function(item){
console.log(item.number + ' 人學(xué)過 ' + item.title + '\r\n');
});
info.forEach(function(courseData) {
console.log('###' + courseData.title + '\n')
courseData.videos.forEach(function(item) {
var chapterTitle = item.chapterTitle;
console.log(chapterTitle + '\r\n');
item.videos.forEach(function(video) {
var subtext = '【' + video.id +'】' + video.title
console.log(subtext)
})
})
})
}
var fetchCourseArray = [];
videoIds.forEach(function(id) {
fetchCourseArray.push(getPageAsync(baseUrl + id));
})
function getPageAsync(url) {
return new Promise(function(resolve, reject){
console.log('正在爬取....');
http.get(url, function(res){
var html = '';
res.on('data', function(data) {
html += data;
})
res.on('end', function(){
resolve(html);
})
}).on('error', function(e) {
reject(e)
console.log('出錯(cuò)了')
})
})
}
Promise
.all(fetchCourseArray)
.then(function(pages) {
var coursesData = [];//很多課程的數(shù)組(之前是一個(gè)課程里許多個(gè)章節(jié)的數(shù)組)
pages.forEach(function(html){
var courses = filterHtml(html);
coursesData.push(courses);
})
coursesData.sort(function(a, b){
return a.number < b.number;//從大到小
})
printInfo(coursesData)
})
拉出啦溜溜
執(zhí)行
npm install --save cheerio bluebird
node crawler.js
問題
我最開始想爬取慕課網(wǎng)某個(gè)課程的學(xué)習(xí)人數(shù)
DOM結(jié)構(gòu)如下
“上次學(xué)到”趾撵、“學(xué)習(xí)人數(shù)”等四塊內(nèi)容,分別存儲在四個(gè)className為static-item的DIV中共啃。但是通過數(shù)據(jù)抓取占调,我發(fā)現(xiàn),抓出的數(shù)據(jù)“上次學(xué)到”等標(biāo)題是亂碼勋磕,然后具體的內(nèi)容如“1-1課程簡介”等是空
var number = $('.statics ').find('.static-item').html();
也許是我的這次行為被慕課網(wǎng)發(fā)現(xiàn)了妈候,觸發(fā)了自動“反扒”機(jī)制,這個(gè)機(jī)制和怎么規(guī)避挂滓,目前苦银,我還不了解,后面的文章赶站,我會繼續(xù)填坑~