引言
前端 er 应该都晓得奇舞周刊,这是一个技术类博客,下面汇聚了一大批优良博客作者投稿的技术类文章。我自己每隔几天就会去看看下面的文章,然而它的官网常常打不开,每次想看文章的时候还得一页一页的翻能力找到想看的文章。或者,有时候就想轻易找一篇文章看一看作为常识扩大或是温故知新。
基于阅读文章的便利性的思考,于是开始了本次 CLI 工具的摸索,这款工具的外围性能是以开发者的角度来疾速的找出奇舞周刊上公布的文章链接。
次要性能
- 抓取全副文章链接
- 随机 N 篇文章链接
- 定时工作主动抓取
抓取全副文章链接
这个性能目标次要是抓取文章链接数据,为 CLI 工具开发提供数据撑持,以及为后续开发关键词检索、文章内容爬取、文章举荐等性能做铺垫。
npx 75_action fetch
文章数据本地缓存
作为命令行工具应用时,从官网抓取数据的过程实测会耗费 20s+ 的工夫,因而采纳本地文件来缓存曾经抓取的文章数据。npx 75_action random <N>
命令执行实现后会自定缓存抓取数据到本地,缓存有效期为 24h。
随机 N 篇文章链接
CLI 工具次要性能之一,运行命令即可随机返回 N 篇文章的数据。
npx 75_action random <N>
定时工作主动抓取
借助 Github Actions 配置定时工作,每天 0 8 16 点执行自定执行 [[# 抓取全副文章链接]] 工作,并将抓取到的文章数据打包上传至 GitHub 可供下载应用。
方案设计
- 获取文章数据
- CLI 工具
- 缓存策略
性能实现
文章数据抓取
对应源码在这里查看:https://github.com/JohnieXu/7…
抓取奇舞周刊官网首页 HTML 并解析出文章汇合数据
function getCollections() {return fetch(homeUrl)
.then(res => res.text())
.then(res => {if (!res) {return Promise.reject(new Error('获取网页内容失败'))
}
return cheerio.load(res)
})
.then($ => {const list = $('ol.issue-list > li')
const collections = list.map((i, l) => {const title = $(l).find('a').attr('title')
const url = $(l).find('a').attr('href')
const date = $(l).find('.date').attr('datetime')
return {title, url, date}
})
return collections
})
}
抓取汇合 URL 页面的 HTML 并解析出汇合下文章数据
function getArticleDoc(url) {return fetch(homeUrl + url)
.then(res => res.text())
.then(res => {if (!res) {return Promise.reject(new Error("获取网页内容失败"))
}
return cheerio.load(res)
})
}
function getArticles(doc) {
const $ = doc
const el = $('ul > li.article')
const list = el.map((i, l) => {
return {title: $(l).find('h3.title > a').text(),
url: $(l).find('h3.title > a').attr('href'),
desc: $(l).find('.desc').text()}
})
return list
}
整合文章数据并排序输入
getArticleDoc(url).then(getArticles).then(list => list.map((_, item) => ({...item, issue: title, date}))).then(list => {all = [...all, ...list]
}) // 整合文章数据
all = all.sort((a, b) => b.date.localeCompare(a.date)) // date 倒序排列
文章的 date 字段是对应汇合的公布日期(期刊日期),例如:2021-12-17,须要进行日期倒序排列,应用 String.prototype.localeCompare() 进行字符串排序。
文章数据缓存
对应源码在这里查看:https://github.com/JohnieXu/7…
缓存文件及有效期
const CACHE_FILE = './.75_action/.data.json'
const CACHE_TIME = 1000 * 60 * 60 * 24; // 缓存 24h
const homeDir = require('os').homedir()
const p = path.resolve(homeDir, CACHE_FILE) // 缓存文件门路在用户家目录
读取缓存文件的批改工夫判断是否过期(不存在缓存文件也算缓存过期)
function isCacheOutDate() {const p = path.resolve(require('os').homedir(), CACHE_FILE)
if (!fs.existsSync(p)) {return true}
const stat = fs.statSync(p)
const lastModified = stat.mtime
const now = new Date()
return now - lastModified >= CACHE_TIME
}
未过期则读取缓存文件作为抓取到的文章数据
function getHomeFileJson() {const homeDir = require('os').homedir()
const p = path.resolve(homeDir, CACHE_FILE)
const jsonStr = fs.readFileSync(p)
let json
try {json = JSON.parse(jsonStr)
} catch(e) {console.error(e)
json = []}
return json
}
抓取到文章数据后写入本地缓存
function writeFileToHome(json) {const homeDir = require('os').homedir()
const p = path.resolve(homeDir, CACHE_FILE) // 写入门路为用户家目录
return mkdirp(path.dirname(p)).then(() => {fs.writeFileSync(p, JSON.stringify(json, null, 2)) // 应用 JSON 格局序列化
})
}
CLI 工具开发
配置 bin 入口
运行 npx 75_action
命令即应用 Node.js 执行此处指向的 75_action.js
脚本
{
"bin": {"75_action": "bin/75_action.js"}
}
指向的脚本文件源码在这里查看:https://github.com/JohnieXu/7…
令行参数
应用 commander 库注册 CLI 命令和解析命令参数
const program = require('commander')
// 注册命令
program.command('random [number]')
.description('随机获取 n 篇文章链接')
.option('-d, --debug', '开启 debug 模式')
.action((number, options) => {
number = number || 1
if (options.debug) {console.log(number, options)
}
fetch({save: 'home', progress: true}).then(({collections, articles}) => {const selected = random(number, articles)
console.log(JSON.stringify(selected, null, 2))
process.exit()}).catch((e) => {console.log(e)
process.exit(1)
})
})
program.command('fetch')
.description('从新抓取文章链接')
.option('-d, --debug', '开启 debug 模式')
.action((options) => {if (options.debug) {console.log(options)
}
fetch({save: 'home', progress: true, reload: true}).then(({collections, articles}) => {console.log(` 抓取实现,总共 ${collections.length}个汇合,${articles.length}篇文章 `)
process.exit()})
})
program.parse(process.argv)
命令行进度条
应用 cli-progress 库实现命令行进度条成果
const cliProgress = require('cli-progress')
const bar1 = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic)
bar1.start(collections.length, 0) // 文章汇合数设置为进度总值
bar1.update(doneLen) // 抓取实现任一汇合文章后更新进度条
定时抓取数据
此性能应用 GitHub Actions 主动执行定时工作,在我的项目中增加对应的 yml 配置文件即可,对应源码在这里查看:https://github.com/JohnieXu/7…
name: FETCH
on:
push:
branches:
- master
schedule:
- cron: "0 0,8,16 * * *" # 每天 0 8 16 点执行(有 8 小时时差)jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{matrix.node-version}}
uses: actions/setup-node@v2
with:
node-version: ${{matrix.node-version}}
cache: 'npm'
- run: npm i -g yarn
- run: yarn
- run: node index.js
- name: Save
uses: actions/upload-artifact@v2
with:
path: data.json
应用 actions/checkout
克隆仓库源码,应用 actions/setup-node
来切换 Node.js 版本为 16.X,最初应用 actions/upload-artifact
将执行 node index.js
命令生成的 data.json
文件打包输入上传至 GitHub。
执行成果
Npm 包公布
要确保此我的项目反对命令 npx 75_action
执行,须要将此我的项目公布到 npm 官网仓库下,项目名称为 75_action
。
公布流程如下(局部命令依据理论状况抉择),其中 nrm
用法可在这里查看:https://www.npmjs.com/package…。
nrm use npm # 切换 npm 源为官网
npm login # 登录 npm 账号
npm run publish# 公布
成品展现
以下命令均在终端执行,并且须要 Node.js 版本至多 10.X 及以上,并且终端内 node、npx 命令可失常应用
随机一篇文章
npx 75_action random
随机 5 篇文章
npx 75_action random 5
随机 N 篇文章(N 为正整数)
npx 75_action random N
抓取并更新本地文章数据
npx 75_action fetch
结语
本文实现了一个用于抓取奇舞周刊文章题目、形容及文章原始链接的 CLI 工具,该工具依靠于 Node.js 执行。根本满足了疾速获取奇舞周刊文章链接的需要,同时文章数据还可能缓存在本地,无效晋升了应用体验。另外,还有一些进阶性能未开发,比方:依据文章题目进行关键词搜寻,返回最新一期文章汇合,依据文章题目进行分类,文章链接有效性检测等。
上述这些未开发的性能后续会视状况陆续进行开发,也欢送各位关注此我的项目的后续停顿,我的项目地址在这里:https://github.com/JohnieXu/7…。
参考资料
[1] String.prototype.localeCompare():https://developer.mozilla.org…
[2] cheerio 应用文档:https://github.com/cheeriojs/…
[3] commander 应用文档:https://github.com/tj/command…
[4] cli-progress 应用文档:https://github.com/npkgz/cli-…
[5] GitHub Actions 应用教程:https://docs.github.com/cn/ac…