Nodejs爬取螞蜂窩文章的爬蟲以及搭建第三方服務器
如題,本項目用Nodejs實現了對螞蜂窩網站的爬取,並將數據儲存到MongoDB中,再以Express做服務器端,Angularjs做前端實現對數據的託管。
本項目Github地址:https://github.com/golmic/mafengwo-spider
本項目線上地址: http://mafengwo.lujq.me
本文介紹其中部分的技術細節。php
獲取數據
打開螞蜂窩網站,發現文章部分的數據是用Ajax獲取的,包括分頁也是,因此查看一下實際的請求路徑,爲http://www.mafengwo.cn/ajax/ajax_article.php?start=1
因此程序應該向這個php文件發送請求,用Nodejs的話直接http請求也是沒問題的,爲了代碼好看,我使用request庫封裝一下。html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
function getArticleList(pageNum) { request({ url: "http://www.mafengwo.cn/ajax/ajax_article.php?start=" + pageNum, headers: { 'User-Agent': 'Mozilla/5.0' } }, function(error, response, data) { var res = data.match(/i\\\/\d{7}/g); for (var i = 0; i < 12; i++) { articlesUrl[i] = res[i * 3].substr(3, 7); }; async.each(articlesUrl, getArticle, function(err) { console.log('err: ' + err); }); }); }
|
每頁是12篇文章,每篇文字都是(僞)靜態頁面,正則提取出其中的文章頁Url。
對每一個Url發送請求,拿到源碼。前端
1 2 3 4 5 6 7 8 9 10
|
function getArticle(urlNumber) { request({ url: "http://www.mafengwo.cn/i/" + urlNumber + ".html", headers: { 'User-Agent': 'Mozilla/5.0' } }, function(error, response, data) { |
接下來就是處理數據了。git
這一段代碼較長,可是目的是很是明確的,代碼也很清晰。咱們須要從這個頁面中拿到文章的標題,以及文章的內容。(文章做者以及發佈時間因爲時間關係我並無處理,不過也在代碼以及數據庫種預留了位置,這個同理很容易完成。)
來,咱們分析一下這段代碼。程序員
1 2 3 4
|
var title, content, creator, created;
|
先是正則獲取標題,而後把標題中的特殊符號作一下處理。github
而後在實際訪問螞蜂窩網站時發現大多數文章都配有背景音樂,那我也給加上好了。因而這一段代碼負責了獲取背景音樂的直鏈地址。ajax
獲取文章內容,在寫這段代碼時發現它的文章是有兩種dom結構的,因此分類處理了一下。sql
這一段代碼處理一下圖片,第一是文中的圖片由於螞蜂窩給定義了好多樣式,並不符合響應式規則,我把與響應式衝突的部分給處理了一下。
而後爲了美觀,把文章的第一張圖片做爲列表顯示時的特點圖片,記錄一下Url。mongodb
儲存數據
事實上整個的任務到此就能夠結束了。
數據庫
1 2 3 4
|
fs.writeFile("html/" + title + ".html", content, function(e) { if (e) throw e; console.log(title); });
|
把每篇文章做爲一個靜態文件保存。而後遍歷一下目錄獲得文章列表,憑藉Nginx對靜態資源強大的處理能力,這個網站也算是能夠完工了。
出於後期管理文檔以及把項目作得高大上點的目的,仍是採用NOsql的翹楚MongoDB做爲數據庫端的解決方案。
1 2 3 4 5 6
|
MongoClient.connect('mongodb://localhost:27017/mean', function(err, db) { assert.equal(null, err); insertArticle(db, title, content, creator, mp3url, imageUrl, created, function() { db.close(); }); });
|
把數據儲存到mean數據庫中,mean即MongoDB/Expressjs/Angularjs/Nodejs的js全棧實踐。
這樣數據的儲存就完成了。
搭建服務器
目錄結構
爲了後期維護以及合做開發,服務器端目錄的結構與命名規則也須要注意下。

數據結構
爲了後期管理員以及做者維護文章的考慮,數據庫中不止有Articles一個collection,還有一個users的collection。
結構分別以下:
文章:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
var ArticleSchema = new Schema({ created: { type: Date, default: Date.now }, title: { type: String, default: '', trim: true, required: 'Title cannot be blank' }, content: { type: String, default: '', trim: true }, mp3url:{ type:String }, imageUrl:{ type:String }, creator: { type: String, default: 'golmic', } });
|
用戶:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
var UserSchema = new Schema({ firstName: String, lastName: String, email: { type: String, |
Nodejs驅動下,很容易實現對文章以及用戶的CRUD操做。這裏只展現了對文章操做的代碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
exports.list = function(req, res) { Article.find().sort('-created').exec(function(err, articles) { if (err) { return res.status(400).send({ message: getErrorMessage(err) }); } else { for(var i in articles){ articles[i].content=''; }; res.json(articles); } }); }; exports.read = function(req, res) { res.json(req.article); }; exports.update = function(req, res) { var article = req.article; article.title = req.body.title; article.content = req.body.content; article.save(function(err) { if (err) { return res.status(400).send({ message: getErrorMessage(err) }); } else { res.json(article); } }); }; exports.delete = function(req, res) { var article = req.article; article.remove(function(err) { if (err) { return res.status(400).send({ message: getErrorMessage(err) }); } else { res.json(article); } }); };
|
路由規則
首頁爲文章列表,而後每篇文章有一個url。前端規則很容易,另外爲了符合RESTful API的要求,後端須要提供對CRUD操做的API。文章部分路由規則以下:
1 2 3 4 5 6 7 8 9 10
|
module.exports = function(app) { app.route('/api/articles') .get(articles.list) .post(users.requiresLogin, articles.create); app.route('/api/articles/:articleId') .get(articles.read) .put(users.requiresLogin, articles.hasAuthorization, articles.update) .delete(users.requiresLogin, articles.hasAuthorization, articles.delete); app.param('articleId', articles.articleByID); };
|
用戶部分同理.
前端路由由Angular控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
angular.module('articles').config(['$routeProvider', function($routeProvider) { $routeProvider. when('/', { templateUrl: 'articles/views/list-articles.client.view.html' }). when('/articles/create', { templateUrl: 'articles/views/create-article.client.view.html' }). when('/articles/:articleId', { templateUrl: 'articles/views/view-article.client.view.html' }). when('/articles/:articleId/edit', { templateUrl: 'articles/views/edit-article.client.view.html' }); } ]);
|
前端用ngResource模塊處理資源位置:
1 2 3 4 5 6 7 8 9 10
|
angular.module('articles').factory('Articles', ['$resource', function($resource) { |
用戶管理
文章做者以及管理員能夠修改或者刪除文章,邏輯代碼見路由部分,實現代碼見CRUD部分。
最終效果


其它
其它未盡技術細節請發issue或郵件交流。
全能程序員交流QQ羣290551701,羣內程序員都是來自,百度、阿里、京東、小米、去哪兒、餓了嗎、藍港等高級程序員 ,擁有豐富的經驗。加入咱們,直線溝通技術大牛,最佳的學習環境,瞭解業內的一手的資訊。若是你想結實大牛,那 就加入進來,讓大牛帶你超神!