其實寫這篇文章,我是很忐忑的,因為爬取的內容就是博客園的,萬一哪個頑皮的小伙伴拿去干壞事,我豈不成共犯了?
好了,進入主題。
首先,爬蟲需要用到的模塊有:
express
ejs
superagent (nodejs里一個非常方便的客戶端請求代理模塊)
cheerio (nodejs版的jQuery)
前台布局使用bootstrap
分頁插件使用 twbsPagination.js
完整的爬蟲代碼,在我的github中可以下載。主要的邏輯代碼在 router.js 中。
1. 爬取某個欄目第1頁的數據
分析過程:
打開博客園的主頁: http://www.cnblogs.com/
左側導航欄里顯示了所有欄目的分類信息,可以在開發者工具中獲取查看這些信息.
每個欄目的URL也很有規律,都是 www.cnblogs.com/cate/欄目名稱。 根據這個URL就可以爬取某個欄目第1頁的博文了~
下面貼出代碼:
app.js (入口文件)
1 // 載入模塊 2 var express = require('express'); 3 var app = express(); 4 var router = require('./router/router'); 5 6 // 設置模板引擎 7 app.set('view engine', 'ejs'); 8 9 // 靜態資源中間件 10 app.use(express.static('./public')); 11 12 // 博客園 13 app.get('/cnblogs', router.cnblogs); 14 // 欄目 15 app.get('/cnblogs/cate/:cate/', router.cnblogs_cate); 16 17 18 app.listen(1314, function(err){ 19 if(err) console.log('1314端口被占用'); 20 });
router.js
var request = require('superagent'); var cheerio = require('cheerio'); // 欄目 var cate = [ 'java', 'cpp', 'php', 'delphi', 'python', 'ruby', 'web', 'javascript', 'jquery', 'html5' ]; // 顯示頁面 exports.cnblogs = function(req, res){ res.render('cnblogs', { cate: cate }); }; // 爬取欄目數據 exports.cnblogs_cate = function(req, res){ // 欄目 var cate = req.params['cate']; request .get('http://www.cnblogs.com/cate/' + cate) .end(function(err, sres){ var $ = cheerio.load(sres.text); var article = []; $('.titlelnk').each(function(index, ele){ var ele = $(ele); var href = ele.attr('href'); // 博客鏈接 var title = ele.text(); // 博客內容 article.push({ href: href, title: title }); }); res.json({ title: cate, cnblogs: article }); }); };
cnblogs.ejs
只貼出核心代碼
1 <div class="col-lg-6"> 2 <select class="form-control" id="cate"> 3 <option value="0">請選擇分類</option> 4 <% for(var i=0; i<cate.length; i++){ %> 5 <option value="<%= cate[i]%>"><%= cate[i]%></option> 6 <% } %> 7 </select> 8 </div>
JS模板
<script type="text/template" id="cnblogs"> <ul class="list-group"> <li class="list-group-item"> <a href="{{= href}}" target="_blank">{{= title}}</a> </ul> </script>
Ajax請求
$('#cate').on('change', function(){ var cate = $(this).val(); if(cate == 0) return; $('.artic').html(''); $.ajax({ url: '/cnblogs/cate/' + cate, type: 'GET', dataType: 'json', success: function(data){ var cnblogs = data.cnblogs; for(var i=0; i<cnblogs.length; i++){ var compiled = _.template($('#cnblogs').html()); var art = compiled(cnblogs[i]); $('.artic').append(art); } } }); });
輸入: http://localhost:1314/cnblogs/ ,可以看到, 成功獲取javascript下第1頁數據。
2. 分頁功能
以 http://www.cnblogs.com/cate/javascript/ 為例:
首先,分頁的數據是Ajax調用后端接口返回的。
chrome的開發者工具中,可以看到,分頁時,會向服務器發送兩個請求,
PostList.aspx 請求具體某頁的數據.
load.aspx 返回分頁字符串.
我們重點分析 PostList.aspx 這個接口:
可以發現 請求方式是POST。
問題的重點是POST請求的數據是如何組裝的?
分析源碼,發現每個分頁字符串都綁定了一個事件 -- aggSite.loadCategoryPostList()
查看頁面源碼,發現這個函數定義在 aggsite.js 文件里.
也就是下面這個函數.
重點是這行代碼, 使用Ajax向后端發送請求.
this.loadPostList("/mvc/AggSite/" + aggSiteModel.ItemListActionName + ".aspx").
分析loadPostList 函數,可以發現POST的數據是變量aggSiteModel的值.
而 aggSiteModel 在頁面中的定義:
至此前端的分析告一段落。 我們要做的,就是使用nodejs,模擬瀏覽器發送請求。
router.js
1 exports.cate_page = function(req, res){ 2 3 var cate = req.query.cate; 4 var page = req.query.page; 5 6 var url = 'http://www.cnblogs.com/cate/' + cate; 7 8 request 9 .get(url) 10 .end(function(err, sres){ 11 12 // 構造POST請求的參數 13 var $ = cheerio.load(sres.text); 14 var post_data_str = $('#pager_bottom').prev().html().trim(); 15 var post_data_obj = JSON.parse(post_data_str.slice(post_data_str.indexOf('=')+2, -1)); 16 17 // 分頁接口 18 var page_url = 'http://www.cnblogs.com/mvc/AggSite/PostList.aspx'; 19 // 修改當前頁 20 post_data_obj.PageIndex = page; 21 22 request 23 .post(page_url) 24 .set('origin', 'http://www.cnblogs.com') // 偽造來源 25 .set('referer', 'http://www.cnblogs.com/cate/'+cate+'/') // 偽造referer 26 .send(post_data_obj) // POST數據 27 .end(function(err, ssres){ 28 var article = []; 29 var $$ = cheerio.load(ssres.text); 30 $$('.titlelnk').each(function(index, ele){ 31 var ele = $$(ele); 32 var href = ele.attr('href'); 33 var title = ele.text(); 34 article.push({ 35 href: href, 36 title: title 37 }); 38 }); 39 res.json({ 40 title: cate, 41 cnblogs: article 42 }); 43 }); 44 }); 45 };
cate.ejs 分頁代碼
1 $('.pagination').twbsPagination({ 2 totalPages: 20, // 默認顯示20頁 3 startPage: 1, 4 visiblePages: 5, 5 initiateStartPageClick: false, 6 first: '首頁', 7 prev: '上一頁', 8 next: '下一頁', 9 last: '尾頁', 10 onPageClick: function(evt, page){ 11 $.ajax({ 12 url: '/cnblogs/cate_page?cate=' + cate + '&page=' + page, 13 type: 'GET', 14 dataType: 'json', 15 success: function(data){ 16 $('.artic').html(''); 17 var cnblogs = data.cnblogs; 18 for(var i=0; i<cnblogs.length; i++){ 19 var compiled = _.template($('#cnblogs').html()); 20 var art = compiled(cnblogs[i]); 21 $('.artic').append(art); 22 } 23 } 24 }); 25 } 26 });
輸入:http://localhost:1314/cnblogs/cate,可以看到:
javascript欄目第1頁數據
第2頁數據
后話
至此,一個簡單的爬蟲就完成了。 其實爬蟲本身並不難,難點在於分析頁面結構,和一些業務邏輯的處理。
完整的代碼,我已經放在github上,歡迎starn(☆▽☆)。
由於是第一次寫技術類的博客,文筆有限,才學疏淺,若有不正確的地方,歡迎廣大博友指正。
參考資料: