zoukankan      html  css  js  c++  java
  • 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》

  • 相关阅读:
    Linux中创建Daemon进程的三种方法【转】
    Linux 内核定时器使用 二 高精度定时器 hrtimer 的用例【转】
    使用 Qemu 虚拟 ARM64 平台演示 kdump 崩溃转存【转】
    自旋锁 spin_lock、 spin_lock_irq 以及 spin_lock_irqsave 的区别【转】
    Linux中的spinlock机制[四]
    Linux中的虚拟内存机制和内存映射【转】
    那些情况该使用它们spin_lock到spin_lock_irqsave【转】
    Linux内核中的软中断、tasklet和工作队列详解【转】
    Linux 读写memory操作,devmem直接访问物理内存地址【转】
    Linux性能之DVFS/cpufreq【转】
  • 原文地址:https://www.cnblogs.com/slion/p/6297391.html
Copyright © 2011-2022 走看看