资讯专栏INFORMATION COLUMN

使用node爬虫,爬取指定排名网站的JS引用库

helloworldcoding / 1337人阅读

摘要:前期准备本爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。正则获取库由于获取页面库,首先需要获取到的属性,然后通过正则来实现字符串匹配。

前期准备

本爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。

github地址

所用到的node主要模块

express 不用多说

request http模块

cheerio 运行在服务器端的jQuery

node-inspector node调试模块

node-dev 修改文件后自动重启app

关于调试Node

在任意一个文件夹,执行node-inspector,通过打开特定页面,在页面上进行调试,然后运行app,使用node-dev app.js来自动重启应用。

所碰到的问题 1. request请求多个页面

由于请求是异步执行的,和分别返回3个页面的数据,这里只爬取了50个网站,一个页面有20个,所以有3页,通过循环里套request请求,来实现。

通过添加请求头可以实现基本的反爬虫

处理数据的方法都写在analyData()里面,造成后面的数据重复存储了,想了很久,才想到一个解决方法,后面会写到是怎么解决的。

for (var i = 1; i < len+1; i++) {
    (function(i){
      var options = {
        url: "http://www.alexa.cn/siterank/" + i,
        headers: {
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"
        }
      };
      request(options, function (err, response, body) {
          analyData(body,rank);
      })
    })(i)
  }
2. 多层回调

仔细观察代码,你会发现,处理数据的方法使用了如下的多层回调,也可以不使用回调,写在一个函数内部;因为,每层都要使用上一层的数据,造成了这样的写法。

function f1(data1){
    f2(data1);
}


function f2(data2){
    f3(data2);
}


function f3(data3){
    f4(data4);
}
3. 正则获取JS库

由于获取页面库,首先需要获取到script的src属性,然后通过正则来实现字符串匹配。

获取到的script可能是上面这样的,由于库名的命名真是各种各样,后来想了一下,因为文件名是用.js结尾的,所以就以点号为结尾,然后把点号之前的字符截取下来,这样获得了库名,代码如下。

var reg = /[^/]+$/g;
var libName = jsLink.match(reg).join("");
var libFilter = libName.slice(0,libName.indexOf("."));
4.cheerio模块获取JS引用链接

这部分也花了一点时间,才搞定,cheerio获取DOM的方法和jQuery是一样的,需要对返回的DOM对象进行查看,就可以看到对象里隐藏好深的href属性,方法大同小异,你也可以使用其他选择器,选择到script标签

var $ = cheerio.load(body);
var scriptFile = $("script").toArray();


