文章概要
使用nodejs + cheerio + Promise(bluebird库实现)的nodejs课程数据进行爬取。统计scott老师的所有课程的情况:每个课程的课程名、课程介绍、链接地址、课程难度等级。
关于cheerio的使用
- 请参考我的另外一篇文章 《cheerio 使用初步》
也可以参考我的另外一篇爬虫实践文章
准备
- 对应的DOM结构如下
-
我希望的到的是如下
课程名称: Node.js 异步优化 课程介绍:本课程作为 Node.js 进阶提升系列的第一课,主要讲解 Node.js 的异步代码编程习惯以及异步代码编程会带来的潜在问题。通过本课程的学习,学员将学会如何将 Node.js 的异步代码进行改良优化。 课程链接:4510人学习 学习人数:http://www.jikexueyuan.com/course/2052.html
代码 crawler.js
var http = require('http');
var cheerio = require('cheerio');
var Promise = require('bluebird');
// baseUrl + videoIds 是一个课程的
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();//课程标题
var number = $($('.static-item.l')[1]).find('span').last().text().trim();//课程难度等级
var chapters = $('.chapter');//章节(每个章节包含若干小节)
//期望返回的数据结构,将每个html页面处理成这个字面量对象
/*var courseData = {
title: title,//课程名称
number: number,//课程学习人数
//课程的每个章节:章节名称,小章节数组。小章节:小章节名称,小章节链接
videos:[{
chapterTitle: '',
videos: [
title:'',
id:''
]
}]
}*/
var courseData = {
videos: [],
number: number,
title: title
}
//遍历每个章节
chapters.each(function(item) {
var chapter = $(this);
var chapterTitle = chapter.find('strong').text().trim();//每个章节的标题
var videos = chapter.find('.video').children('li');//每个小章节DOM(数组)
var chapterData = {
chapterTitle : chapterTitle,
videos: []
}
videos.each(function(item) {
var video = $(this).find('.J-media-item');//每个小节的a标签
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 + ' 人学过 ' + 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('出错了')
})
})
}
Promise
.all(fetchCourseArray)
.then(function(pages) {
var coursesData = [];//很多课程的数组(之前是一个课程里许多个章节的数组)
pages.forEach(function(html){
var courses = filterHtml(html);
coursesData.push(courses);
})
coursesData.sort(function(a, b){
return a.number < b.number;//从大到小
})
printInfo(coursesData)
})
拉出啦溜溜
执行
npm install --save cheerio bluebird
node crawler.js
问题
我最开始想爬取慕课网某个课程的学习人数
DOM结构如下
“上次学到”、“学习人数”等四块内容,分别存储在四个className为static-item的DIV中。但是通过数据抓取,我发现,抓出的数据“上次学到”等标题是乱码,然后具体的内容如“1-1课程简介”等是空
var number = $('.statics ').find('.static-item').html();
也许是我的这次行为被慕课网发现了,触发了自动“反扒”机制,这个机制和怎么规避,目前,我还不了解,后面的文章,我会继续填坑~