node.js入门(express和superagent的使用)爬博客园和知乎数据,并实时显示到前端

先来看一下结果:http://39.105.101.122:81/html/home.html

用到的东西:前端(H5盒子模型+vue+jquery)后端node.js(express+superagent),这里没有用到数据库和其他存储数据的文件。前端发送ajax请求,后端用express接收请求,然后通过superagent去请求博客园或者知乎的网站,拿到数据之后看情况对数据进行处理,然后再将数据返回给前端。

之前做过快半年的前端实习,主要是做H5方面的。毕业之后在新的公司里面主要是写java的爬虫,用的是htmlunit。这个东西相当于是用代码模拟浏览器来访问网站,可以模拟点击按钮的操作,用起来还是非常方便的。后来就想学一下node.js,看看用它是怎么来写后端,怎么写爬虫的。

首先是下载并安装node.js,新建一个文件夹作为一个新项目,进入这个文件夹,按住Shift键然后点击鼠标右键,点击“在此处打开命令窗口”。这样省去了切换目录的麻烦。然后在命令窗口里面输入npm init,回车,看情况填写信息,也可以一路回车默认。然后安装这里用到的几个模块:express、superagent、querystring、cheerio、eventproxy,比如在命令窗口输入npm install express --save,然后回车就可以在当前项目内安装express模块。安装完这几个模块之后,在当前目录下新建一个js文件:server.js,里面存放后台的代码,然后在当前目录下新建一个文件夹webapp,里面存放前端的东西:html、css、js、图片、还有其他用到的vue、jquery。

express的作用是可以创建一个后台的服务器,接收来自前端的请求,并且发送数据给前端。

用法如下:

var express = require('express');
var app = express();
app.use(express.static('webapp'));

var server = app.listen(8081, function () {

  var host = server.address().address
  var port = server.address().port

  console.log("应用实例,访问地址为 http://%s:%s", host, port)

})
app.use(express.static('webapp'));加上这句代码,浏览器里面就可以访问webapp这个文件夹里面的内容了。在命令窗口里面输入node server.js然后回车,在浏览器里面输入http://localhost:8081/webapp里面的内容就可以访问了。
接下来看一下前后端通信的代码是怎样的:
前端的请求如下所示:
$.ajax({
    url: 'http://你的ip地址:8081/getData',
    type: 'POST',
    data: {
        num: $this.num,
    },
    success: function(data){
        var tempData = data.ret;
        for( var i = 0; i < tempData.length; i++ ){
            var tempItem = {
                img: tempData[i].itemCover,
                hasImg: tempData[i].itemCover==undefined?false:true,
                text: tempData[i].itemText,
                num: "139",
                title: tempData[i].itemTitle,
                url: tempData[i].itemLink
            }
            $this.items.push(tempItem);
        }
        $this.num++;
        $this.working = false;
        $this.loading = false;
        // localStorage.items=JSON.stringify($this.items);
        // localStorage.num=$this.num;
    }
})

server.js里面对应接收这个请求的方法如下所示:

app.post('/getData', function (req, res) {
    req.on('data', function(data) {
        var currentData = ""+data;
        var tempData = qs.parse(currentData);
        var index = tempData.num;
        var postData = [];
        var tempData = {
            value: "s"
        };
        postData.push(tempData);
        res.header("Access-Control-Allow-Origin", "*");
        res.json({
            success: 1,
            ret: postData
        })
    });
})

使用req.on('data',function(data){...})就可以接收到前端post过来的数据,然后用querystring的parse方法可以将数据解析成json格式,这样就能读取相应的数据。然后用res.json({...})就可以将数据返回给前端。res.header("Access-Control-Allow-Origin", "*")这句代码可以解决跨域的问题,比如说如果没有这句代码的话,在浏览器里面用localhost访问页面,前端的ajax请求是不会成功的,因为他会提示跨域的错误,加上这句代码之后就可以访问成功。

接下来看一下怎样用superagent来爬数据:

superagent.get("https://www.cnblogs.com/").end(function(err,pre){
    var $ = cheerio.load(pre.text);
    for( var i = 0; i < $(".post_item_body").length; i++ ){
       ...
    }
})

用superagent来请求博客园首页的网址,然后pre.text就是博客园首页的html内容了,但是由于它是字符串,所以这里需要用到cheerio.load方法。然后这个$就和jquery里面的$一样了,可以直接用jquery里面的方法来获取页面的元素。在这里呢,推荐看https://www.cnblogs.com/coco1s/p/4954063.html这篇博客,这里面详细的描述了怎样用superagent来爬取博客园的数据。

爬取博客园的数据是成功了,但是我还想试一下其他的网站,平时看知乎比较多,就爬知乎吧。但是我发现用之前爬取博客园的方法去爬知乎的话,返回的是一个登陆的页面。所以这里就需要把cookie添加进去了。

superagent.get("https://www.zhihu.com/")
.set('Cookie': '...')
.end(function(err,obj){
    if(err){return;
    }
    var tempData = {
        value: obj.text,
    };
    res.header("Access-Control-Allow-Origin", "*");
    res.json({
        success: 1,
        ret: tempData
    })
})

这样的话,首页是进去了,但是在浏览器里面下滑的话还会有很多条目出来的,要想爬到这部分后来加载出来的条目,就需要分析网页的请求了。在浏览器里面打开知乎,然后按F12进入控制台,点击network,然后在网页里面下滑,就会发现network里面有很多请求,其中有一条请求是这样的:

可以看到这条请求返回的数据是很多的,它应该就是我们需要的请求了。点击上面的Headers,然后找到请求头,如下所示:

将请求头里面的这些信息塞到superagent的set函数里面,并且将Headers下面的Request URL替换之前的url,如下所示:

