插件简介
相信写过博客的都知道,每天会经常打开自己的主页无数次,尤其是写了一篇新文章,就为了看文章浏览量增长了多少,文章获得了多少个赞,有多少人评论(谁不想自己写的文章成为爆款呢~)
因此我特意做了一个Chrome插件,让用户更方便地实时查看自己在CSDN博客上的数据
我们先来看看最后的实现效果:
这个插件能显示两部分数据,一部分是用户个人数据,包括:总阅读数、文章数、排名、粉丝数,另一部分是个人博客成就数据,包括:点赞数、评论数、收藏数、分享数
插件显示的数据默认每1小时更新一次,用户可以自行修改数据更新时间
插件的UI第一版写的暂时比较简陋,可以后期版本再修改以及增加一些功能
在开发之前,我们需要会一些前置知识,包括:Node.js爬虫、Chrome插件开发
不会的同学也没关系,可以看我之前写过的博客,里面关于爬虫和插件开发写的非常详细:
都 2023 年了还不会 Node.js 爬虫?快学起来!
Node.js 爬虫只会 Cheerio?来试试 Puppeteer!
从零入门 Chrome 插件开发
好的,现在我们就来开动吧!
爬取CSDN个人数据
这里默认大家已经学过puppeteer,具体爬取的操作在上一篇文章中有很详细的解释
这里我们分析完网页结构之后直接来编写爬虫脚本:
// 无头浏览器模块
const puppeteer = require("puppeteer");// 目标页面
const crawlPage = "https://blog.csdn.net/weixin_46232841?spm=1000.2115.3001.5343";// 网页爬虫
async function crawler() {//创建实例const browser = await puppeteer.launch({//无浏览器界面启动headless: "new",//放慢浏览器执行速度,方便测试观察slowMo: 100,// 设置打开的浏览器窗口尺寸defaultViewport: { width: 960, height: 540 },});// 新开一个tab页面const page = await browser.newPage();// 加载目标页,在 500ms 内没有任何网络请求才算加载完await page.goto(crawlPage, { waitUntil: "networkidle0" });// 在无头浏览器页面dom环境,获取页面数据const myData = await page.evaluate(() => {let data = {};let name = "萌萌哒の瑞萌萌";let achievement = {};// 个人数据document.querySelectorAll(".user-profile-head-info-r-c ul").forEach((ele) => {const allReadNum = ele.querySelector("li:nth-child(1) .user-profile-statistics-views .user-profile-statistics-num").innerText;const articleNum = ele.querySelector("li:nth-child(2) .user-profile-statistics-num").innerText;const rank = ele.querySelector("li:nth-child(3) .user-profile-statistics-num").innerText;const fans = ele.querySelector("li:nth-child(4) .user-profile-statistics-num").innerText;data = {allReadNum,articleNum,rank,fans}});// 个人成就document.querySelectorAll(".aside-common-box-achievement").forEach((ele) => {const like = ele.querySelector("li:nth-child(1) div").innerText;const comment = ele.querySelector("li:nth-child(2) div").innerText;const favorite = ele.querySelector("li:nth-child(3) div").innerText;const share = ele.querySelector("li:nth-child(4) div").innerText;achievement = {like,comment,favorite,share}});return {name,data,achievement};});console.log(myData);// 关闭tab页await page.close();// 关闭实例await browser.close();
})
运行一下看看有没有获取到我们想要的数据吧:
完美!现在我们拿到了我们想要的数据,接下来要完成的需求就是:
- 将数据存储到json文件里,方便插件目录获取
- 定时爬取,每隔一小时爬取一次,及时更新json文件
解决办法:用 fs 模块的
fs.writeFile
来保存文件,用node-schedule
模块来实现定时爬取上代码!
// 无头浏览器模块
const puppeteer = require("puppeteer");
const schedule = require('node-schedule');
const fs = require('fs');// 目标页面
const crawlPage = "https://blog.csdn.net/weixin_46232841?spm=1000.2115.3001.5343";// 创建一个定时任务,每隔1小时执行一次
let job = schedule.scheduleJob('0 0 */1 * * *',// 网页爬虫
async function crawler() {//创建实例const browser = await puppeteer.launch({//无浏览器界面启动headless: "new",//放慢浏览器执行速度,方便测试观察slowMo: 100,// 设置打开的浏览器窗口尺寸defaultViewport: { width: 960, height: 540 },});// 新开一个tab页面const page = await browser.newPage();// 加载目标页,在 500ms 内没有任何网络请求才算加载完await page.goto(crawlPage, { waitUntil: "networkidle0" });// 在无头浏览器页面dom环境,获取页面数据const myData = await page.evaluate(() => {let data = {};let name = "萌萌哒の瑞萌萌";let achievement = {};// 个人数据document.querySelectorAll(".user-profile-head-info-r-c ul").forEach((ele) => {const allReadNum = ele.querySelector("li:nth-child(1) .user-profile-statistics-views .user-profile-statistics-num").innerText;const articleNum = ele.querySelector("li:nth-child(2) .user-profile-statistics-num").innerText;const rank = ele.querySelector("li:nth-child(3) .user-profile-statistics-num").innerText;const fans = ele.querySelector("li:nth-child(4) .user-profile-statistics-num").innerText;data = {allReadNum,articleNum,rank,fans}});// 个人成就document.querySelectorAll(".aside-common-box-achievement").forEach((ele) => {const like = ele.querySelector("li:nth-child(1) div").innerText;const comment = ele.querySelector("li:nth-child(2) div").innerText;const favorite = ele.querySelector("li:nth-child(3) div").innerText;const share = ele.querySelector("li:nth-child(4) div").innerText;achievement = {like,comment,favorite,share}});return {name,data,achievement};});// 将数据写入文件中fs.writeFile('../chrome/mycsdn.json', JSON.stringify(myData), function (err, data) {if (err) {throw err}console.log('文件保存成功,当前时间:' + new Date());})// 关闭tab页await page.close();// 关闭实例await browser.close();
})
最终实现效果:
到此我们就实现了定时爬取CSDN个人主页数据的操作,接下来我们一起来完成Chrome插件开发!
Chrome插件开发
先来创建一个 Chrome 插件项目叫 mycsdn:
mkdir mycsdn # 创建插件项目
cd mycsdn # 进入项目根目录
touch manifest.json # 在项目根目录中创建一个名为 manifest.json 的文件
接下来我们简单编写 manifest.json
文件,参数配置如下:
{"manifest_version": 2,"name": "CSDN个人数据","version": "1.0","description": "CSDN个人数据展示","permissions": ["activeTab"],"browser_action": {"default_title": "CSDN个人数据","default_popup": "popup/popup.html","default_icon": {"128": "icon/icon128.png"}}
}
然后我们在根目录创建popup文件夹,添加一个名为 popup.html 的文件,我们来完成点击弹出的页面结构:
<!DOCTYPE html>
<html>
<head><title>CSDN个人数据展示</title><meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1.0"><link rel="stylesheet" href="popup.css"><script src="popup.js"></script>
</head>
<body><div class="container"><div class="box"><div class="card-data content"><h2>个人数据</h2><p>总阅读数</p><p>文章数</p><p>排名</p><p>粉丝数</p></div></div><div class="box"><div class="card-data card-achievement content"><h2>个人成就</h2><p>点赞数</p><p>评论数</p><p>收藏数</p><p>分享数</p></div></div></div>
</body>
</html>
来点CSS装饰一下:
body {min-width: 400px;min-height: 300px;font-family: Arial, Helvetica, sans-serif;background-color: #f2f2f2;margin: 0;padding: 0;
}.container {max-width: 800px;margin: 0 auto;box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);padding: 10px 20px;background: radial-gradient(circle, rgba(238,174,202,1) 0%, rgba(148,187,233,1) 100%);
}.box {position: relative;height: 220px;align-items: center;transition: 0.5s;z-index: 1;display: flex;flex-wrap: wrap;align-items: center;}.box .content {position: relative;padding: 0px 40px 10px 40px;backdrop-filter: blur(10px);z-index: 1;transform: 0.5s;background-color: rgba(255, 255, 255, 0.8);border-radius: 20px;box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);overflow: hidden;margin-bottom: 0;font-size: 18px;color: #666;flex-grow: 1;text-align: right;
}.content::before {content: "";background-image: url("../bk1.jpg");background-repeat: no-repeat;background-size: cover;opacity: 0.5;filter: blur(1px);position: absolute;top: 0;left: 0;bottom: 0;right: 0;
}.box .content p {font-size: 14px;font-weight: bolder;color: black;
}.box .content h2 {font-size: 20px;margin-bottom: 10px;font-weight: bolder;color: black;
}
效果还不错,大家也可以根据自己的审美自己来设计页面
接下来我们来写JS,这里因为已经将数据存储在本地JSON文件中,所以我们用 XMLHttpRequest 对象来读取这个JSON文件,详细信息可以看一下注释:
// 当DOM加载完成时执行以下代码
document.addEventListener("DOMContentLoaded", function () {// 创建一个新的XMLHttpRequest对象let xhr = new XMLHttpRequest();// 指定要读取的文件路径xhr.open('GET', '../mycsdn.json', true);// 当请求完成时执行的回调函数xhr.onload = function() {if (xhr.status === 200) {// 将JSON字符串转换为JavaScript对象let data = JSON.parse(xhr.responseText);// 处理读取到的数据const allReadNum = document.querySelector("p:nth-of-type(1)");allReadNum.innerHTML = `总阅读数:${data.data.allReadNum}`;const articleNum = document.querySelector("p:nth-of-type(2)");articleNum.innerHTML = `文章数:${data.data.articleNum}`;const rank = document.querySelector("p:nth-of-type(3)");rank.innerHTML = `排名:${data.data.rank}`;const fans = document.querySelector("p:nth-of-type(4)");fans.innerHTML = `粉丝数:${data.data.fans}`;const like = document.querySelector(".card-achievement p:nth-of-type(1)");like.innerHTML = `点赞数:${data.achievement.like}`;const comment = document.querySelector(".card-achievement p:nth-of-type(2)");comment.innerHTML = `评论数:${data.achievement.comment}`;const favorite = document.querySelector(".card-achievement p:nth-of-type(3)");favorite.innerHTML = `收藏数:${data.achievement.favorite}`;const share = document.querySelector(".card-achievement p:nth-of-type(4)");share.innerHTML = `分享数:${data.achievement.share}`;}};// 发送请求xhr.send();// 设置插件图标chrome.browserAction.setIcon({ path: "icon/icon128.png" });// 设置插件标题chrome.browserAction.setTitle({ title: "CSDN个人数据展示" });
});
我们首先创建了一个XMLHttpRequest对象,然后使用open方法指定要读取的文件路径。接着我们定义了一个onload回调函数,在请求完成时执行。在这个回调函数中,我们首先检查请求的状态是否为200(表示成功),然后使用JSON.parse方法将JSON字符串转换为JavaScript对象,最后我们就可以处理读取到的数据将其显示在页面上。
进程管理工具PM2
到此为止,我们这个插件项目还剩最后一个问题没有解决:我们编写的这个nodejs 爬虫文件不可能一直在控制台运行,那么我们该怎么让这个文件一直运行从而来定时爬取更新JSON文件呢?
我们这里来用一个管理 Node.js 进程的第三方工具 PM2
PM2是一个流行的Node.js进程管理器,它可以帮助我们简化应用程序的部署、监控和管理:
- 进程管理:PM2可以启动、停止、重启和删除进程,可以使用PM2来管理单个应用程序或多个应用程序。
- 自动重启:如果进程崩溃或异常退出,PM2会自动重启它。
- 监控和日志记录:PM2可以监控应用程序的CPU和内存使用情况,并将日志记录到文件中。
- 负载均衡:如果运行多个实例,PM2可以使用负载均衡来将流量分配到不同的实例上。
- 部署:PM2可以在生产环境中部署应用程序,可以将应用程序作为系统服务运行,并在系统启动时自动启动。
以下是一些PM2的常用命令:
pm2 start <file>
:启动一个应用程序。pm2 stop <id|name>
:停止一个应用程序。pm2 restart <id|name>
:重启一个应用程序。pm2 delete <id|name>
:删除一个应用程序。pm2 list
:列出所有正在运行的应用程序。pm2 monit
:监视所有正在运行的应用程序的CPU和内存使用情况。
我们来安装一下 pm2:
npm install -g pm2
然后用 pm2 启动项目:
pm2 start /path/to/your/nodejs/script.js
这个命令会启动Node.js程序,并且在后台运行,即使关闭了终端也不会停止运行
如果想停止程序可以使用以下命令:
pm2 stop /path/to/your/nodejs/script.js
插件开发总结
本项目是Node爬虫和Chrome插件开发的一个综合案例,这个插件的功能非常实用,可以帮助我们更好地了解自己的CSDN博客个人数据。
我们首先用puppeteer编写node爬虫脚本爬取到了CSDN个人主页数据,然后将数据写入了JSON文件,并用node-schedule模块完成了定时爬取的功能。
之后我们完成了Chrome插件的主体开发,最后我们使用PM2进程管理工具让这个爬虫文件一直运行。
插件完整代码可以自取:https://github.com/KongC-X/node-creeper/tree/main/chrome