利用node.js爬取指定排名网站的JS引用库详解

前端之家收集整理的这篇文章主要介绍了利用node.js爬取指定排名网站的JS引用库详解前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

本文给大家介绍的爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。下面话不多说了,来一起看看详细的介绍:

所用到的node主要模块

@H_404_10@
  • 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()里面,造成后面的数据重复存储了,想了很久,才想到一个解决方法,后面会写到是怎么解决的。

    2. 多层回调

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

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

    function f3(data3){
    f4(data4);
    }

    3. 正则获取JS库

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

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

    4.cheerio模块获取JS引用链接

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

    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请求的方式来实现翻页效果,这里的注意项,主要是最后一页的显示,最好前面做个判断,因为返回的数据,不一定刚好是页数的整数倍

    首页"; 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 += '

    完整代码

    1. 前端

    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 += '<tr><td>';
    libHtml += (i+1) + '</td><td>';
    libHtml += data[i].name + '</td><td>';
    libHtml += data[i].libsNum + '</td></tr>';
    }
    libShow.html(libHtml);
    }
    )
    });
    }

    function _showLibs(){
    show.click(function(){
    $.get(
    '/getLibs',function(data){
    console.log('一共返回'+ data.length + '条数据');
    console.log(data)
    var libHtml = '';
    for (var i = 0; i < 20; i++) {
    libHtml += '<tr><td>';
    libHtml += (i+1) + '</td><td>';
    libHtml += data[i].name + '</td><td>';
    libHtml += data[i].libsNum + '</td></tr>';
    }
    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,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 += '<tr><td>';
    pHtml += (i+1) + '</td><td>';
    pHtml += libObj[i].name + '</td><td>';
    pHtml += libObj[i].libsNum + '</td></tr>';
    }
    libShow.html(pHtml);
    }
    })
    }

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

    return {
    init: init
    }

    })();

    checkLib.init();

    })

    2.后端路由

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

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

    // 库的查询
    router.post('/queryLib',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',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,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],like Gecko) Chrome/59.0.3071.115 Safari/537.36'
    }
    }

    request(options,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);
    }
    })

    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,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的异步执行特性,前后端是怎么进行交互的。同时,也意识到有一些方面的不足,后面还需要继续改进,欢迎大家的相互交流。

    好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对编程之家的支持

    猜你在找的Node.js相关文章