superagent.get("https://www.zhihu.com/api/v3/feed/topstory?action_feed=True&limit=10&session_token=a28a2fdc4537d7d14cbd46bcaf16912a&action=down&after_id=9&desktop=true"")
.set({
    'Cookie': '_zap=67cf877b-b4c4-4798-9e23-d3ef6553d2f9; q_c1=fae7f35742874cd2b0356a5c321d085f|1501321325000|1501321325000; aliyungf_tc=AQAAAPsawBFU6QgABtBbcXShs11WbLol; q_c1=fae7f35742874cd2b0356a5c321d085f|1512744706000|1501321325000; _xsrf=51039ae2b79b7ba81f1da8eba41f2b62; r_cap_id="MDc0MTM0MWZlNWY2NDc2Mzk1YTdhOWE0MWFkZDQzYjA=|1512744706|b5b2a49f82637c6408347fe8941ea3315f5f5be3"; cap_id="NjI5ZDM3YTIzY2ZjNDhhOWJlZWM2MjIxNjQ0MjNmOTE=|1512744706|210d6f4e2c44900b25afebff79b79d9aac13dda0"; d_c0="ABBCbcS6zQyPTk28d91f51hdLtLQ1xQPe3s=|1512744707"; l_n_c=1; z_c0=Mi4xRDVobkFRQUFBQUFBRUVKdHhMck5EQmNBQUFCaEFsVk5JUFVYV3dBaC1ZVktoUjFoRExNMEI1Mkp5a1BWSHpUaERn|1512744736|1d19657b025b92339fecc550c2fc8606f0f5ba48; __utma=155987696.856620421.1512833230.1512833230.1512833230.1; __utmc=155987696; __utmz=155987696.1512833230.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _xsrf=51039ae2b79b7ba81f1da8eba41f2b62',
    'Host': 'www.zhihu.com',
    'Referer': 'https://www.zhihu.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
    'x-api-version': '3.0.53',
    'x-udid': 'ABBCbcS6zQyPTk28d91f51hdLtLQ1xQPe3s=',
    'Connection': 'keep-alive',
    'authorization': 'Bearer Mi4xRDVobkFRQUFBQUFBRUVKdHhMck5EQmNBQUFCaEFsVk5JUFVYV3dBaC1ZVktoUjFoRExNMEI1Mkp5a1BWSHpUaERn|1512744736|1d19657b025b92339fecc550c2fc8606f0f5ba48'
})
.end(function(err,obj){
    if(err){
        return;
    }
    var tempData = {
        value: obj.text,
    };
    res.header("Access-Control-Allow-Origin", "*");
    res.json({
        success: 1,
        ret: tempData
    })
})

其实看代码可以发现我并没有把accept、Accept-Encoding、Accept-Language这三项加进去,因为我发现把请求头里面所有的信息塞进去之后,会返回错误信息,然后我试着删除掉一些请求信息,结果发现把这三项删除之后就可以请求成功。不清楚原因是什么。

这样的请求返回的结果和浏览器里面看到的返回的结果是一样的,中文字符是是经过转义的,使用JSON.parse函数可以将这个经过转义的字符串变为可用的json对象。然后分析这个json对象,从里面取我们需要的数据。

完整的server.js代码如下所示:

var express = require('express');
var superagent = require('superagent');
var qs = require("querystring");
var cheerio = require('cheerio');
var eventproxy = require('eventproxy');
var ep = new eventproxy();
var app = express();

app.use(express.static('webapp'));

app.post('/getData', function (req, res) {
    req.on('data', function(data) {
        var currentData = ""+data;
        var tempData = qs.parse(currentData);
        var index = tempData.num;
        console.log(tempData.num);
        var urlArr = [];
        for( var i = index*2-1; i <= index*2; i++ ){
             var url = "http://www.cnblogs.com/?CategoryId=808&CategoryType=%22SiteHome%22&ItemListActionName=%22PostList%22&PageIndex="+i+"&ParentCategoryId=0";
            urlArr.push(url);
        }
        var postData = [];
        var startTime = new Date().getTime();
        for( var j = 0; j < urlArr.length; j++ ){
            superagent.get(urlArr[j]).end(function(err,pre){
                var $ = cheerio.load(pre.text);
                for( var i = 0; i < $(".post_item_body").length; i++ ){
                    var titleElement = $(".post_item_body>h3>a").eq(i);
                    var title = titleElement.text();
                    var sourceUrl = titleElement.attr("href");
                    var textElement = $(".post_item_summary").eq(i);
                    var text = textElement.text();
                    var Element = $(".post_item_summary").eq(i);
                    var coverElement = Element.find('img');
                    var cover = "";
                    if( coverElement!=undefined&&coverElement!=null ){
                        if( coverElement.attr("src")==undefined ){
                            cover = undefined;
                        }else{
                            cover = "https:"+coverElement.attr("src");
                        }
                    }
                    var tempData = {
                        itemTitle: title,
                        itemLink: sourceUrl,
                        itemText: text,
                        itemCover: cover
                    }
                    postData.push(tempData);
                }
                ep.emit("dataEvent");
            })
        }
        ep.after('dataEvent',2,function(){
            res.header("Access-Control-Allow-Origin", "*");
            res.json({
                success: 1,
                ret: postData
            })
            var endTime = new Date().getTime();
            var time = endTime-startTime;
            console.log(time);
        });
    });
})

app.post('/getZhihuData', function (req, res) {
    req.on('data', function(data) {
        var currentData = ""+data;
        var tempData = qs.parse(currentData);
        var index = tempData.num;
        console.log(tempData.num);
        
        var postData = [];
        var startTime = new Date().getTime();
        var afterId = index*10-1;
        var url = "https://www.zhihu.com/api/v3/feed/topstory?action_feed=True&limit=10&session_token=a28a2fdc4537d7d14cbd46bcaf16912a&action=down&after_id="+afterId+"&desktop=true";
        superagent.get(url)
        .set({
            'Cookie': '_zap=67cf877b-b4c4-4798-9e23-d3ef6553d2f9; q_c1=fae7f35742874cd2b0356a5c321d085f|1501321325000|1501321325000; aliyungf_tc=AQAAAPsawBFU6QgABtBbcXShs11WbLol; q_c1=fae7f35742874cd2b0356a5c321d085f|1512744706000|1501321325000; _xsrf=51039ae2b79b7ba81f1da8eba41f2b62; r_cap_id="MDc0MTM0MWZlNWY2NDc2Mzk1YTdhOWE0MWFkZDQzYjA=|1512744706|b5b2a49f82637c6408347fe8941ea3315f5f5be3"; cap_id="NjI5ZDM3YTIzY2ZjNDhhOWJlZWM2MjIxNjQ0MjNmOTE=|1512744706|210d6f4e2c44900b25afebff79b79d9aac13dda0"; d_c0="ABBCbcS6zQyPTk28d91f51hdLtLQ1xQPe3s=|1512744707"; l_n_c=1; z_c0=Mi4xRDVobkFRQUFBQUFBRUVKdHhMck5EQmNBQUFCaEFsVk5JUFVYV3dBaC1ZVktoUjFoRExNMEI1Mkp5a1BWSHpUaERn|1512744736|1d19657b025b92339fecc550c2fc8606f0f5ba48; __utma=155987696.856620421.1512833230.1512833230.1512833230.1; __utmc=155987696; __utmz=155987696.1512833230.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _xsrf=51039ae2b79b7ba81f1da8eba41f2b62',
            'Host': 'www.zhihu.com',
            'Referer': 'https://www.zhihu.com/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
            'x-api-version': '3.0.53',
            'x-udid': 'ABBCbcS6zQyPTk28d91f51hdLtLQ1xQPe3s=',
            'Connection': 'keep-alive',
            'authorization': 'Bearer Mi4xRDVobkFRQUFBQUFBRUVKdHhMck5EQmNBQUFCaEFsVk5JUFVYV3dBaC1ZVktoUjFoRExNMEI1Mkp5a1BWSHpUaERn|1512744736|1d19657b025b92339fecc550c2fc8606f0f5ba48'
        })
        .end(function(err,obj){
            if(err){
                var X = JSON.parse(err);
                var Y = JSON.stringify(X);
                console.log(Y);
                console.log("err");
                return;
            }
            var tempData = {
                value: obj.text,
            };
            res.header("Access-Control-Allow-Origin", "*");
            res.json({
                success: 1,
                ret: tempData
            })
        })
        
    });
})

var server = app.listen(8081, function () {

  var host = server.address().address
  var port = server.address().port

  console.log("应用实例,访问地址为 http://%s:%s", host, port)

})

在这里面还用到了eventproxy这个东西,因为node.js可以异步执行,所以在一个循环里面使用的superagent也是异步执行的,它不会等当前循环执行完之后再执行下一个循环,所以在这种情况下,我们不知道这个for循环里面的所有的superagent什么时候可以执行完。所以就有了eventproxy这个东西。它相当于是一个监听器,在一个循环的末尾使用ep.emit("dataEvent");这个函数,然后就可以触发ep.after函数,在触发的次数达到设定的次数之后,after函数里面的代码就会执行。

这里前端的细节就不再赘述了,主要实现的功能就是前端请求数据的时候,后端就去爬取数据并返回给前端,没有用到数据库或者是存储数据的文件。前端也实现了上划加载,loading动画的功能是我之前写过的一个loading动画,突然想到可以用到这里就搬来了。loading动画可以看这里:http://www.cnblogs.com/huizit1/p/5470587.html

前端除了vue和jquery就没有使用其他的插件或者库了。完整的代码在这里:https://github.com/swang23/NodeWebParser

项目代码克隆到本地之后,要想跑起来需要在home.js里面把ajax请求的url里面的ip地址换成自己电脑的ip地址(命令窗口里面:ipconfig回车查看ipv4地址),然后node server.js,在浏览器里面输入http://localhost:8081/html/home.html即可查看结果。

posted on 2017-12-12 01:12  辉子t1  阅读(3408)  评论(3编辑  收藏  举报

导航