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即可查看結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM