本爬蟲將從網站爬取排名前幾的網站,具體前幾名能夠具體設置,並分別爬取他們的主頁,檢查是否引用特定庫。html
github地址前端
所用到的node主要模塊node
express 不用多說jquery
request http模塊git
cheerio 運行在服務器端的jQuerygithub
node-inspector node調試模塊web
node-dev 修改文件後自動重啓app算法
在任意一個文件夾,執行node-inspector
,經過打開特定頁面,在頁面上進行調試,而後運行app,使用node-dev app.js
來自動重啓應用。數據庫
因爲請求是異步執行的,和分別返回3個頁面的數據,這裏只爬取了50個網站,一個頁面有20個,因此有3頁,經過循環裏套request請求,來實現。express
經過添加請求頭能夠實現基本的反爬蟲
處理數據的方法都寫在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) }
仔細觀察代碼,你會發現,處理數據的方法使用了以下的多層回調,也能夠不使用回調,寫在一個函數內部;由於,每層都要使用上一層的數據,形成了這樣的寫法。
function f1(data1){ f2(data1); } function f2(data2){ f3(data2); } function f3(data3){ f4(data4); }
因爲獲取頁面庫,首先須要獲取到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('.'));
這部分也花了一點時間,才搞定,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); }
存儲數據的邏輯是先獲取全部的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 + '條數據到數據庫'); }
本爬蟲前端使用了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); } }) }
$(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(); })
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;
經過這個小爬蟲,學習到不少知識,例如爬蟲的反爬蟲有哪些策越,意識到node.js的異步執行特性,先後端是怎麼進行交互的。同時,也意識到有一些方面的不足,後面還須要繼續改進,歡迎你們的相互交流。