scriptFile.forEach(function(item,index){
    if (item.attribs.src != null) {
      obtainLibName(item.attribs.src,index);
}
5.存储数据到数据库

存储数据的逻辑是先获取所有的script信息,然后push到一个缓存数组,由于push后面,紧跟着存储到数据库的方法,这两个方法都写在循环里面的,例如爬取5个网站,每个网站存储一次,后面也会跟着存储,造成数据重复存储。解决方法是存储数据的一般逻辑是先查,再存,这个查比较重要,查询的方法也有多种,这里主要是根据库名来查找唯一的数据对象,使用findOne方法。注意,由于node.js是异步执行的,这里的闭包,每次只传一个i值进去,执行存储的操作。

// 将缓存数据存储到数据库
function store2db(libObj){
  console.log(libObj);
  for (var i = 0; i < libObj.length; i++) {
    (function(i){
      var jsLib = new JsLib({
          name: libObj[i].lib,
          libsNum: libObj[i].num
      });
      
      JsLib.findOne({"name": libObj[i].lib},function(err,libDoc){
        if(err) console.log(err);
        // console.log(libDoc)
        if (!libDoc){
          jsLib.save(function(err,result){
            if(err) console.log("保存数据出错" + err);
          });
        }

      })
    })(i)
  }
  console.log("一共存储" + libObj.length + "条数据到数据库");
}
6.分页插件

本爬虫前端使用了bootstrap.paginator插件,主要是前台分页,返回数据,根据点击的页数,来显示对应的数据,后期考虑使用AJAX请求的方式来实现翻页的效果,这里的注意项,主要是最后一页的显示,最好前面做个判断,因为返回的数据,不一定刚好是页数的整数倍

function _paging(libObj) {
        var ele = $("#page");
        var pages = Math.ceil(libObj.length/20);
        console.log("总页数" + pages);
        ele.bootstrapPaginator({    
            currentPage: 1,    
            totalPages: pages,    
            size:"normal",    
            bootstrapMajorVersion: 3,    
            alignment:"left",    
            numberOfPages:pages,    
            itemTexts: function (type, page, current) {        
                switch (type) {            
                    case "first": return "首页";            
                    case "prev": return "上一页";            
                    case "next": return "下一页";            
                    case "last": return "末页";            
                    case "page": return page;
                }
            },
            onPageClicked:  function(event, originalEvent, type, page){
                // console.log("当前选中第:" + page + "页");
                var pHtml = "";
                var endPage;
                var startPage = (page-1) * 20;
                if (page < pages) {
                     endPage = page * 20;
                }else{
                     endPage = libObj.length;
                }
                for (var i = startPage; i < endPage; i++) {
                    pHtml += "";
                    pHtml += (i+1) + "";
                    pHtml += libObj[i].name + "";
                    pHtml += libObj[i].libsNum + "";
                }
                libShow.html(pHtml);
            }
        })
      }
完整代码 1. 前端
 
$(function () {
    var query = $(".query"),
        rank = $(".rank"),
        show = $(".show"),
        queryLib = $(".queryLib"),
        libShow = $("#libShow"),
        libName = $(".libName"),
        displayResult = $(".displayResult");

    var checkLib = (function(){

      function _query(){
        query.click(function(){
            $.post(
                "/query",
                {
                    rank: rank.val(),
                },
                function(data){
                    console.log(data);
                }
            )
        });
        queryLib.click(function(){
            var inputLibName = libName.val();
            if (inputLibName.length == 0) {
                alert("请输入库名~");
                return;
            }
            $.post(
                "/queryLib",
                {
                    libName: inputLibName,
                },
                function(data){
                    if(data.length == 0){
                        alert("没有查询到名为" + inputLibName + "的库");
                        libName.val("");
                        libName.focus();
                        libShow.html("")
                        return;
                    }
                    var libHtml = "";
                    for (var i = 0; i < data.length; i++) {
                        libHtml += "";
                        libHtml += (i+1) + "";
                        libHtml += data[i].name + "";
                        libHtml += data[i].libsNum + "";
                    }
                    libShow.html(libHtml);
                }
            )
        });
      }

      function _showLibs(){
        show.click(function(){
            $.get(
                "/getLibs",
                {
                    rank: rank.val(),
                },
                function(data){
                    console.log("一共返回"+ data.length + "条数据");
                    console.log(data)
                    var libHtml = "";
                    for (var i = 0; i < 20; i++) {
                        libHtml += "";
                        libHtml += (i+1) + "";
                        libHtml += data[i].name + "";
                        libHtml += data[i].libsNum + "";
                    }
                    displayResult.show();
                    libShow.html(libHtml);// 点击显示按钮,显示前20项数据
                    _paging(data);
                }
            )
        });
      }

      //翻页器
      function _paging(libObj) {
        var ele = $("#page");
        var pages = Math.ceil(libObj.length/20);
        console.log("总页数" + pages);
        ele.bootstrapPaginator({    
            currentPage: 1,    
            totalPages: pages,    
            size:"normal",    
            bootstrapMajorVersion: 3,    
            alignment:"left",    
            numberOfPages:pages,    
            itemTexts: function (type, page, current) {        
                switch (type) {            
                    case "first": return "首页";            
                    case "prev": return "上一页";            
                    case "next": return "下一页";            
                    case "last": return "末页";            
                    case "page": return page;
                }
            },
            onPageClicked:  function(event, originalEvent, type, page){
                // console.log("当前选中第:" + page + "页");
                var pHtml = "";
                var endPage;
                var startPage = (page-1) * 20;
                if (page < pages) {
                     endPage = page * 20;
                }else{
                     endPage = libObj.length;
                }
                for (var i = startPage; i < endPage; i++) {
                    pHtml += "";
                    pHtml += (i+1) + "";
                    pHtml += libObj[i].name + "";
                    pHtml += libObj[i].libsNum + "";
                }
                libShow.html(pHtml);
            }
        })
      }

        function init() {
          _query();
         _showLibs();
        }

        return {
            init: init
        }

    })();

    checkLib.init();

})
2.后端路由
var express = require("express");
var mongoose = require("mongoose");
var request = require("request");
var cheerio =require("cheerio");
var router = express.Router();
var JsLib = require("../model/jsLib")

/* 显示主页 */
router.get("/", function(req, res, next) {
  res.render("index");
});

// 显示库
router.get("/getLibs",function(req,res,next){
  JsLib.find({})
  .sort({"libsNum": -1})
  .exec(function(err,data){
    res.json(data);
  })
})

// 库的查询
router.post("/queryLib",function(req,res,next){
  var libName = req.body.libName;

  JsLib.find({
    name: libName
  }).exec(function(err,data){
    if (err) console.log("查询出现错误" + err);
    res.json(data);
  })
})

router.post("/query",function(req,res,next) {
  var rank = req.body.rank;
  var len = Math.round(rank/20);
  
  for (var i = 1; i < len+1; i++) {
    (function(i){
      var options = {
        url: "http://www.alexa.cn/siterank/" + i,
        headers: {
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"
        }
      };
      request(options, function (err, response, body) {
          analyData(body,rank);
      })
    })(i)
  }
  res.json("保存成功")
})
 
var sites = [];
var flag = 0;
function analyData(data,rank) {
    if(data.indexOf("html") == -1) return false;
    var $ = cheerio.load(data);// 传递 HTML
    var sitesArr = $(".info-wrap .domain-link a").toArray();//将所有a链接存为数组

    console.log("网站爬取中``")
    for (var i = 0; i < 10; i++) { // ***这里后面要改,默认爬取前10名
        var url = sitesArr[i].attribs.href;
        sites.push(url);//保存网址,添加wwww前缀
    }
    console.log(sites);
    console.log("一共爬取" + sites.length +"个网站");
    console.log("存储数据中...")

    getScript(sites);
}


// 获取JS库文件地址
function getScript(urls) {
  var scriptArr = [];
  var src = [];
  var jsSrc = [];
  for (var j = 0; j < urls.length; j++) {
      (function(i,callback){
        var options = {
            url: urls[i],
            headers: {
              "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36"
            }
          }

        request(options, function (err, res, body) {
          if(err) console.log("出现错误: "+err);
          var $ = cheerio.load(body);
          var scriptFile = $("script").toArray();
          callback(scriptFile,options.url);
        })
    })(j,storeLib)
  };

  function storeLib(scriptFile,url){
    flag++;// 是否存储数据的标志
    scriptFile.forEach(function(item,index){
      if (item.attribs.src != null) {
          obtainLibName(item.attribs.src,index);
      }
    })
  
      
    function obtainLibName(jsLink,i){
      var reg = /[^/]+$/g;
      var libName = jsLink.match(reg).join("");
      var libFilter = libName.slice(0,libName.indexOf("."));

        src.push(libFilter);
    }

    // console.log(src.length);
    // console.log(calcNum(src).length)
    (function(len,urlLength,src){
      // console.log("length is "+ len)
      if (len == 10 ) {// len长度为url的长度才向src和数据库里存储数据,防止重复储存
        // calcNum(src);//存储数据到数据库 // ***这里后面要改,默认爬取前10名
        var libSrc = calcNum(src);
        store2db(libSrc);
      }
    })(flag,urls.length,src)
  } 
}// getScript END

// 将缓存数据存储到数据库
function store2db(libObj){
  console.log(libObj);
  for (var i = 0; i < libObj.length; i++) {
    (function(i){
      var jsLib = new JsLib({
          name: libObj[i].lib,
          libsNum: libObj[i].num
      });
      
      JsLib.findOne({"name": libObj[i].lib},function(err,libDoc){
        if(err) console.log(err);
        // console.log(libDoc)
        if (!libDoc){
          jsLib.save(function(err,result){
            if(err) console.log("保存数据出错" + err);
          });
        }

      })
    })(i)
  }
  console.log("一共存储" + libObj.length + "条数据到数据库");
}
// JS库排序算法
function calcNum(arr){
    var libObj = {};
    var result = [];
    for (var i = 0, len = arr.length; i < len; i++) {
        
        if (libObj[arr[i]]) {
            libObj[arr[i]] ++;
        } else {
            libObj[arr[i]] = 1;
        }
    }
   
    for(var o in libObj){
        result.push({
            lib: o,
            num: libObj[o]
        })
    }

    result.sort(function(a,b){
        return b.num - a.num;
    });

    return result;
}


module.exports = router;
后记

通过这个小爬虫,学习到很多知识,例如爬虫的反爬虫有哪些策越,意识到node.js的异步执行特性,前后端是怎么进行交互的。同时,也意识到有一些方面的不足,后面还需要继续改进,欢迎大家的相互交流。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84284.html

相关文章

  • 使用node爬虫爬取指定排名网站JS引用

    摘要:前期准备本爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。正则获取库由于获取页面库,首先需要获取到的属性,然后通过正则来实现字符串匹配。 前期准备 本爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。 github地址 所用到的node主要模块 express 不用多说 request ...

    Sleepy 评论0 收藏0
  • 使用node爬虫爬取指定排名网站JS引用

    摘要:前期准备本爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。正则获取库由于获取页面库,首先需要获取到的属性,然后通过正则来实现字符串匹配。 前期准备 本爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。 github地址 所用到的node主要模块 express 不用多说 request ...

    Stardustsky 评论0 收藏0
  • Python爬虫笔记1-爬虫背景了解

    摘要:学习爬虫的背景了解。但是搜索引擎蜘蛛的爬行是被输入了一定的规则的,它需要遵从一些命令或文件的内容,如标注为的链接,或者是协议。不同领域不同背景的用户往往具有不同的检索目的和需求,搜索引擎无法提供针对具体某个用户的搜索结果。 学习python爬虫的背景了解。 大数据时代数据获取方式 如今,人类社会已经进入了大数据时代,数据已经成为必不可少的部分,可见数据的获取非常重要,而数据的获取的方式...

    oujie 评论0 收藏0
  • 一只node爬虫升级打怪之路

    摘要:我是一个知乎轻微重度用户,之前写了一只爬虫帮我爬取并分析它的数据,我感觉这个过程还是挺有意思,因为这是一个不断给自己创造问题又去解决问题的过程。所以这只爬虫还有登陆知乎搜索题目的功能。 我一直觉得,爬虫是许多web开发人员难以回避的点。我们也应该或多或少的去接触这方面,因为可以从爬虫中学习到web开发中应当掌握的一些基本知识。而且,它还很有趣。 我是一个知乎轻微重度用户,之前写了一只爬...

    shiweifu 评论0 收藏0
  • Python_爬虫基础

    摘要:并不是所有爬虫都遵守,一般只有大型搜索引擎爬虫才会遵守。的端口号为的端口号为工作原理网络爬虫抓取过程可以理解为模拟浏览器操作的过程。表示服务器成功接收请求并已完成整个处理过程。 爬虫概念 数据获取的方式: 企业生产的用户数据:大型互联网公司有海量用户,所以他们积累数据有天然优势。有数据意识的中小型企业,也开始积累的数据。 数据管理咨询公司 政府/机构提供的公开数据 第三方数据平台购买...

    ixlei 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<