使用node爬虫,爬取指定排名网站的JS引用库
2021-04-26 17:29
前期准备
本爬虫将从网站爬取排名前几的网站,具体前几名可以具体设置,并分别爬取他们的主页,检查是否引用特定库。
github地址
所用到的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
2. 多层回调
仔细观察代码,你会发现,处理数据的方法使用了如下的多层回调,也可以不使用回调,写在一个函数内部;因为,每层都要使用上一层的数据,造成了这样的写法。
function f1(data1){
f2(data1);
}
function f2(data2){
f3(data2);
}
function f3(data3){
f4(data4);
}
3. 正则获取JS库
由于获取页面库,首先需要获取到script的src属性,然后通过正则来实现字符串匹配。
获取到的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
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 ‘;
pHtml += (i+1) + ‘
‘;
pHtml += libObj[i].name + ‘
‘;
pHtml += libObj[i].libsNum + ‘ ‘;
}
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 ‘;
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)
var libHtml = ‘‘;
for (var i = 0; i ‘;
libHtml += (i+1) + ‘
‘;
libHtml += data[i].name + ‘
‘;
libHtml += data[i].libsNum + ‘ ‘;
}
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 ‘;
pHtml += (i+1) + ‘
‘;
pHtml += libObj[i].name + ‘
‘;
pHtml += libObj[i].libsNum + ‘ ‘;
}
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
后记
通过这个小爬虫,学习到很多知识,例如爬虫的反爬虫有哪些策越,意识到node.js的异步执行特性,前后端是怎么进行交互的。同时,也意识到有一些方面的不足,后面还需要继续改进,欢迎大家的相互交流。
下一篇:IMWEB 前端面试题汇总
文章标题:使用node爬虫,爬取指定排名网站的JS引用库
文章链接:http://soscw.com/index.php/essay/79868.html