前言

本教程仍在测试中,仍然可能出现跨域请求的问题

本文教程主要针对 Hexo 博客,对博客站点的的访问地图、每月访问量、访问来源的维度绘制统计图,使用的是 ECharts 开源可视化库。具体效果可以点击本站的 统计--博客统计 页面查看。

  • 本文数据来源均为百度统计,请确保博客站点已加入百度统计,以 butterfly 主题为例,可参照 Butterfly 安装文档(四) 主题配置-2 的分析统计段落实现。
  • 本地主机访问(localhost)也会记录到百度统计,推荐在 【百度统计】--【管理】--【统计规则设置】--【过滤规则设置】--【受访域名统计规则】--【勾选排除 localhost(本地主机)】 排除本地主机访问(貌似在勾选后生效,但是以前的访问记录仍会统计)。
  • 此文是针对于文章 Hexo 博客访问统计图 改良版,实时调用百度统计 API 获取访问数据。
  • 本教程将会泄漏属于百度统计的站点 ID 和百度统计 AccessToken,请先前往 百度统计用户手册 了解,介意者请谨慎部署。

如果想绘制博客文章发布统计图的可以参考文章 Hexo 博客文章统计图

新建 census 页面

1
hexo new page census

[Blogroot]\source\ 目录下新建 census 文件夹,并在新建的 census 文件夹下新建 index.md 文件,添加以下内容:

1
2
3
4
---
title: 博客统计
date: 2020-03-01 08:00:00
---

引入 ECharts.js

echarts.js 必须在渲染 echarts 实例的 JavaScript 前引入。

以 butterfly 主题为例,可以在 [Blogroot]\_config.butterfly.ymlinject 配置项中引入 echart.js 文件。

1
2
3
4
inject:
head:
- <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script>
- <script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/map/js/china.js"></script> # 绘制地图需要另外添加 china.js

可以在 index.md 添加以下内容:

1
2
<script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@4.7.0/map/js/china.js"></script> <!-- 绘制地图需要另外添加 china.js -->

博客访问统计

获取网站统计数据

  1. 已经添加百度统计分析的小伙伴可以登录百度统计的官网网站,此处博主使用的是百度账号登录。

  2. 登录进入首页后可以点击基础报告查看网站访问统计数据(若无绑定网站,需要先在 管理页面--账户管理--网站列表 添加博客网址)。

  3. 此时我们想要获取这些数据可以调用百度统计 API,点击管理,进入管理页面后在左边侧边栏找到数据导出服务

  4. 登录百度开发者中心控制台,创建工程,应用名称任意。

  5. 点击刚刚创建的工程,记录下 API Key,Secret Key。

  6. 填写下面链接参数后打开链接获取授权码,具体步骤可以参考 Tongji API 用户手册

    http://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id={CLIENT_ID}&redirect_uri={REDIRECT_URI}&scope=basic&display=popup

    • API Key:{CLIENT_ID}
    • 回调 URI:{REDIRECT_URI},可以填写 oob

  7. 复制第 6 步获取的授权码,填写下面链接参数后打开链接获取 ACCESS_TOKEN :。

    http://openapi.baidu.com/oauth/2.0/token?grant_type=authorization_code&code={CODE}&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&redirect_uri={REDIRECT_URI}

    • Auth Code:{CODE},第 6 步获取的授权码。
    • API Key:{CLIENT_ID}
    • Secret Key:{CLIENT_SECRET}
    • 回调 URI:{REDIRECT_URI},可以填写 oob

    注意:从上述步骤得到的数据中包含 ACCESS_TOKENREFRESH_TOKEN 两个值,其中 ACCESS_TOKEN 的有效期为一个月,REFRESH_TOKEN 的有效期为十年。REFRESH_TOKEN 的作用就是刷新获取新的 ACCESS_TOKENREFRESH_TOKEN , 如此反复操作来实现 ACCESS_TOKEN 有效期永久的机制。

    一旦 ACCESS_TOKEN 过期,可根据以下请求更换新的 ACCESS_TOKENREFRESH_TOKEN

    http://openapi.baidu.com/oauth/2.0/token?grant_type=refresh_token&refresh_token={REFRESH_TOKEN}&client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}

    • API Key:{CLIENT_ID}
    • Secret Key:{CLIENT_SECRET}
    • Refresh Token:{REFRESH_TOKEN}
  8. 调用百度统计 API

    第 7 步获取的 ACCESS_TOKEN 是所调用 API 的用户级参数,结合各 API 的应用级参数即可正常调用 API 获取数据,填写下面链接参数后打开链接获取网站 ID:

    https://openapi.baidu.com/rest/2.0/tongji/config/getSiteList?access_token={ACCESS_TOKEN}

    • Access Token:{ACCESS_TOKEN}

    也可以在 Tongji API 调试工具 输入 ACCESS_TOKEN 获取网址 ID:

  9. Tongji API 调试工具 选择需要获取的报告数据,填写 ACCESS_TOKEN 、必填参数、选填参数获取百度统计数据。

