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


Posted in Javascript onJuly 25, 2017

前言

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

所用到的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 src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>

获取到的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 += '<tr><td>';
 pHtml += (i+1) + '</td><td>';
 pHtml += libObj[i].name + '</td><td>';
 pHtml += libObj[i].libsNum + '</td></tr>';
 }
 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 += '<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',
 {
 rank: rank.val(),
 },
 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, 
 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 += '<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.后端路由

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;

源码下载

github下载地址 (本地下载

后记

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

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

Javascript 相关文章推荐
javascript 写类方式之五
Jul 05 Javascript
combox改进版 页面原型参考dojo的,比网上jQuery的那些combox功能强,代码更小
Apr 15 Javascript
IE中document.createElement的iframe无法设置属性name的解决方法
Sep 14 Javascript
jquery中cookie用法实例详解(获取,存储,删除等)
Jan 04 Javascript
jQuery实现鼠标选中文字后弹出提示窗口效果【附demo源码】
Sep 05 Javascript
js获取时间函数及扩展函数的方法
Oct 30 Javascript
js学使用setTimeout实现轮循动画
Jul 17 Javascript
js下拉菜单生成器dropMenu使用方法详解
Aug 01 Javascript
jquery+css3实现熊猫tv导航代码分享
Feb 12 jQuery
Vue render渲染时间戳转时间,时间转时间戳及渲染进度条效果
Jul 27 Javascript
vuex state中的数组变化监听实例
Nov 06 Javascript
JS面向对象之多选框实现
Jan 17 Javascript
详解angularjs获取元素以及angular.element()用法
Jul 25 #Javascript
以BootStrap Tab为例写一个前端组件
Jul 25 #Javascript
基于Bootstrap的标签页组件及bootstrap-tab使用说明
Jul 25 #Javascript
js事件委托和事件代理案例分享
Jul 25 #Javascript
基于JavaScript实现多级菜单效果
Jul 25 #Javascript
简单谈谈React中的路由系统
Jul 25 #Javascript
老生常谈js中的MVC
Jul 25 #Javascript
You might like
smarty中先strip_tags过滤html标签后truncate截取文章运用
2010/10/25 PHP
JavaScript 存在陷阱 删除某一区域所有节点
2010/05/10 Javascript
JS 自定义带默认值的函数
2011/07/21 Javascript
关于Javascript与iframe的那些事儿
2013/07/04 Javascript
jquery 缓存问题的几个解决方法
2013/11/11 Javascript
详解addEventListener的三个参数之useCapture
2015/03/16 Javascript
JS+CSS实现仿触屏手机拨号盘界面及功能模拟完整实例
2015/05/16 Javascript
javascript字符串与数组转换汇总
2015/05/26 Javascript
JavaScript去除数组里重复值的方法
2015/07/13 Javascript
jquery实现文字单行横移或翻转(上下、左右跳转)
2017/01/08 Javascript
jQuery插件HighCharts绘制简单2D柱状图效果示例【附demo源码】
2017/03/21 jQuery
bootstrap 弹出框modal添加垂直方向滚轴效果
2018/07/09 Javascript
layui实现多图片上传并限制上传的图片数量
2019/09/26 Javascript
详解关闭令人抓狂的ESlint 语法检测配置方法
2019/10/28 Javascript
JS时间戳与日期格式互相转换的简单方法示例
2021/01/30 Javascript
Python中函数的用法实例教程
2014/09/08 Python
跟老齐学Python之不要红头文件(1)
2014/09/28 Python
django创建自定义模板处理器的实例详解
2017/08/14 Python
Python列表推导式、字典推导式与集合推导式用法实例分析
2018/02/07 Python
python下载微信公众号相关文章
2019/02/26 Python
对Python 检查文件名是否规范的实例详解
2019/06/10 Python
Python Pickle 实现在同一个文件中序列化多个对象
2019/12/30 Python
python logging设置level失败的解决方法
2020/02/19 Python
Python猴子补丁Monkey Patch用法实例解析
2020/03/23 Python
使用opencv识别图像红色区域,并输出红色区域中心点坐标
2020/06/02 Python
利用python+request通过接口实现人员通行记录上传功能
2021/01/13 Python
简历中求职的个人自我评价
2013/12/03 职场文书
新婚姻法离婚协议书范文
2014/11/30 职场文书
优秀少先队员事迹材料
2014/12/24 职场文书
安全先进班组材料
2014/12/26 职场文书
2015年教师工作总结范文
2015/03/31 职场文书
酒吧七夕情人节宣传语
2015/11/24 职场文书
小学三年级语文教学反思
2016/03/03 职场文书
用几道面试题来看JavaScript执行机制
2021/04/30 Javascript
vue-cli4.5.x快速搭建项目
2021/05/30 Vue.js
Python实现byte转integer
2021/06/03 Python