nodejs爬取博客园的博文

 

其实写这篇文章,我是很忐忑的,因为爬取的内容就是博客园的,万一哪个顽皮的小伙伴拿去干坏事,我岂不成共犯了?

 

好了,进入主题。

 

首先,爬虫需要用到的模块有:

express

ejs

superagent (nodejs里一个非常方便的客户端请求代理模块)

cheerio (nodejs版的jQuery)

 

前台布局使用bootstrap

分页插件使用 twbsPagination.js 

 

完整的爬虫代码,在我的github中可以下载。主要的逻辑代码在 router.js 中。

 

1. 爬取某个栏目第1页的数据

分析过程:

打开博客园的主页: http://www.cnblogs.com/

左侧导航栏里显示了所有栏目的分类信息,可以在开发者工具中获取查看这些信息.

 

每个栏目的URL也很有规律,都是 www.cnblogs.com/cate/栏目名称。 根据这个URL就可以爬取某个栏目第1页的博文了~

 

下面贴出代码:

app.js (入口文件)

 1 // 载入模块
 2 var express = require('express');
 3 var app = express();
 4 var router = require('./router/router');
 5 
 6 // 设置模板引擎
 7 app.set('view engine', 'ejs');
 8 
 9 // 静态资源中间件
10 app.use(express.static('./public'));
11 
12 // 博客园
13 app.get('/cnblogs', router.cnblogs);
14 // 栏目
15 app.get('/cnblogs/cate/:cate/', router.cnblogs_cate);
16 
17 
18 app.listen(1314, function(err){
19     if(err) console.log('1314端口被占用');
20 });

 

router.js 

var request = require('superagent');
var cheerio = require('cheerio');

// 栏目
var cate = [
    'java', 'cpp', 'php', 'delphi', 'python', 'ruby',
    'web', 'javascript', 'jquery', 'html5'
];

// 显示页面
exports.cnblogs = function(req, res){
    res.render('cnblogs', {
        cate: cate
    });
};

// 爬取栏目数据
exports.cnblogs_cate = function(req, res){

    // 栏目
    var cate = req.params['cate'];

    request
    .get('http://www.cnblogs.com/cate/' + cate)
    .end(function(err, sres){

        var $ = cheerio.load(sres.text);
        var article = [];
        $('.titlelnk').each(function(index, ele){
            var ele = $(ele);
            var href = ele.attr('href'); // 博客链接
            var title = ele.text();      // 博客内容
            article.push({
                href: href,
                title: title
            });            
        });
        res.json({
            title: cate,
            cnblogs: article
        });
    });
};

 

cnblogs.ejs 

只贴出核心代码

1 <div class="col-lg-6">
2           <select class="form-control" id="cate">
3             <option value="0">请选择分类</option>
4             <% for(var i=0; i<cate.length; i++){ %>
5               <option value="<%= cate[i]%>"><%= cate[i]%></option>
6             <% } %>
7           </select>
8 </div>

JS模板

    <script type="text/template" id="cnblogs">
      <ul class="list-group">
          <li class="list-group-item">
            <a href="{{= href}}" target="_blank">{{= title}}</a>
      </ul>
    </script>

Ajax请求

$('#cate').on('change', function(){
 var cate = $(this).val();
 if(cate == 0) return;
 $('.artic').html('');
 $.ajax({
   url: '/cnblogs/cate/' + cate,
   type: 'GET',
   dataType: 'json',
   success: function(data){
     var cnblogs = data.cnblogs;
     for(var i=0; i<cnblogs.length; i++){
       var compiled = _.template($('#cnblogs').html());
       var art = compiled(cnblogs[i]);
       $('.artic').append(art);
     }
   }
 });
});

 

输入: http://localhost:1314/cnblogs/  ,可以看到, 成功获取javascript下第1页数据。

 

2. 分页功能

以 http://www.cnblogs.com/cate/javascript/ 为例:

首先,分页的数据是Ajax调用后端接口返回的。

 

chrome的开发者工具中,可以看到,分页时,会向服务器发送两个请求,

 PostList.aspx 请求具体某页的数据.

 load.aspx 返回分页字符串.

 

我们重点分析 PostList.aspx 这个接口:

 

可以发现 请求方式是POST。

问题的重点是POST请求的数据是如何组装的?

分析源码,发现每个分页字符串都绑定了一个事件 -- aggSite.loadCategoryPostList()

 

查看页面源码,发现这个函数定义在 aggsite.js 文件里.

 

也就是下面这个函数.

 

重点是这行代码, 使用Ajax向后端发送请求.

this.loadPostList("/mvc/AggSite/" + aggSiteModel.ItemListActionName + ".aspx").

分析loadPostList 函数,可以发现POST的数据是变量aggSiteModel的值.

 

而 aggSiteModel 在页面中的定义:

至此前端的分析告一段落。 我们要做的,就是使用nodejs,模拟浏览器发送请求。

 

router.js

 1 exports.cate_page = function(req, res){
 2 
 3     var cate = req.query.cate;
 4     var page = req.query.page;
 5 
 6     var url = 'http://www.cnblogs.com/cate/' + cate;
 7 
 8     request
 9     .get(url)
10     .end(function(err, sres){
11 
12         // 构造POST请求的参数
13         var $ = cheerio.load(sres.text);
14         var post_data_str = $('#pager_bottom').prev().html().trim();
15         var post_data_obj = JSON.parse(post_data_str.slice(post_data_str.indexOf('=')+2, -1));    
16 
17         // 分页接口
18         var page_url = 'http://www.cnblogs.com/mvc/AggSite/PostList.aspx';
19         // 修改当前页
20         post_data_obj.PageIndex = page; 
21 
22         request
23         .post(page_url)
24         .set('origin', 'http://www.cnblogs.com') // 伪造来源
25         .set('referer', 'http://www.cnblogs.com/cate/'+cate+'/') // 伪造referer
26         .send(post_data_obj) // POST数据
27         .end(function(err, ssres){
28             var article = [];
29             var $$ = cheerio.load(ssres.text);
30             $$('.titlelnk').each(function(index, ele){
31                 var ele = $$(ele);
32                 var href = ele.attr('href');
33                 var title = ele.text();
34                 article.push({
35                     href: href,
36                     title: title
37                 });            
38             });
39             res.json({
40                 title: cate,
41                 cnblogs: article
42             });
43         });    
44     });
45 };

 

cate.ejs 分页代码

 1 $('.pagination').twbsPagination({
 2   totalPages: 20, // 默认显示20页
 3   startPage: 1,
 4   visiblePages: 5,
 5   initiateStartPageClick: false,
 6   first: '首页',
 7   prev: '上一页',
 8   next: '下一页',
 9   last: '尾页',
10   onPageClick: function(evt, page){
11     $.ajax({
12       url: '/cnblogs/cate_page?cate=' + cate + '&page=' + page,
13       type: 'GET',
14       dataType: 'json',
15       success: function(data){
16         $('.artic').html('');
17         var cnblogs = data.cnblogs;
18          for(var i=0; i<cnblogs.length; i++){
19            var compiled = _.template($('#cnblogs').html());
20            var art = compiled(cnblogs[i]);
21            $('.artic').append(art);
22          }
23       }
24     });
25   }
26 });

 

输入:http://localhost:1314/cnblogs/cate,可以看到:

javascript栏目第1页数据

第2页数据

 

后话

至此,一个简单的爬虫就完成了。  其实爬虫本身并不难,难点在于分析页面结构,和一些业务逻辑的处理。

完整的代码,我已经放在github上,欢迎starn(☆▽☆)

由于是第一次写技术类的博客,文笔有限,才学疏浅,若有不正确的地方,欢迎广大博友指正。

 

参考资料:

《SuperAgent中文使用文档》

《通读cheerio API》

 

posted @ 2017-01-19 11:08  卡卡小狮子  阅读(1190)  评论(11编辑  收藏  举报