Node.js 实现爬虫(2) —— 多页面的爬虫程序

多页面的爬虫程序

在上一篇“简单的爬虫程序”中,我们写了一个对一个单页面进行爬虫的程序,今天,我们将实现对多个页面进行爬虫,也会使用到大名鼎鼎的promise。

Step1:获取多个页面的url

选取CSDN博客的排行榜中的博客周排行,获取每一条的url,为之后的爬虫做准备。
初始的url:http://blog.csdn.net/ranking.html
多个页面的url数组:urlArr[url1,url2,……,url10]

var http = require('http')
var cheerio = require('cheerio')
var url = 'http://blog.csdn.net/ranking.html'

function filterRankUrl(html){
    var $ = cheerio.load(html)     //加载html内容

    var blogRank = $('.rankList .ranking').eq(1).find('li')
    var blogRankUrl = []

    blogRank.each(function(item){
        var blogRankItem = $(this)
        var url = blogRankItem.find('a.blog_a').attr('href')
        blogRankUrl.push(url)
    })
    return blogRankUrl
}

//打印url数组
function printUrlInfo(blogRankData){
    blogRankData.forEach(function(item){
        console.log(item)
    })
}

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

    res.on('data',function(data){
        html+=data;
    })

    res.on('end',function(){
        var urlArr = filterRankUrl(html)        //获取url数组
        printUrlInfo(urlArr)
    })
}).on('error',function(){
    console.log('获取数据出错');
})

运行结果截图
Node.js 实现爬虫(2) —— 多页面的爬虫程序_第1张图片

Step2:顺序爬取url数组中的每个页面

下面使用回调函数,在获取url数组后顺序的访问每个页面的内容

每个页面的数据组成形式如下:

{
    author : author,
    blogs : [{
        title:title,
        description : description,
        time : time,
        view : view,
        comments :comments
    }]
}
var http = require('http')
var cheerio = require('cheerio')
var url = 'http://blog.csdn.net/ranking.html'

//过滤排行榜页面中“博客周排行”模块的url
function filterRankUrl(html){
    var $ = cheerio.load(html)     //加载html内容

    var blogRank = $('.rankList .ranking').eq(1).find('li')
    var blogRankUrl = []

    blogRank.each(function(item){
        var blogRankItem = $(this)
        var url = blogRankItem.find('a.blog_a').attr('href')
        blogRankUrl.push(url)
    })
    return blogRankUrl
}

//过滤每个URL数组对应页面的文章
function filterArticle(html){

    var $ = cheerio.load(html)

    var blogData = {}

    //获取博主
    var author = $('#panel_Profile .user_name').text()
    blogData.author = author

    //获取文章信息
    var blogAtricle = $('.list_item_new .list_item')
    var blogs = []
    blogAtricle.each(function(item){
        var blogItem = $(this)
        var blogItemData = {}
        var title = blogItem.find('.link_title').text().trim()
        var description = blogItem.find('.article_description').text().trim()
        var time = blogItem.find('.article_manage .link_postdate').text().trim()
        var view = blogItem.find('.article_manage .link_view').text().trim()
        var comments = blogItem.find('.article_manage .link_comments').text().trim()
        blogItemData.title = title
        blogItemData.description = description
        blogItemData.time = time
        blogItemData.view = view
        blogItemData.comments = comments

        blogs.push(blogItemData)
    })
    blogData.blogs = blogs
    return blogData

}

//打印url数组
function printUrlInfo(blogRankData){
    blogRankData.forEach(function(item){
        console.log(item)
    })
}

