利用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 相关文章推荐
JQuery AJAX实现目录浏览与编辑的代码
Oct 21 Javascript
jquery 表单下所有元素的隐藏
Jul 25 Javascript
javascript之通用简单的table选项卡实现(二)
May 09 Javascript
js从Cookies里面取值的简单实现
Jun 30 Javascript
用jquery实现动画跳到顶部和底部(这个比较简单)
Sep 01 Javascript
JS实现在网页中弹出一个输入框的方法
Mar 03 Javascript
JavaScript中两个字符串的匹配
Jun 08 Javascript
9种改善AngularJS性能的方法
Nov 28 Javascript
jquery点击回车键实现登录效果并默认焦点的方法
Mar 09 jQuery
vue两组件间值传递 $router.push实现方法
May 15 Javascript
JavaScript oncopy事件用法实例解析
May 13 Javascript
基于VSCode调试网页JavaScript代码过程详解
Jul 20 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
php实现的获取网站备案信息查询代码(360)
2013/09/23 PHP
php实现源代码加密的方法
2015/07/11 PHP
PHP的全局错误处理详解
2016/04/25 PHP
thinkphp5框架前后端分离项目实现分页功能的方法分析
2019/10/08 PHP
javascript字典探测用户名工具
2006/10/05 Javascript
javascript中substr,substring,slice.splice的区别说明
2010/11/25 Javascript
jQuery读取XML文件内容的方法
2015/03/09 Javascript
jquery转盘抽奖功能实现
2015/11/13 Javascript
JavaScript保留关键字汇总
2015/12/01 Javascript
如何解决jQuery EasyUI 已打开Tab重新加载问题
2016/12/19 Javascript
JavaScript实现的鼠标跟随特效示例【2则实例】
2018/12/22 Javascript
小程序:授权、登录、session_key、unionId的详解
2019/05/15 Javascript
bootstrap中的导航条实例代码详解
2019/05/20 Javascript
javascript获取元素的计算样式
2019/05/24 Javascript
小程序如何支持使用 async/await详解
2019/09/12 Javascript
vue+导航锚点联动-滚动监听和点击平滑滚动跳转实例
2019/11/13 Javascript
理解Proxy及使用Proxy实现vue数据双向绑定操作
2020/07/18 Javascript
[00:10]DOTA2全国高校联赛 以DOTA2会友
2018/05/30 DOTA
python opencv实现切变换 不裁减图片
2018/07/26 Python
python opencv读mp4视频的实例
2018/12/07 Python
Python实现字符型图片验证码识别完整过程详解
2019/05/10 Python
Python循环结构的应用场景详解
2019/07/11 Python
利用pyecharts实现地图可视化的例子
2019/08/12 Python
基于torch.where和布尔索引的速度比较
2020/01/02 Python
Python参数传递实现过程及原理详解
2020/05/14 Python
理解Django 中Call Stack机制的小Demo
2020/09/01 Python
纯CSS3实现绘制各种图形实现代码详细整理
2012/12/26 HTML / CSS
Hurley官方网站:扎根于海滩生活方式的全球青年文化品牌
2020/05/18 全球购物
高中毕业自我鉴定
2013/12/19 职场文书
爱情寄语大全
2014/04/09 职场文书
党员批评与自我批评思想汇报
2014/10/08 职场文书
大学生团员个人总结
2015/02/14 职场文书
网络营销实训总结
2015/08/03 职场文书
小学作文之描写天气
2019/08/15 职场文书
Python类方法总结讲解
2021/07/26 Python
基于Python编写一个监控CPU的应用系统
2022/06/25 Python