在上一篇“简单的爬虫程序”中,我们写了一个对一个单页面进行爬虫的程序,今天,我们将实现对多个页面进行爬虫,也会使用到大名鼎鼎的promise。
选取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('获取数据出错');
})
下面使用回调函数,在获取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方法来改造我们的程序
对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