多页面的爬虫程序
在上一篇“简单的爬虫程序”中,我们写了一个对一个单页面进行爬虫的程序,今天,我们将实现对多个页面进行爬虫,也会使用到大名鼎鼎的promise。
Step1:获取多个页面的url
选取CSDN博客的排行榜中的博客周排行,获取每一条的url,为之后的爬虫做准备。
初始的url:http://blog.csdn.net/ranking.html
多个页面的url数组:urlArr[url1,url2,……,url10]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 1var http = require('http')
2var cheerio = require('cheerio')
3var url = 'http://blog.csdn.net/ranking.html'
4
5function filterRankUrl(html){
6 var $ = cheerio.load(html) //加载html内容
7
8 var blogRank = $('.rankList .ranking').eq(1).find('li')
9 var blogRankUrl = []
10
11 blogRank.each(function(item){
12 var blogRankItem = $(this)
13 var url = blogRankItem.find('a.blog_a').attr('href')
14 blogRankUrl.push(url)
15 })
16 return blogRankUrl
17}
18
19//打印url数组
20function printUrlInfo(blogRankData){
21 blogRankData.forEach(function(item){
22 console.log(item)
23 })
24}
25
26http.get(url,function(res){
27 var html = '';
28
29 res.on('data',function(data){
30 html+=data;
31 })
32
33 res.on('end',function(){
34 var urlArr = filterRankUrl(html) //获取url数组
35 printUrlInfo(urlArr)
36 })
37}).on('error',function(){
38 console.log('获取数据出错');
39})
40
运行结果截图
Step2:顺序爬取url数组中的每个页面
下面使用回调函数,在获取url数组后顺序的访问每个页面的内容
每个页面的数据组成形式如下:
1
2
3
4
5
6
7
8
9
10
11 1{
2 author : author,
3 blogs : [{
4 title:title,
5 description : description,
6 time : time,
7 view : view,
8 comments :comments
9 }]
10}
11
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 1var http = require('http')
2var cheerio = require('cheerio')
3var url = 'http://blog.csdn.net/ranking.html'
4
5//过滤排行榜页面中“博客周排行”模块的url
6function filterRankUrl(html){
7 var $ = cheerio.load(html) //加载html内容
8
9 var blogRank = $('.rankList .ranking').eq(1).find('li')
10 var blogRankUrl = []
11
12 blogRank.each(function(item){
13 var blogRankItem = $(this)
14 var url = blogRankItem.find('a.blog_a').attr('href')
15 blogRankUrl.push(url)
16 })
17 return blogRankUrl
18}
19
20//过滤每个URL数组对应页面的文章
21function filterArticle(html){
22
23 var $ = cheerio.load(html)
24
25 var blogData = {}
26
27 //获取博主
28 var author = $('#panel_Profile .user_name').text()
29 blogData.author = author
30
31 //获取文章信息
32 var blogAtricle = $('.list_item_new .list_item')
33 var blogs = []
34 blogAtricle.each(function(item){
35 var blogItem = $(this)
36 var blogItemData = {}
37 var title = blogItem.find('.link_title').text().trim()
38 var description = blogItem.find('.article_description').text().trim()
39 var time = blogItem.find('.article_manage .link_postdate').text().trim()
40 var view = blogItem.find('.article_manage .link_view').text().trim()
41 var comments = blogItem.find('.article_manage .link_comments').text().trim()
42 blogItemData.title = title
43 blogItemData.description = description
44 blogItemData.time = time
45 blogItemData.view = view
46 blogItemData.comments = comments
47
48 blogs.push(blogItemData)
49 })
50 blogData.blogs = blogs
51 return blogData
52
53}
54
55//打印url数组
56function printUrlInfo(blogRankData){
57 blogRankData.forEach(function(item){
58 console.log(item)
59 })
60}
61
62//打印每个页面的信息
63function printArticleInfo(blogData){
64 console.log("----博主 : "+blogData.author+"----\n")
65 console.log("爬取文章数 : "+blogData.blogs.length+"\n")
66 blogData.blogs.forEach(function(item){
67 console.log("文章名:"+item.title+'\n')
68 console.log("文章描述:"+item.description+'\n')
69 console.log("发表时间:"+item.time+'\n')
70 console.log("阅读:"+item.view+'\n')
71 console.log("评论:"+item.comments+'\n')
72
73 console.log("\n--------------------\n\n")
74 })
75}
76
77http.get(url,function(res){
78 var html = '';
79
80 res.on('data',function(data){
81 html+=data;
82 })
83
84 res.on('end',function(){
85 var urlArr = filterRankUrl(html) //获取url
86 printUrlInfo(urlArr)
87 //使用回调函数,顺序的访问url数组中的页面
88 http.get(urlArr[0],function(res){
89 var html = '';
90 res.on('data',function(data){
91 html+=data;
92 })
93
94 res.on('end',function(){
95 var blogData = filterArticle(html)
96 printArticleInfo(blogData)
97 //使用回调函数,顺序的访问url数组中的下一个页面
98 http.get(urlArr[1],function(res){
99 var html = '';
100 res.on('data',function(data){
101 html+=data;
102 })
103
104 res.on('end',function(){
105 var blogData = filterArticle(html)
106 printArticleInfo(blogData)
107 })
108 }).on('error',function(){
109 console.log('获取数据出错')
110 })
111 })
112 }).on('error',function(){
113 console.log('获取数据出错')
114 })
115 })
116}).on('error',function(){
117 console.log('获取数据出错')
118})
119
以上程序中,只展示了爬取其中两个页面的博客信息的程序。
why?为什么不全部展示出来?
我们单独分析一下这段程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 1 //使用回调函数,顺序的访问url数组中的页面
2 http.get(urlArr[0],function(res){
3 var html = '';
4 res.on('data',function(data){
5 html+=data;
6 })
7
8 res.on('end',function(){
9 var blogData = filterArticle(html)
10 printArticleInfo(blogData)
11 //使用回调函数,顺序的访问url数组中的下一个页面
12 http.get(urlArr[1],function(res){
13 var html = '';
14 res.on('data',function(data){
15 html+=data;
16 })
17
18 res.on('end',function(){
19 var blogData = filterArticle(html)
20 printArticleInfo(blogData)
21 })
22 }).on('error',function(){
23 console.log('获取数据出错')
24 })
25 })
26 }).on('error',function(){
27 console.log('获取数据出错')
28 })
29
从这段程序可以看出来,每一次的回调,都要嵌入上一次的回调函数结束的位置,最后,这段代码量会十分庞大,代码的可读性也会非常差,回调地狱
因此,我们引入Promise方法来改造我们的程序
Step3:使用Promise来更优雅的实现
对Step2的程序进行改造,主要改造的地方:
- 使用var Promise = require('bluebird')来引入Promise方法
- 改造http.get()
用以下程序替代之前的http.get()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 1function getUrlAsync(url){
2 return new Promise(function(resolve,reject){
3 console.log('正在爬取:'+url)
4 http.get(url,function(res){
5 var html = '';
6 res.on('data',function(data){
7 html+=data;
8 })
9
10 res.on('end',function(){
11 resolve(html)
12 })
13 }).on('error',function(){
14 reject(e)
15 console.log('获取数据出错');
16 })
17 })
18}
19
20http.get(url,function(res){
21 var html = '';
22
23 res.on('data',function(data){
24 html+=data;
25 })
26
27 res.on('end',function(){
28 var urlArr = filterRankUrl(html) //获取url
29 printUrlInfo(urlArr)
30
31 var fetchBlogArray = []
32
33 urlArr.forEach(function(url){
34 fetchBlogArray.push(getUrlAsync(url))
35 })
36
37 //执行promise
38 Promise
39 .all(fetchBlogArray)
40 .then(function(pages){
41 pages.forEach(function(html){
42 var blogData = filterArticle(html)
43 printArticleInfo(blogData)
44 })
45 })
46 })
47}).on('error',function(){
48 console.log('获取数据出错');
49})
50
以上程序,便可以实现我们的需求。
使用Promise,让回调的代码看起来更优雅,便于理解。
特别说明,由于博主而是刚接触Promise和爬虫,有些地方不太懂,不敢乱说,所以很多地方都没有讲解,大家想要进一步的理解细节,可以参考下面的慕课视频。博主有进一步的理解,也会更新微博
本文章参考慕课视频:http://www.imooc.com/learn/637