最近正在學習node.js,就像搞一些東西來玩玩,于是這個簡單的爬蟲就誕生了,
準備作業
- node.js爬蟲肯定要先安裝node.js環境
- 創建一個檔案夾
- 在該檔案夾打開命令列,執行
npm init初始化專案
正式開始
安裝依賴
- express 用來搭建一個簡單http服務器,也可以使用node原生api
- cheerio 相當于node版的jQuery,用來決議頁面
- superagent 用來請求目標頁面
- eventproxy 解決同時處理多個頁面的問題
直接使用npm install express cheerio superagent eventproxy 來安裝依賴包,當然你也可以用別的方法,
創建建好目錄
node-spider-csdn
├─ .gitignore
├─ node_modules
├─ README.md
├─ index.js 專案入口
├─ package-lock.json
├─ package.json
└─ routes
└─ csdn.js 爬蟲主要代碼
創建一個Http服務器
在index.js檔案中,實體化一個express物件,啟動一個Http服務
const express = require('express');
const app = express();
app.listen(3000, function() {
console.log('running in http://127.0.0.1:3000');
});
這樣就啟動了一個簡單的Http本地服務,執行node index.js后通過http://127.0.0.1:3000就可以訪問到這個服務器,有關Express的更多內容可以參考官方檔案,
撰寫csdn.js模塊
先引入csdn.js檔案并且添加路由
const express = require('express');
const csdn = require('./routes/csdn.js');
const app = express();
app.use(csdn);
app.listen(3000, function() {
console.log('running in http://127.0.0.1:3000');
});
然后開始撰寫csdn.js
整體結構
// 引入需要的第三方包
const cheerio = require('cheerio');
const superagent = require('superagent');
const express = require('express');
const eventproxy = require('eventproxy');
const router = express.Router(); // 掛載路由
const ep = new eventproxy();
router.get('/csdn/:name', function(req, res) {
const name = req.params.name; // 用戶id
// 具體實作...
});
// 將router暴露出去
module.exports = router;
分析頁面
整體結構寫好后就要開始分析CSDN用戶文章頁面的HTML了,
隨便找一個人的博客,經過觀察發現:
- 原創文章的完整url:
https://blog.csdn.net/l1028386804/article/list/2?t=1 - CSDN的文章串列是40篇一頁
- 分頁控制元件是動態生成的,所以無法直接通過HTML決議獲得


然后我們通過開發者工具查看文章串列結構,可以發現:
- 文章資訊都在類名為
article-item-box的盒子中 - id資訊在該盒子的
data-articleid屬性中
還有一些其他的資訊都很容易能查到,比如博主原創文章總數值等,可以在以后需要的時候再過來查看,

獲取所有文章頁面
因為無法直接獲得分頁資訊,所以我們通過文章總數 / 每頁文章數來獲取所有的頁面,
首先獲取文章的總數:
/**
* 獲取總文章數目
* @param {String} url 頁面路徑
* @param {Function} callback 回呼
*/
let getArticleNum = function (url, callback) {
superagent.get(url).end(function (err, html) {
if (err) {
console.log(`err = ${err}`);
}
let $ = cheerio.load(html.text);
let num = parseInt($('.data-info dl').first().attr('title'));
callback(num);
});
};
然后利用簡單的回圈獲取所有文章頁面:
// ...
router.get('/csdn/:name', function(req, res) {
const name = req.params.name;
getArticleNum(`https://blog.csdn.net/${name}`, function (num) {
let pages = []; // 保存要抓取的頁面
let pageNum = Math.ceil(num / 40); // 計算一共有多少頁面
for (let i = 1; i <= pageNum; i++) {
pages.push(`https://blog.csdn.net/${name}/article/list/${i}?t=1`);
}
// ...
});
});
// ...
我們可以通過console.log()或者res.send()來查看獲取的網址是否正確
遍歷獲取所有頁面的HTML
// ...
router.get('/csdn/:name', function (req, res) {
const name = req.params.name;
getArticleNum(`https://blog.csdn.net/${name}`, function (num) {
let pages = [];
let articleData = https://www.cnblogs.com/xueyubao/p/[]; // 保存所有文章資料
let pageNum = Math.ceil(num / 40); // 計算一共有多少頁面
for (let i = 1; i <= pageNum; i++) {
pages.push(`https://blog.csdn.net/${name}/article/list/${i}?t=1`);
}
// 獲取所有頁面的文章資訊
pages.forEach(function (targetUrl) {
superagent.get(targetUrl).end(function (err, html) {
if (err) {
console.log(`err ${err}`);
}
let $ = cheerio.load(html.text);
// 當前頁面的文章串列
let articlesHtml = $('.article-list .article-item-box');
// 遍歷當前頁的文章串列
for (let i = 0; i < articlesHtml.length; i++) {
// 決議獲取文章資訊
// push到articleData中
// ...
}
});
});
});
});
// ...
決議文章資訊
因為獲取到的有些文本中空格太多,所以需要用到正則運算式來去除多余的空格,
cheerio對于Document的操作和jQuery基本一樣,所以有前端基礎的可以很輕松上手,
/**
* 決議html字串,獲取文章資訊
* @param {String} html 包含文章資訊的html
* @param {Number} index 文章索引
*/
let analysisHtml = function (html, index) {
return {
id: html.eq(index).attr('data-articleid'),
title: html.eq(index).find('h4 a').text().replace(/\s+/g, '').slice(2),
link: html.eq(index).find('a').attr('href'),
abstract: html.eq(index).find('.content a').text().replace(/\s+/g, ''),
shared_time: html.eq(index).find('.info-box .date').text().replace(/\s+/, ''),
read_count: html.eq(index).find('.info-box .read-num .num').first().text().replace(/\s+/, ''),
comment_count: html.eq(index).find('.info-box .read-num .num').last().text().replace(/\s+/, '')
};
};
// ...
// 遍歷當前頁的文章串列
for (let i = 0; i < articlesHtml.length; i++) {
let article = analysisHtml(articlesHtml, i);
articleData.push(article);
// ...
}
// ...
我們已經獲取到所有文章的資訊資料,但是因為獲取各個頁面的文章時是并發異步進行的,所以要同時利用這些資料特殊的方法,
處理并發異步操作
這里我使用的是“計數器”eventproxy,還有很多其他的方法都可以解決這個問題,
// ...
pages.forEach(function (targetUrl) {
superagent.get(targetUrl).end(function (err, html) {
if (err) {
console.log(`err ${err}`);
}
let $ = cheerio.load(html.text);
let articlesHtml = $('.article-list .article-item-box');
for (let i = 0; i < articlesHtml.length; i++) {
let article = analysisHtml(articlesHtml, i);
articleData.push(article);
ep.emit('blogArtc', article); // 計數器
}
});
});
// 當所有'blogArtc'完成后,觸發回呼
ep.after('blogArtc', num, function (data) {
res.json({
status_code: 0,
data: data
});
});
// ...
這樣,一個簡單的node爬蟲就寫好了,執行node index.js啟動服務后,在瀏覽器中輸入http://127.0.0.1:3000/csdn/xxxx就可以獲得xxxx(這是id)的全部文章了,
完整代碼
- node-spider-csdn
參考文章
-
分分鐘教你用node.js寫個爬蟲
-
【nodeJS爬蟲】前端爬蟲系列 -- 小爬「博客園」
-
10分鐘教你擼一個nodejs爬蟲系統
-
node.js 學習筆記004:使用eventproxy控制并發
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/149823.html
標籤:JavaScript
上一篇:01.JS語法規范、變數與常量
