利用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 相关文章推荐
在textarea文本域中显示HTML代码的方法
Mar 06 Javascript
基于jquery实现的省市区级联无ajax
Sep 24 Javascript
浅谈JavaScript函数节流
Dec 09 Javascript
jQuery封装的tab选项卡插件分享
Jun 16 Javascript
Javascript编写俄罗斯方块思路及实例
Jul 07 Javascript
Jquery实现$.fn.extend和$.extend函数
Apr 14 Javascript
vue中动态添加class类名的方法
Sep 05 Javascript
jQuery中使用validate插件校验表单功能
May 24 jQuery
jQuery实现手风琴效果(蒙版)
Jan 11 jQuery
ES6使用 Array.includes 处理多重条件用法实例分析
Mar 02 Javascript
JavaScript中的执行环境和作用域链
Sep 04 Javascript
如何通过简单的代码描述Angular父组件、子组件传值
Apr 07 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
20个PHP常用类库小结
2011/09/11 PHP
PHP MVC框架skymvc支持多文件上传
2016/05/26 PHP
php取出数组单个值的方法
2018/03/12 PHP
php 中phar包的使用教程详解
2018/10/26 PHP
PDO::lastInsertId讲解
2019/01/29 PHP
JS 自动完成 AutoComplete(Ajax 查询)
2009/07/07 Javascript
JavaScript 读取元素的CSS信息的代码
2010/02/07 Javascript
js购物车实现思路及代码(个人感觉不错)
2013/12/23 Javascript
检查输入的是否是数字使用keyCode配合onkeypress事件
2014/01/23 Javascript
基于Javascript实现二级联动菜单效果
2016/03/04 Javascript
Bootstrap 最常用的JS插件系列总结(图片轮播、标签切换等)
2016/07/14 Javascript
让DIV的滚动条自动滚动到最底部的3种方法(推荐)
2016/09/24 Javascript
详解angularJS自定义指令间的相互交互
2017/07/05 Javascript
微信小程序实战篇之购物车的实现代码示例
2017/11/30 Javascript
nodejs简单实现TCP服务器端和客户端的聊天功能示例
2018/01/04 NodeJs
jQuery zTree搜索-关键字查询 递归无限层功能实现代码
2018/01/25 jQuery
解决Mac安装thrift因bison报错的问题
2018/05/17 Javascript
使用Node.js实现一个多人游戏服务器引擎
2019/03/13 Javascript
详解Vue 项目中的几个实用组件(ts)
2019/10/29 Javascript
小程序中设置缓存过期的实现方法
2020/01/14 Javascript
利用React高阶组件实现一个面包屑导航的示例
2020/08/23 Javascript
[08:42]DOTA2每周TOP10 精彩击杀集锦vol.2
2014/06/25 DOTA
[00:43]拉比克至宝魔导师密钥展示
2018/12/20 DOTA
[01:47]2018年度DOTA2最佳教练-完美盛典
2018/12/16 DOTA
python 安装virtualenv和virtualenvwrapper的方法
2017/01/13 Python
Python3实现计算两个数组的交集算法示例
2019/04/03 Python
在django中自定义字段Field详解
2019/12/03 Python
django rest framework serializer返回时间自动格式化方法
2020/03/31 Python
Python使用os.listdir和os.walk获取文件路径
2020/05/21 Python
美国电子元器件分销商:Newark element14
2018/01/13 全球购物
交通事故调解协议书
2014/04/16 职场文书
铅球加油稿100字
2014/09/26 职场文书
在教室放鞭炮的检讨书
2014/09/28 职场文书
给老婆的道歉信
2015/01/20 职场文书
办公室禁烟通知
2015/04/23 职场文书
Redis5之后版本的高可用集群搭建的实现
2021/04/27 Redis