网站统计代码

以 butterfly 主题为例,可以在 [Blogroot]\js\ 目录下新建 census.js 文件,然后添加以下内容:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
var start_date = '20210101' // 开始日期
var date = new Date();
var end_date = date.getFullYear() + (date.getMonth() > 8 ? (date.getMonth() + 1) : ("0" + (date.getMonth() + 1))) + (date.getDate() > 9 ? date.getDate() : ("0" + date.getDate())); // 结束日期
var access_token = '121.c644d8c4*****' // accessToken
var site_id = '16****' // 网址 id
var dataUrl = 'https://baidu-tongji-api.vercel.app/api?access_token=' + access_token + '&site_id=' + site_id
var metrics = 'pv_count' // 统计访问次数 PV 填写 'pv_count',统计访客数 UV 填写 'visitor_count',二选一
var metricsName = (metrics === 'pv_count' ? '访问次数' : (metrics === 'visitor_count' ? '访客数' : ''))
// 这里为了统一颜色选取的是“明暗模式”下的两种字体颜色,也可以自己定义
var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'

// 访问地图
function mapChart () {
let script = document.createElement("script")
let paramUrl = '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=visit/district/a';
fetch(dataUrl + paramUrl).then(data => data.json()).then(data => {
let mapName = data.result.items[0]
let mapValue = data.result.items[1]
let mapArr = []
let max = mapValue[0][0]
for (let i = 0; i < mapName.length; i++) {
mapArr.push({ name: mapName[i][0].name, value: mapValue[i][0] })
}
let mapArrJson = JSON.stringify(mapArr)
script.innerHTML = `
var mapChart = echarts.init(document.getElementById('map-chart'), 'light');
var mapOption = {
title: {
text: '博客访问来源地图',
x: 'center',
textStyle: {
color: '${color}'
}
},
tooltip: {
trigger: 'item'
},
visualMap: {
min: 0,
max: ${max},
left: 'left',
top: 'bottom',
text: ['高','低'],
color: ['#1E90FF', '#AAFAFA'],
textStyle: {
color: '${color}'
},
calculable: true
},
series: [{
name: '${metricsName}',
type: 'map',
mapType: 'china',
showLegendSymbol: false,
label: {
emphasis: {
show: false
}
},
itemStyle: {
normal: {
areaColor: 'rgba(255, 255, 255, 0.1)',
borderColor: '#121212'
},
emphasis: {
areaColor: 'gold'
}
},
data: ${mapArrJson}
}]
};
mapChart.setOption(mapOption);
window.addEventListener("resize", () => {
mapChart.resize();
});`
document.getElementById('map-chart').after(script);
}).catch(function (error) {
console.log(error);
});
}

// 访问趋势
function trendsChart () {
let script = document.createElement("script")
let paramUrl = '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=trend/time/a&gran=month'
fetch(dataUrl + paramUrl)
.then(data => data.json())
.then(data => {
let monthArr = []
let monthValueArr = []
let monthName = data.result.items[0]
let monthValue = data.result.items[1]
for (let i = Math.min(monthName.length, 12) - 1; i >= 0; i--) {
monthArr.push(monthName[i][0].substring(0, 7).replace('/', '-'))
if (monthValue[i][0] !== '--') {
monthValueArr.push(monthValue[i][0])
} else {
monthValueArr.push(null)
}
}
let monthArrJson = JSON.stringify(monthArr)
let monthValueArrJson = JSON.stringify(monthValueArr)
script.innerHTML = `
var trendsChart = echarts.init(document.getElementById('trends-chart'), 'light');
var trendsOption = {
textStyle: {
color: '${color}'
},
title: {
text: '博客访问统计图',
x: 'center',
textStyle: {
color: '${color}'
}
},
tooltip: {
trigger: 'axis'
},
xAxis: {
name: '日期',
type: 'category',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '${color}'
}
},
data: ${monthArrJson}
},
yAxis: {
name: '${metricsName}',
type: 'value',
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '${color}'
}
}
},
series: [{
name: '${metricsName}',
type: 'line',
smooth: true,
lineStyle: {
width: 0
},
showSymbol: false,
itemStyle: {
opacity: 1,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(128, 255, 165)'
},
{
offset: 1,
color: 'rgba(1, 191, 236)'
}])
},
areaStyle: {
opacity: 1,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(128, 255, 165)'
}, {
offset: 1,
color: 'rgba(1, 191, 236)'
}])
},
data: ${monthValueArrJson},
markLine: {
data: [{
name: '平均值',
type: 'average'
}]
}
}]
};
trendsChart.setOption(trendsOption);
window.addEventListener("resize", () => {
trendsChart.resize();
});`
document.getElementById('trends-chart').after(script);
}).catch(function (error) {
console.log(error);
});
}

