利用node.js爬取指定排名网站的JS引用库详解
前言
本文给大家介绍的爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。下面话不多说了,来一起看看详细的介绍:
所用到的node主要模块
- express不用多说
- requesthttp模块
- cheerio运行在服务器端的jQuery
- node-inspectornode调试模块
- node-dev修改文件后自动重启app
关于调试Node
在任意一个文件夹,执行node-inspector,通过打开特定页面,在页面上进行调试,然后运行app,使用node-devapp.js来自动重启应用。
所碰到的问题
1.request请求多个页面
由于请求是异步执行的,和分别返回3个页面的数据,这里只爬取了50个网站,一个页面有20个,所以有3页,通过循环里套request请求,来实现。
通过添加请求头可以实现基本的反爬虫
处理数据的方法都写在analyData()里面,造成后面的数据重复存储了,想了很久,才想到一个解决方法,后面会写到是怎么解决的。
for(vari=1;i2.多层回调
仔细观察代码,你会发现,处理数据的方法使用了如下的多层回调,也可以不使用回调,写在一个函数内部;因为,每层都要使用上一层的数据,造成了这样的写法。
functionf1(data1){ f2(data1); } functionf2(data2){ f3(data2); } functionf3(data3){ f4(data4); }3.正则获取JS库
由于获取页面库,首先需要获取到script的src属性,然后通过正则来实现字符串匹配。
获取到的script可能是上面这样的,由于库名的命名真是各种各样,后来想了一下,因为文件名是用.js结尾的,所以就以点号为结尾,然后把点号之前的字符截取下来,这样获得了库名,代码如下。
varreg=/[^\/\\]+$/g; varlibName=jsLink.match(reg).join(''); varlibFilter=libName.slice(0,libName.indexOf('.'));4.cheerio模块获取JS引用链接
这部分也花了一点时间,才搞定,cheerio获取DOM的方法和jQuery是一样的,需要对返回的DOM对象进行查看,就可以看到对象里隐藏好深的href属性,方法大同小异,你也可以使用其他选择器,选择到script标签
var$=cheerio.load(body); varscriptFile=$('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值进去,执行存储的操作。
//将缓存数据存储到数据库 functionstore2db(libObj){ console.log(libObj); for(vari=0;i6.分页插件
本爬虫前端使用了bootstrap.paginator插件,主要是前台分页,返回数据,根据点击的页数,来显示对应的数据,后期考虑使用AJAX请求的方式来实现翻页的效果,这里的注意项,主要是最后一页的显示,最好前面做个判断,因为返回的数据,不一定刚好是页数的整数倍
function_paging(libObj){ varele=$('#page'); varpages=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":returnpage; } }, onPageClicked:function(event,originalEvent,type,page){ //console.log('当前选中第:'+page+'页'); varpHtml=''; varendPage; varstartPage=(page-1)*20; if(page'; pHtml+=(i+1)+' '; pHtml+=libObj[i].name+' '; pHtml+=libObj[i].libsNum+' '; } libShow.html(pHtml); } }) }完整代码
1.前端
$(function(){ varquery=$('.query'), rank=$('.rank'), show=$('.show'), queryLib=$('.queryLib'), libShow=$('#libShow'), libName=$('.libName'), displayResult=$('.displayResult'); varcheckLib=(function(){ function_query(){ query.click(function(){ $.post( '/query', { rank:rank.val(), }, function(data){ console.log(data); } ) }); queryLib.click(function(){ varinputLibName=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; } varlibHtml=''; for(vari=0;i'; 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) varlibHtml=''; for(vari=0;i<20;i++){ libHtml+=''; } displayResult.show(); libShow.html(libHtml);//点击显示按钮,显示前20项数据 _paging(data); } ) }); } //翻页器 function_paging(libObj){ varele=$('#page'); varpages=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":returnpage; } }, onPageClicked:function(event,originalEvent,type,page){ //console.log('当前选中第:'+page+'页'); varpHtml=''; varendPage; varstartPage=(page-1)*20; if(page '; libHtml+=(i+1)+' '; libHtml+=data[i].name+' '; libHtml+=data[i].libsNum+' '; pHtml+=(i+1)+' '; pHtml+=libObj[i].name+' '; pHtml+=libObj[i].libsNum+' '; } libShow.html(pHtml); } }) } functioninit(){ _query(); _showLibs(); } return{ init:init } })(); checkLib.init(); })2.后端路由
varexpress=require('express'); varmongoose=require('mongoose'); varrequest=require('request'); varcheerio=require('cheerio'); varrouter=express.Router(); varJsLib=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){ varlibName=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){ varrank=req.body.rank; varlen=Math.round(rank/20); for(vari=1;i源码下载
github下载地址
(本地下载 ) 后记
通过这个小爬虫,学习到很多知识,例如爬虫的反爬虫有哪些策越,意识到node.js的异步执行特性,前后端是怎么进行交互的。同时,也意识到有一些方面的不足,后面还需要继续改进,欢迎大家的相互交流。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。