//打印每个页面的信息
function printArticleInfo(blogData){
    console.log("----博主 :  "+blogData.author+"----\n")
    console.log("爬取文章数 :  "+blogData.blogs.length+"\n")
    blogData.blogs.forEach(function(item){
        console.log("文章名:"+item.title+'\n')
        console.log("文章描述:"+item.description+'\n')
        console.log("发表时间:"+item.time+'\n')
        console.log("阅读:"+item.view+'\n')
        console.log("评论:"+item.comments+'\n')

        console.log("\n--------------------\n\n")
    })
}

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

    res.on('data',function(data){
        html+=data;
    })

    res.on('end',function(){
        var urlArr = filterRankUrl(html)        //获取url
        printUrlInfo(urlArr)
        //使用回调函数,顺序的访问url数组中的页面
        http.get(urlArr[0],function(res){
            var html = '';
            res.on('data',function(data){
                html+=data;
            })

            res.on('end',function(){
                var blogData = filterArticle(html)
                printArticleInfo(blogData)
                //使用回调函数,顺序的访问url数组中的下一个页面
                http.get(urlArr[1],function(res){
                    var html = '';
                    res.on('data',function(data){
                        html+=data;
                    })

                    res.on('end',function(){
                        var blogData = filterArticle(html)
                        printArticleInfo(blogData)
                    })
                }).on('error',function(){
                    console.log('获取数据出错')
                })
            })
        }).on('error',function(){
            console.log('获取数据出错')
        })
    })
}).on('error',function(){
    console.log('获取数据出错')
})

以上程序中,只展示了爬取其中两个页面的博客信息的程序。
why?为什么不全部展示出来?
我们单独分析一下这段程序

        //使用回调函数,顺序的访问url数组中的页面
        http.get(urlArr[0],function(res){
            var html = '';
            res.on('data',function(data){
                html+=data;
            })

            res.on('end',function(){
                var blogData = filterArticle(html)
                printArticleInfo(blogData)
                //使用回调函数,顺序的访问url数组中的下一个页面
                http.get(urlArr[1],function(res){
                    var html = '';
                    res.on('data',function(data){
                        html+=data;
                    })

                    res.on('end',function(){
                        var blogData = filterArticle(html)
                        printArticleInfo(blogData)
                    })
                }).on('error',function(){
                    console.log('获取数据出错')
                })
            })
        }).on('error',function(){
            console.log('获取数据出错')
        })

从这段程序可以看出来,每一次的回调,都要嵌入上一次的回调函数结束的位置,最后,这段代码量会十分庞大,代码的可读性也会非常差,回调地狱

因此,我们引入Promise方法来改造我们的程序

Step3:使用Promise来更优雅的实现

对Step2的程序进行改造,主要改造的地方:

  • 使用var Promise = require('bluebird')来引入Promise方法
  • 改造http.get()

用以下程序替代之前的http.get()

function getUrlAsync(url){
    return new Promise(function(resolve,reject){
        console.log('正在爬取:'+url)
        http.get(url,function(res){
            var html = '';
            res.on('data',function(data){
                html+=data;
            })

            res.on('end',function(){
                resolve(html)
            })
        }).on('error',function(){
            reject(e)
            console.log('获取数据出错');
        })
    })
}

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

    res.on('data',function(data){
        html+=data;
    })

    res.on('end',function(){
        var urlArr = filterRankUrl(html)        //获取url
        printUrlInfo(urlArr)

        var fetchBlogArray = []

        urlArr.forEach(function(url){
            fetchBlogArray.push(getUrlAsync(url))
        })

        //执行promise
        Promise
            .all(fetchBlogArray)
            .then(function(pages){
                pages.forEach(function(html){
                    var blogData = filterArticle(html)
                    printArticleInfo(blogData)
                })
            })
    })
}).on('error',function(){
    console.log('获取数据出错');
})

以上程序,便可以实现我们的需求。
使用Promise,让回调的代码看起来更优雅,便于理解。

特别说明,由于博主而是刚接触Promise和爬虫,有些地方不太懂,不敢乱说,所以很多地方都没有讲解,大家想要进一步的理解细节,可以参考下面的慕课视频。博主有进一步的理解,也会更新微博

本文章参考慕课视频:http://www.imooc.com/learn/637

你可能感兴趣的:(node-js)