// 访问来源
function sourcesChart () {
let script = document.createElement("script")
let paramUrl = '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=source/all/a';
fetch(dataUrl + paramUrl)
.then(data => data.json())
.then(data => {
monthArr = [];
let sourcesName = data.result.items[0]
let sourcesValue = data.result.items[1]
let sourcesArr = []
for (let i = 0; i < sourcesName.length; i++) {
sourcesArr.push({ name: sourcesName[i][0].name, value: sourcesValue[i][0] })
}
let sourcesArrJson = JSON.stringify(sourcesArr)
script.innerHTML = `
var sourcesChart = echarts.init(document.getElementById('sources-chart'), 'light');
var sourcesOption = {
textStyle: {
color: '${color}'
},
title: {
text: '博客访问来源统计图',
x: 'center',
textStyle: {
color: '${color}'
}
},
legend: {
top: 'bottom',
textStyle: {
color: '${color}'
}
},
tooltip: {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
series: [{
name: '${metricsName}',
type: 'pie',
radius: [30, 80],
center: ['50%', '50%'],
roseType: 'area',
label: {
formatter: "{b} : {c} ({d}%)"
},
data: ${sourcesArrJson},
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(255, 255, 255, 0.5)'
}
}
}]
};
sourcesChart.setOption(sourcesOption);
window.addEventListener("resize", () => {
sourcesChart.resize();
});`
document.getElementById('sources-chart').after(script);
}).catch(function (error) {
console.log(error);
});
}

function switchVisitChart () {
// 这里为了统一颜色选取的是“明暗模式”下的两种字体颜色,也可以自己定义
let color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'
try {
let mapOptionNew = mapOption
mapOptionNew.title.textStyle.color = color
mapOptionNew.visualMap.textStyle.color = color
mapChart.setOption(mapOptionNew)
} catch (error) {
console.log(error)
}
try {
let trendsOptionNew = trendsOption
trendsOptionNew.title.textStyle.color = color
trendsOptionNew.xAxis.axisLine.lineStyle.color = color
trendsOptionNew.yAxis.axisLine.lineStyle.color = color
trendsOptionNew.textStyle.color = color
trendsChart.setOption(trendsOptionNew)
} catch (error) {
console.log(error)
}
try {
let sourcesOptionNew = sourcesOption
sourcesOptionNew.title.textStyle.color = color
sourcesOptionNew.legend.textStyle.color = color
sourcesOptionNew.textStyle.color = color
sourcesChart.setOption(sourcesOptionNew)
} catch (error) {
console.log(error)
}
}

if (document.getElementById('map-chart')) mapChart()
if (document.getElementById('trends-chart')) trendsChart()
if (document.getElementById('sources-chart')) sourcesChart()

document.getElementById("darkmode").addEventListener("click", function () { setTimeout(switchVisitChart, 100) })

上面是博主自己三个统计图的代码,小伙伴们可以用以参考,根据自己的需求绘制百度分析统计图。

更多统计图的自定义属性可以查看 ECharts 配置项文档,根据自行喜好对 ECharts 统计图进行修改。

自建 Vercel API(可选)

由于在 js 文件中请求百度统计 API 获取数据会出现跨域请求的错误。

博主参考 Akilar 的 基于 Butterfly 主题的首页置顶 gitcalendar 自建 Vercel API 部署教程,以及冰老师的 python_github_calendar_api 搭建了获取百度统计数据的 Vercel API,力求解决百度统计 API 的跨域请求问题,但是仍然有时会出现跨域问题。

虽然 Vercel 的访问应当没有次数限制,但是不排除存在因访问次数过多而限流等限制。所以还是建议各位自建 API。使用 Vercel 部署,完全免费。且无需服务器。

部署完成后将获取到的默认域名替换 census.js 文件中的 data_Url,如:

1
2
- var dataUrl = 'https://baidu-tongji-api.vercel.app/api?access_token=' + access_token + '&site_id=' + site_id
+ var dataUrl = 'https://域名/api?access_token=' + access_token + '&site_id=' + site_id

使用统计图

在上文新建的 [Blogroot]\source\census\index.md 文件中添加以下内容:

1
2
3
4
5
6
7
8
<!-- 访问地图 -->
<div id="map-chart" style="border-radius: 8px; height: 600px; padding: 10px;"></div>
<!-- 访问趋势 -->
<div id="trends-chart" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
<!-- 访问来源 -->
<div id="sources-chart" style="border-radius: 8px; height: 300px; padding: 10px;"></div>

<script defer data-pjax src="/js/census.js"></script>

当然也可以在其他页面引入博客访问统计图。

可能遇到的问题

  • 控制台报错 Uncaught ReferenceError: echarts is not defined

    解决方案:

    需要在统计图的前引入 echarts.js 文件,最好是在页面的头部引入。

  • 控制台报错 Access to fetch at 'https://baidu-tongji-api.vercel.app/api?...' from origin 'http://...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

    解决方案:

    仍在努力解决跨域问题。