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

释放双眼,带上耳机,听听看~!

多页面的爬虫程序

在上一篇“简单的爬虫程序”中,我们写了一个对一个单页面进行爬虫的程序,今天,我们将实现对多个页面进行爬虫,也会使用到大名鼎鼎的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

给TA打赏
共{{data.count}}人
人已打赏
安全技术

DES 加密 解密

2021-8-18 16:36:11

安全技术

C++ 高性能服务器网络框架设计细节

2022-1-11 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索