node下利用Promise更优雅的写网站爬虫

上篇文章《基于node下的http小爬虫》我们简单的介绍了如何爬取菜鸟教程中node.js这一节课程的章节目录。今天我们再一次来爬爬菜鸟教程的官网,获取官网中所有大课程及大课程下的分类课程以及分类课程下的章节目录。这个工程有点大呀!只能我们一起加油咯。


开始之前呢,我们先去菜鸟教程的官网首页看看我们需要做的大概是个什么样子:

  1. 获取首页中左侧全部课程信息
  2. 获取全部课程信息中每个课程下的分类课程信息
  3. (点击分类课程进入当前课程的详细课程中),获取当前课程下的章节目录
    如果用JSON来映照这个需求大概是这个样子(这里我们举一段例子来直观感受一下):
[
    {
        title:'网站建设',
        subTitle:'网站品质',
        url:'quality/quality-tutorial.html',
        subChapter:[网站品质教程,Web 品质标准,Web 品质 - 重要的HTML元素,Web 品质样式表,Web 品质可读性,Web 无障碍(WAI),Web 品质国际化]
    }
]

明确任务需求后,就是准备工作

  1. 查看当前版本的node是否自带promise(进入一个终端命令中查看)
G:\node> node
> Promise
[Function: Promise]
>

如上终端打印可知我的node中有promise,如果你们的没有的话可以install bluebird,通过bluebird来引入,在《node中的Promisse》一文中有介绍。

2.我们还需要cheerio,这个在 《基于node下的http小爬虫》也有介绍

3.有Promise的简单概念《node中的Promisse》有简单运用介绍


接下来就直接从代码中通过注释讲解吧

var http = require('http');//获取http模块
var Promise = require('bluebird');//引入Promise模块
var cheerio = require('cheerio');//引入cheerio模块
var baseUrl = 'http://www.runoob.com';//定义node官网地址变量

var resultData = [];//{title:'',moreTitle:[{subTitle:'',url:''}]}
var data = [];//所有分类课程下章节目录

// filer 全部课程,分类课程及分类课程的链接
function filerAllChapter(html) {
    // 将爬取得HTML装载起来
    var $ = cheerio.load(html);
    // 拿到全部课程
    var courses = $('.codelist.codelist-desktop');
    //这里我希望我能获取的到的最终数据格式这个样子的,如此我们能知道主课程分类,分类课程及分类课程地址
    /**
     * {title:'',moreTitle:[{subTitle:'',url:''}]}
     */   
    var allCourseArray = [];
    courses.each(function (item) {
        // 获取主课程
        var title = $(this).find('h2').text();   

        // 分类课程
        var moreTitle = [];
        var subCourse = $(this).find('.item-top.item-1');

        subCourse.each(function (sub) {
            var subTitle = $(this).find('h4').text();
            var url = $(this).attr('href');
            moreTitle.push({
                subTitle: subTitle,
                url: url
            })
        })

        resultData.push({
            title: title,
            moreTitle: moreTitle
        })
    })

    return resultData;

}

// 分类课程下的章节目录
function filerSubChapter(html) {
    // 将爬取得HTML装载起来
    var $ = cheerio.load(html);
    // 拿到左侧边栏的每个目录
    var nodeChapter = $('#leftcolumn a');
    var chapterData = [];
    nodeChapter.each(function (item) {
        // 获取每项的地址及标题
        var title = $(this).text().trim();
        chapterData.push(title)
    })

    return chapterData;
}

// 打印过滤全课程,每个课程下的分类课程及分类课程下的目录
function getChapterData(resultData) {
    resultData.forEach(function (item) {
        console.log('\n');
        console.log("全课程 : 【 " + item.title + " 】");
        console.log('\n');
        item.moreTitle.forEach(function (item1) {
            console.log('-----------'+ item1.subTitle +'--------------');
            item1.subChapter.forEach(function (item2) {
                console.log(item2)
            })
        })
    });
}

// 利用Promise来封装get请求,promise的好处就在于比回调易维护,易理解,链式操作开发也快捷
function getPageAsync(url) {
    return new Promise(function (resolve, reject) {
        
        console.log('正在爬取 ' + url)

        http.get(url, function (res) {
            var html = '';

            // 这里将会触发data事件,不断触发不断跟新html直至完毕
            res.on('data', function (data) {
                html += data
            })

            // 当数据获取完成将会触发end事件,这里将会打印初菜鸟教程官网的html
            res.on('end', function () {
                // 这里是promise的回调函数,我们通过resolve将获取的html传递下去
                resolve(html)
            })
        }).on('error', function (e) {
            // 如果爬取出错,则通过reject返回
            reject(e)
            console.log('获取node官网相关数据出错')
        })
    })
}

// 获取分类课程的链接地址
var allCourseArray = [];
var urls = [];
getPageAsync(baseUrl).then(function (pages) {
    // pages及为菜鸟教程首页的HTML

    // {title:'',moreTitle:[{subTitle:'',url:''}]}
    var courses = filerAllChapter(pages);

    // 将分类课程的地址取出来
    courses.forEach(function (item) {
        item.moreTitle.forEach(function (item1) {
            urls.push(item1.url.split('//www.runoob.com')[1]);
        })
    })

    // 访问每个分类课程,将每个分类课程的信息放入allCourseArray数组
    urls.forEach(function (path) {
        allCourseArray.push(getPageAsync(baseUrl + path))
    })

}).then(function(){
    // 并发处理每个分类课程
    Promise.all(allCourseArray)
        .then(function (pages) {
            // pages所有分类课程下的页面html
            pages.forEach(function (html) {
                // 获取分类课程下章节目录
                data.push(filerSubChapter(html))
            })

        })
        .then(function () {
            for (var i = 0; i < resultData.length; i++) {
                if(i>=1){
                    data.splice(0,resultData[i-1].moreTitle.length);
                }
                for (var j = 0; j < resultData[i].moreTitle.length; j++) {
                    resultData[i].moreTitle[j]['subChapter'] = data[j]
                }
            }
           getChapterData(resultData)
        })

})

终端打印结果如下:(因为信息较多这里只显示一小部分)

G:\node> node node-http/node-http-promise.js
正在爬取 http://www.runoob.com
正在爬取 http://www.runoob.com/html/html-tutorial.html
正在爬取 http://www.runoob.com/html/html5-intro.html
正在爬取 http://www.runoob.com/css/css-tutorial.html
正在爬取 http://www.runoob.com/css3/css3-tutorial.html
正在爬取 http://www.runoob.com/bootstrap/bootstrap-tutorial.html
正在爬取 http://www.runoob.com/bootstrap4/bootstrap4-tutorial.html
正在爬取 http://www.runoob.com/font-awesome/fontawesome-tutorial.html
正在爬取 http://www.runoob.com/foundation/foundation-tutorial.html
正在爬取 http://www.runoob.com/js/js-tutorial.html
正在爬取 http://www.runoob.com/htmldom/htmldom-tutorial.html
正在爬取 http://www.runoob.com/jquery/jquery-tutorial.html
正在爬取 http://www.runoob.com/angularjs/angularjs-tutorial.html
正在爬取 http://www.runoob.com/angularjs2/angularjs2-tutorial.html
正在爬取 http://www.runoob.com/vue2/vue-tutorial.html
正在爬取 http://www.runoob.com/react/react-tutorial.html
正在爬取 http://www.runoob.com/jqueryui/jqueryui-tutorial.html


全课程 : 【  HTML / CSS 】


-----------【学习 HTML】--------------
HTML 教程
HTML 简介
HTML 编辑器
HTML 基础
HTML 元素
HTML 属性
HTML 标题
HTML 段落
HTML 文本格式化
HTML 链接
HTML 头部

。。。。。。。。。。。。。

如此我们就将菜鸟教程官网的全部课程,分类课程及分类课程下的章节目录都对应打印出来了。爬虫可以做很多有趣的事情,大家不妨去试一试,关于爬虫我就介绍到这里。

原文地址 https://gitee.com/wangFengJ/node/blob/master/node-http/node-http-promise.js

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,900评论 25 707
  • 如果你还在北国 我想,你会在那个日暮西垂的黄昏看到我 在村口的石子路旁 我采下一朵紫色的酒花, 贪婪而又不舍的吮吸...
    呵呵阿斌阅读 300评论 0 0
  • 0 序言 笑来老师总结过区块链世界未来的三个方向:发行、交易、基建。 单说基建里的挖矿,最近在挖QYB,一台笔记本...
    时汝佳阅读 2,180评论 0 1
  • 蔚蓝色的天空下,你随风而起,慢慢的越来越远,这时你似乎并不满足现状,你发现天空离你还是那么遥远,你想站在白云之...
    月光下的身影阅读 78评论 2 1
  • 在之前的《Hello App》一文中,总结了APP在不同的技术框架下可以分为三种:Native App(原生应用)...
    哈噗HelloApp阅读 1,680评论 3 31