利用node.js爬取指定排名網(wǎng)站的JS引用庫(kù)詳解
前言
本文給大家介紹的爬蟲將從網(wǎng)站爬取排名前幾的網(wǎng)站,具體前幾名可以具體設(shè)置,并分別爬取他們的主頁(yè),檢查是否引用特定庫(kù)。下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹:
所用到的node主要模塊
- express 不用多說(shuō)
- request http模塊
- cheerio 運(yùn)行在服務(wù)器端的jQuery
- node-inspector node調(diào)試模塊
- node-dev 修改文件后自動(dòng)重啟app
關(guān)于調(diào)試Node
在任意一個(gè)文件夾,執(zhí)行node-inspector,通過(guò)打開特定頁(yè)面,在頁(yè)面上進(jìn)行調(diào)試,然后運(yùn)行app,使用node-dev app.js來(lái)自動(dòng)重啟應(yīng)用。
所碰到的問(wèn)題
1. request請(qǐng)求多個(gè)頁(yè)面
由于請(qǐng)求是異步執(zhí)行的,和分別返回3個(gè)頁(yè)面的數(shù)據(jù),這里只爬取了50個(gè)網(wǎng)站,一個(gè)頁(yè)面有20個(gè),所以有3頁(yè),通過(guò)循環(huán)里套request請(qǐng)求,來(lái)實(shí)現(xiàn)。
通過(guò)添加請(qǐng)求頭可以實(shí)現(xiàn)基本的反爬蟲
處理數(shù)據(jù)的方法都寫在analyData()里面,造成后面的數(shù)據(jù)重復(fù)存儲(chǔ)了,想了很久,才想到一個(gè)解決方法,后面會(huì)寫到是怎么解決的。
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. 多層回調(diào)
仔細(xì)觀察代碼,你會(huì)發(fā)現(xiàn),處理數(shù)據(jù)的方法使用了如下的多層回調(diào),也可以不使用回調(diào),寫在一個(gè)函數(shù)內(nèi)部;因?yàn)?,每層都要使用上一層的?shù)據(jù),造成了這樣的寫法。
function f1(data1){
f2(data1);
}
function f2(data2){
f3(data2);
}
function f3(data3){
f4(data4);
}
3. 正則獲取JS庫(kù)
由于獲取頁(yè)面庫(kù),首先需要獲取到script的src屬性,然后通過(guò)正則來(lái)實(shí)現(xiàn)字符串匹配。
<script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
獲取到的script可能是上面這樣的,由于庫(kù)名的命名真是各種各樣,后來(lái)想了一下,因?yàn)槲募怯?js結(jié)尾的,所以就以點(diǎn)號(hào)為結(jié)尾,然后把點(diǎn)號(hào)之前的字符截取下來(lái),這樣獲得了庫(kù)名,代碼如下。
var reg = /[^\/\\]+$/g;
var libName = jsLink.match(reg).join('');
var libFilter = libName.slice(0,libName.indexOf('.'));
4.cheerio模塊獲取JS引用鏈接
這部分也花了一點(diǎn)時(shí)間,才搞定,cheerio獲取DOM的方法和jQuery是一樣的,需要對(duì)返回的DOM對(duì)象進(jìn)行查看,就可以看到對(duì)象里隱藏好深的href屬性,方法大同小異,你也可以使用其他選擇器,選擇到script標(biāo)簽
var $ = cheerio.load(body);
var scriptFile = $('script').toArray();
scriptFile.forEach(function(item,index){
if (item.attribs.src != null) {
obtainLibName(item.attribs.src,index);
}
5.存儲(chǔ)數(shù)據(jù)到數(shù)據(jù)庫(kù)
存儲(chǔ)數(shù)據(jù)的邏輯是先獲取所有的script信息,然后push到一個(gè)緩存數(shù)組,由于push后面,緊跟著存儲(chǔ)到數(shù)據(jù)庫(kù)的方法,這兩個(gè)方法都寫在循環(huán)里面的,例如爬取5個(gè)網(wǎng)站,每個(gè)網(wǎng)站存儲(chǔ)一次,后面也會(huì)跟著存儲(chǔ),造成數(shù)據(jù)重復(fù)存儲(chǔ)。解決方法是存儲(chǔ)數(shù)據(jù)的一般邏輯是先查,再存,這個(gè)查比較重要,查詢的方法也有多種,這里主要是根據(jù)庫(kù)名來(lái)查找唯一的數(shù)據(jù)對(duì)象,使用findOne方法。注意,由于node.js是異步執(zhí)行的,這里的閉包,每次只傳一個(gè)i值進(jìn)去,執(zhí)行存儲(chǔ)的操作。
// 將緩存數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)
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('保存數(shù)據(jù)出錯(cuò)' + err);
});
}
})
})(i)
}
console.log('一共存儲(chǔ)' + libObj.length + '條數(shù)據(jù)到數(shù)據(jù)庫(kù)');
}
6.分頁(yè)插件
本爬蟲前端使用了bootstrap.paginator插件,主要是前臺(tái)分頁(yè),返回?cái)?shù)據(jù),根據(jù)點(diǎn)擊的頁(yè)數(shù),來(lái)顯示對(duì)應(yīng)的數(shù)據(jù),后期考慮使用AJAX請(qǐng)求的方式來(lái)實(shí)現(xiàn)翻頁(yè)的效果,這里的注意項(xiàng),主要是最后一頁(yè)的顯示,最好前面做個(gè)判斷,因?yàn)榉祷氐臄?shù)據(jù),不一定剛好是頁(yè)數(shù)的整數(shù)倍
function _paging(libObj) {
var ele = $('#page');
var pages = Math.ceil(libObj.length/20);
console.log('總頁(yè)數(shù)' + 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 "首頁(yè)";
case "prev": return "上一頁(yè)";
case "next": return "下一頁(yè)";
case "last": return "末頁(yè)";
case "page": return page;
}
},
onPageClicked: function(event, originalEvent, type, page){
// console.log('當(dāng)前選中第:' + page + '頁(yè)');
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('請(qǐng)輸入庫(kù)名~');
return;
}
$.post(
'/queryLib',
{
libName: inputLibName,
},
function(data){
if(data.length == 0){
alert('沒(méi)有查詢到名為' + inputLibName + '的庫(kù)');
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 + '條數(shù)據(jù)');
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);// 點(diǎn)擊顯示按鈕,顯示前20項(xiàng)數(shù)據(jù)
_paging(data);
}
)
});
}
//翻頁(yè)器
function _paging(libObj) {
var ele = $('#page');
var pages = Math.ceil(libObj.length/20);
console.log('總頁(yè)數(shù)' + 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 "首頁(yè)";
case "prev": return "上一頁(yè)";
case "next": return "下一頁(yè)";
case "last": return "末頁(yè)";
case "page": return page;
}
},
onPageClicked: function(event, originalEvent, type, page){
// console.log('當(dāng)前選中第:' + page + '頁(yè)');
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')
/* 顯示主頁(yè) */
router.get('/', function(req, res, next) {
res.render('index');
});
// 顯示庫(kù)
router.get('/getLibs',function(req,res,next){
JsLib.find({})
.sort({'libsNum': -1})
.exec(function(err,data){
res.json(data);
})
})
// 庫(kù)的查詢
router.post('/queryLib',function(req,res,next){
var libName = req.body.libName;
JsLib.find({
name: libName
}).exec(function(err,data){
if (err) console.log('查詢出現(xiàn)錯(cuò)誤' + 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鏈接存為數(shù)組
console.log('網(wǎng)站爬取中``')
for (var i = 0; i < 10; i++) { // ***這里后面要改,默認(rèn)爬取前10名
var url = sitesArr[i].attribs.href;
sites.push(url);//保存網(wǎng)址,添加wwww前綴
}
console.log(sites);
console.log('一共爬取' + sites.length +'個(gè)網(wǎng)站');
console.log('存儲(chǔ)數(shù)據(jù)中...')
getScript(sites);
}
// 獲取JS庫(kù)文件地址
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('出現(xiàn)錯(cuò)誤: '+err);
var $ = cheerio.load(body);
var scriptFile = $('script').toArray();
callback(scriptFile,options.url);
})
})(j,storeLib)
};
function storeLib(scriptFile,url){
flag++;// 是否存儲(chǔ)數(shù)據(jù)的標(biāo)志
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長(zhǎng)度為url的長(zhǎng)度才向src和數(shù)據(jù)庫(kù)里存儲(chǔ)數(shù)據(jù),防止重復(fù)儲(chǔ)存
// calcNum(src);//存儲(chǔ)數(shù)據(jù)到數(shù)據(jù)庫(kù) // ***這里后面要改,默認(rèn)爬取前10名
var libSrc = calcNum(src);
store2db(libSrc);
}
})(flag,urls.length,src)
}
}// getScript END
// 將緩存數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫(kù)
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('保存數(shù)據(jù)出錯(cuò)' + err);
});
}
})
})(i)
}
console.log('一共存儲(chǔ)' + libObj.length + '條數(shù)據(jù)到數(shù)據(jù)庫(kù)');
}
// JS庫(kù)排序算法
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;
源碼下載
后記
通過(guò)這個(gè)小爬蟲,學(xué)習(xí)到很多知識(shí),例如爬蟲的反爬蟲有哪些策越,意識(shí)到node.js的異步執(zhí)行特性,前后端是怎么進(jìn)行交互的。同時(shí),也意識(shí)到有一些方面的不足,后面還需要繼續(xù)改進(jìn),歡迎大家的相互交流。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
node.js中的path.delimiter方法使用說(shuō)明
這篇文章主要介紹了node.js中的path.delimiter方法使用說(shuō)明,本文介紹了path.delimiter的方法說(shuō)明、語(yǔ)法、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12
解決npm管理員身份install時(shí)出現(xiàn)權(quán)限的問(wèn)題
下面小編就為大家分享一篇解決npm管理員身份install時(shí)出現(xiàn)權(quán)限的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
node.js實(shí)現(xiàn)微信開發(fā)之獲取用戶授權(quán)
這篇文章主要介紹了node.js實(shí)現(xiàn)微信開發(fā)之獲取用戶授權(quán),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Node.js實(shí)現(xiàn)壓縮與解壓數(shù)據(jù)
這篇文章介紹了Node.js實(shí)現(xiàn)壓縮與解壓數(shù)據(jù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
Node.js從字符串生成文件流的實(shí)現(xiàn)方法
這篇文章主要介紹了Node.js從字符串生成文件流的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
詳解nodejs微信公眾號(hào)開發(fā)——1.接入微信公眾號(hào)
本篇文章主要介紹了詳解nodejs微信公眾號(hào)開發(fā)——1.接入微信公眾號(hào),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-04-04
Node.js之網(wǎng)絡(luò)通訊模塊實(shí)現(xiàn)淺析
本篇文章主要介紹了Node.js之網(wǎng)絡(luò)通訊模塊實(shí)現(xiàn)淺析,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-04-04
基于Alpine Linux構(gòu)建前端node-web鏡像步驟詳解
這篇文章主要為大家介紹了基于Alpine Linux構(gòu)建前端node-web鏡像步驟詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11

