在Hexo中显示你的发文折线图

本文最后更新于:2022年9月9日 下午

在Hexo中显示你的发文折线图

在GitHub敲代码的时候,看见Profile里的Contributions统计图就会获得一股成就感,而Hexo中却没有这种功能,作为一个对前端一无所知的coder,最近现学现卖做了个实时同步的发文统计图,放在了“关于”页面。如下图所示,这个统计图能很适应地嵌入在正文中,并且鼠标放上去会有响应,不是简单的图片。

制作这个的基本流程如下图所示,基本思路是通过GitHub的API来获取寄放在GitHub Pages上的本网站的repo,之后由于hexo将文章排序按照时间规律分目录存储,可以获取到各个时间段的发文数量。这个数据之后通过很便宜甚至能白嫖的腾讯云Severless云函数来实时获取,最后在我们的网站中只需要简单访问云函数的API就可以获得数据。

graph LR
	A[获取GitHub API权限] --> B[用Python写好获取发文数据的函数]
	B --> C[将函数部署为云函数]
	C --> D[配置Hexo导入要用的第三方JS库]
	D --> E[在文章插入JS代码]

获取GitHub API权限

由于GitHub的API就能方便地提供我们需要的信息,所以我们不需要费心用各种方法去爬取。GitHub的REST API是RESTful架构的教科书级参考,非常好用易懂,文档也很全面:GitHub REST API - GitHub Docs。我们主要用到其查询commits和查询tree的API。使用API需要先获得Personal access token,可以在这里生成(这个页面在Settings -> Developer settings -> Personal access tokens下),假如只是实现我们这个功能不需要勾选任何权限,也不推荐勾选过多的权限。

⚠️要注意这个token只显示一次,及时保存。

使用Python获取发文数据

使用requests就能轻松的访问API,我这里文章的目录结构是<年份>/<月份>/<日>/<标题>/页面,年份直接在根目录下。为了之后部署在云函数上,需要以Flask的形式写。两个库的细节就不说啦,代码含义见注释。

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
@app.route("/github/<owner>/<repo>")
def main(owner, repo):
secret_token = "你的token"
headers = { # 通用头
'Accept': "application/vnd.github+json",
'Authorization': f"Bearer {secret_token}"
}
# 先获取最新commit的url
api_url = f"https://api.github.com/repos/{owner}/{repo}/commits"
rsp = requests.get(api_url, headers=headers)
tree_url = json.loads(rsp.content)[0]['commit']['tree']['url']
# 然后获取表示年份的根url
rsp = requests.get(tree_url, headers=headers)
year_root_url = []
for item in json.loads(rsp.content)['tree']:
p: str = item['path'] # 由于有多个目录,要先判断是年份
if p.isdigit() and p.startswith('20') and len(p) == 4:
year_root_url.append([p, item['url']])
# 然后recursive获取下面的所有路径,保存到blog_info中
blog_info = []
for yr, yr_url in year_root_url:
rsp = requests.get(yr_url, headers=headers, params={'recursive': True})
for item in json.loads(rsp.content)['tree']:
if item['type'] == 'blob': # 节点才是我们需要的文章
month, day, name, _ = item['path'].split('/')
blog_info.append([yr, month, day, name])
# 合并一些数据进行可视化(按月)
blog_counter = {}
for yr, mo, _, _ in blog_info:
date = f"{yr}/{mo}"
if date in blog_counter:
blog_counter[date] += 1
else:
blog_counter[date] = 1
# 组合返回
result_dict = {
"labels": list(blog_counter.keys()),
"values": list(blog_counter.values()),
}
return jsonify(data=result_dict)

部署到云函数上

在腾讯云打开Serverless的控制台(第一次用可能要授权什么的,但是很简单),然后在函数服务中用模板创建一个flask的云函数。

云函数

函数创建成功后,点开云函数,可以在线编辑代码,找到里面的app.py,加上之前写好的代码,然后在requirements.txt中添加requests库,最后点击编辑器右上角的部署就可以啦。

在线编辑云函数

要访问云函数,则点开Severless应用,然后找到URL,之间点开应该就能看到模版自带的欢迎界面,出现这个界面就表示应用成功了。之后可以用postman等工具来测试自己写的代码能不能用。

模版自带界面

配置Hexo

要在hexo中访问我们的云函数,以及之后的可视化,需要使用第三方的js库,但是并不能直接在文章中用<script>标签引入。(不太懂为啥不能,反正试过了)

要解决这个问题,我们使用Hexo 5 注入器,在生成页面的头部加上对应要导入的<script>标签。

根据上面这个链接的教材,我们在<blog目录>/themes/<主题>/scripts下新建一个injector.js,内容为:

1
2
3
4
hexo.extend.injector.register('head_begin', `
<script src="/js/Chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"></script>
`)

此处假如用CDN就直接贴链接,假如用本地相对目录,则是以<blog目录>/source为根目录的相对路径。我们要使用的可视化工具为Chart.js。并且由于我只学过一点点jquery,所以就导入jq来访问云函数了。

在文章中插入

为了控制大小,用一个div包裹住canvas,canvas在js中绑定为一个new Chart。由于对前端不是很熟,就进行了一些小小的配置,懂js的老哥应该能写得比我好很多hhhh。

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
44
45
46
<div class="chart-container" style="position: relative; height:80%; width:100%">
<canvas id="myChart"></canvas>
</div>

<script>
$.getJSON("https://<你的云函数链接>/release/github/Kamino666/kamino666.github.io", function(api_data){
const ctx = document.getElementById('myChart').getContext('2d');
const myChart = new Chart(ctx, {
type: 'line',
data: {
labels: api_data.data.labels,
datasets: [{
label: '发表博客数量',
data: api_data.data.values,
backgroundColor: ["rgba(238, 238, 238, 0.2)"],
borderColor: ["rgba(0, 173, 181, 1)"],
borderWidth: 2,
pointBorderWidth: 2,
pointHoverBorderWidth: 4,
pointHoverBackgroundColor: "rgba(0, 0, 0, 0)",
pointHoverBorderColor: "rgba(0, 173, 181, 1)",
pointHitRadius: 15,
pointBackgroundColor: "rgba(238, 238, 238, 1)",
}]
},
options: {
responsive: true,
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
},
legend: {
display: false
},
title: {
display: true,
fontSize: 18,
text: "Kamino发表博客的数量"
}
}
});
})
</script>

Q&A

Q:为什么要用云函数,不直接用js访问GitHub API?

A:因为我不会js。因为用js的话token会暴露出去,可能不是很安全。

Q:为什么这么麻烦,每次更新的时候直接生成新的数据就不用云函数了。

A:因为浪漫每次更新生成新数据也挺麻烦,而且这套流程也很适合练手。

Q:为什么要通过GitHub?我的Hexo不寄存在GitHub怎么办?

A:目前的解决方法不是最佳的,最佳应该是每次hexo g的时候自动生成对应的数据和代码,但是对这部分就更更更不熟悉了……