最近正在学习node.js,就像搞一些东西来玩玩,于是这个简单的爬虫就诞生了。
npm init
初始化项目直接使用npm install express cheerio superagent eventproxy
来安装依赖包,当然你也可以用别的方法。
node-spider-csdn
├─ .gitignore
├─ node_modules
├─ README.md
├─ index.js 项目入口
├─ package-lock.json
├─ package.json
└─ routes
└─ csdn.js 爬虫主要代码
在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了。
随便找一个人的博客,经过观察发现:
https://blog.csdn.net/l1028386804/article/list/2?t=1
然后我们通过开发者工具查看文章列表结构,可以发现:
article-item-box
的盒子中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()
来查看获取的网址是否正确
// ...
router.get('/csdn/:name', function (req, res) {
const name = req.params.name;
getArticleNum(`https://blog.csdn.net/${name}`, function (num) {
let pages = [];
let articleData = []; // 保存所有文章数据
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.js写个爬虫
【nodeJS爬虫】前端爬虫系列 -- 小爬「博客园」
10分钟教你撸一个nodejs爬虫系统
node.js 学习笔记004:使用eventproxy控制并发