前言
通过百度统计、CNZZ等服务,我们可以记录站点的访问地图、访问量和来源,如果想要展示数据,可以选择直接导向服务商提供的公开页面,但是样式丑陋,所以本文通过百度统计API,使用Echarts制作站点访问准实时统计页面,效果可以参考统计。
之所以说是准实时统计,是因为为了解决跨域问题(CROS error),本文采用的方法是定时通过百度统计API将数据下载保存为json文件,放置在网站目录下(后续可能会发展为vercel api,挖坑)。不过作为个人博客,方式访问量不会很大,没有必要实时更新,目前本站设置是每隔6小时更新一次。
数据获取
百度统计
在设置样式之前首先需要获取统计数据,使用百度账号登陆百度统计,根据参考资料4进行操作,获取token与site_id,具体教程可以查看参考资料1。
下载文件
通过6个链接,我们可以获取:一年内每日访问统计、访问地图数据、月度访问统计、来源分类统计、搜索引擎访问统计和外部链接访问统计;通过python或者nodejs都可以很方便的下载文件保存,以下为python的示例:
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
| import requests import time, datetime
start_date = '20201218' date = datetime.datetime.now()
end_date = str(date.year) + (str(date.month) if date.month > 9 else ('0' + str(date.month))) + (str(date.day) if date.day > 9 else ('0' + str(date.day)))
access_token = '121.6e...' site_id = '16******'
dataUrl = 'https://openapi.baidu.com/rest/2.0/tongji/report/getData?access_token=' + access_token + '&site_id=' + site_id
metrics = 'pv_count'
def downFile(url, fileName, prefix='/home/API/data/'): print('downloading :', url) down_res = requests.get(url) with open(prefix+fileName, 'wb') as f: f.write(down_res.content) print('writing :', prefix+fileName)
downFile(dataUrl + '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=visit/district/a', 'map.json')
downFile(dataUrl + '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=trend/time/a&gran=month', 'trends.json')
downFile(dataUrl + '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=source/all/a', 'sources.json')
downFile(dataUrl + '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=source/engine/a', 'engine.json')
downFile(dataUrl + '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=source/link/a', 'link.json')
''' 访问日历需要获取一年内的数据,按照一年365天计算,大概为52周多一点,所以前面有完整的52排,获取方式只要通过开始日期年份-1即可 然后就是第53排的处理,python中的date.weekday()获取的星期几是0对应周一,所以通过(date.weekday()+1)%7即可转换到0对应周日 于是在52周的基础上,减去星期数,就可以得到新的start_date ''' date = datetime.datetime(date.year-1, date.month, date.day) date = datetime.datetime.fromtimestamp(date.timestamp()-3600*24*((date.weekday()+1)%7)) start_date = str(date.year) + (str(date.month) if date.month > 9 else ('0' + str(date.month))) + (str(date.day) if date.day > 9 else ('0' + str(date.day))) downFile(dataUrl + '&method=overview/getTimeTrendRpt' + '&metrics=' + metrics + '&start_date=' + start_date + '&end_date=' + end_date, 'calendar.json')
|
自动更新
在Linux中可以通过crontab
设置定时任务,以下为每整6小时的0点执行一次任务:
1 2 3 4 5 6 7 8
| 0 0 * * * /usr/bin/python /home/API/data/get.py >> /home/API/data/get.log 0 6 * * * /usr/bin/python /home/API/data/get.py >> /home/API/data/get.log 0 12 * * * /usr/bin/python /home/API/data/get.py >> /home/API/data/get.log 0 18 * * * /usr/bin/python /home/API/data/get.py >> /home/API/data/get.log
0 0,6,12,18 * * * /usr/bin/python /home/API/data/get.py >> /home/API/data/get.log
0 */6 * * * /usr/bin/python /home/API/data/get.py >> /home/API/data/get.log
|
数据展示
统计图容器
在html代码中插入:
1 2 3 4 5 6 7 8 9 10 11
| <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>
<div style="max-width: 90%;margin: 0 auto;"> <div id="calendar_container" class="data-container"></div> <div id="map_container" class="data-container" style="height: 500px;"></div> <div id="trends_container" class="data-container" style="height: 350px;"></div> <div id="sources_container" class="data-container" style="height: 450px;"></div> </div>
<script src="https://foolishfox.cn/js/census.js"></script>
|
访问日历
访问日历类似于Github贡献日历,有两种方法实现。
- 源自于hexo-githubcalendar 插件,由Eurkon修改,原文见参考资料3。需要修改的地方有:
1 2 3 4 5 6 7
| ...
fetch('https://api.foolishfox.cn/data/calendar.json?date'+new Date()).then(data => data.json()).then(res => { ...
visit_calendar('pv_count', visit_color); ...
|
另外,需要更改容器id可以直接搜索替换即可。
- 通过census.js进行设置,包括后续的访问地图、访问趋势(访问次数)和访问来源都是基于此文件通过Echarts实现的。
1 2 3 4 5 6 7 8 9 10 11
| var metrics = 'pv_count' var metricsName = (metrics === 'pv_count' ? '访问次数' : (metrics === 'visitor_count' ? '访客数' : '')) ... function calChart () { let script = document.createElement("script") fetch('https://api.foolishfox.cn/data/calendar.json?date'+new Date()).then(data => data.json()).then(data => { ... let colorBox = ['#EBEDF0', '#FFE9BB', '#FFD1A7', '#FFBB95', '#FFA383', '#FF8D70', '#FF745C', '#FF5C4A', '#FF4638', '#FF2E26', '#FF1812'];
|
访问地图
最简单的就是访问地图,直接修改json文件的请求url即可:
1 2 3 4 5
| function mapChart () { let script = document.createElement("script") fetch('https://api.foolishfox.cn/data/map.json?date'+new Date()).then(data => data.json()).then(data => {
|
访问趋势
访问趋势中按照年份,将12个月的数据展开,需要从json中获取到年份和月份信息,由于时间日期格式是固定的,所以可以直接截取。本部分代码以下列展示的为准:
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
| function get_year(s) { return parseInt(s.substr(0, 4)) } function get_month(s) { return parseInt(s.substr(5, 2)) }
function trendsChart () { let script = document.createElement("script") fetch('https://api.foolishfox.cn/data/trends.json?date'+new Date()).then(data => data.json()).then(data => { let date = new Date(); let monthValueArr = {}; let monthName = data.result.items[0]; let monthValue = data.result.items[1]; for (let i =2020; i <= date.getFullYear(); i++) monthValueArr[String(i)] = [ , , , , , , , , , , , ]; monthValueArr for (let i = 0; i < monthName.length; i++) { let year = get_year(monthName[i][0]); let month = get_month(monthName[i][0]); monthValueArr[String(year)][String(month-1)] = monthValue[i][0]; } script.innerHTML = ` var trendsChart = echarts.init(document.getElementById('trends_container'), 'light'); var trendsOption = { title: { text: '访问趋势', x: 'center' }, tooltip: { trigger: 'axis' }, legend: { data: ['2020', '2021', '2022'], x: 'right' }, xAxis: { name: '日期', type: 'category', boundaryGap: false, data: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'] }, yAxis: { name: '${metricsName}', type: 'value' }, series: [ { name: '2020', type: 'line', smooth: true, data: [${monthValueArr["2020"]}], markLine: { data: [{type: 'average', name: '平均值'}] } }, { name: '2021', type: 'line', smooth: true, data: [${monthValueArr["2021"]}], markLine: { data: [{type: 'average', name: '平均值'}] } }, { name: '2022', type: 'line', smooth: true, data: [${monthValueArr["2022"]}], markLine: { data: [{type: 'average', name: '平均值'}] } } ] }; trendsChart.setOption(trendsOption); window.addEventListener("resize", () => { trendsChart.resize(); });` document.getElementById('trends_container').after(script); }).catch(function (error) { console.log(error); }); }
|
访问来源
留在最后的是最复杂的访问来源,实际上百度的全部来源(source/all/a
)API可以将来源分为:直达、外部链接和搜索引擎三个部分,直接使用并不困难。但是百度统计会将必应(cn.bing.com
和www.bing.com
)的来源归类到外部链接而不是搜索引擎,而且我自己还想统计来自于Github、十年之约等网站的流量,所以需要获取多个数据文件。
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
| ...
fetch('https://api.foolishfox.cn/data/sources.json?date'+new Date()).then(data => data.json()).then(data => { 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] }); link = sourcesArr[1]['value'] ; search = sourcesArr[2]['value']; direct = sourcesArr[0]['value']; ...
fetch('https://api.foolishfox.cn/data/link.json?date'+new Date()).then(data => data.json()).then(data => { let linksName = data.result.items[0]; let linksValue = data.result.items[1]; let linksArr = {}; for (let i = 0; i < linksName.length; i++) linksArr[linksName[i][0].name] = linksValue[i][0]; let sum = data.result.sum[0][0]; let bing = linksArr['http://cn.bing.com']+linksArr['http://www.bing.com']; let github = linksArr['http://github.com']; let travel = linksArr['http://travellings.now.sh']+linksArr['http://travellings.vercel.app']+linksArr['http://www.foreverblog.cn']; innerHTML += ` {value: ${bing}, name: '必应'}, {value: ${direct}, name: '直达'}, {value: ${github}, name: 'Github'}, {value: ${travel}, name: '开往/十年之约'},`+ `{value: ${sum-bing-github-travel}, name: '其他'} ] }, { name: '访问来源', type: 'pie', selectedMode: 'single', radius: [0, '30%'], label: { position: 'inner', fontSize: 14}, labelLine: { show: false }, data: [`+ `{value: ${search+bing}, name: '搜索', itemStyle: { color : 'green' }}, {value: ${direct}, name: '直达', itemStyle: { color : '#FFDB5C' }}, {value: ${link-bing}, name: '外链', itemStyle: { color : '#32C5E9' }} ] }, ] }; ...
|
Q&A
- 不想使用python下载文件保存,想要实时统计
根据参考资料2中4.3节——自建 Vercel API(可选)进行设置,替换上述文件中的url即可(感谢秋水)
根据参考资料2中5.3节进行设置,替换上述js
文件中的url
即可,具体形式可参考参考资料2中4.2节。
- 依然出现跨域问题?
请务必保证通过2.2节获取的文件保存在博客的网站目录下,并通过url可以访问;如果想要向我一样通过其他域名(例如api.foolishfox.cn/data/*.json
)访问,需要对服务器进行设置,以Nginx为例:
configuration1 2 3 4 5 6 7 8 9
| location ~ ^/data/ { if ($http_origin ~* (http://localhost:4000|https://foolishfox.cn)) { add_header 'Access-Control-Allow-Origin' "$http_origin"; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; } }
|
- 地图显示错误
务必注意,Echarts目前已经不提供地图js/json文件的下载,所以要通过npm获取到旧版本中的China.js
文件,因此Echarts的版本最好也保持一致
- Echarts绘制的访问日历无法自动适应窗口大小
正在积极解决中…
更新
- 2021.6.1:访问趋势中第23行代码应改为:
1
| monthValueArr[String(year)][String(month-1)] = monthValue[i][0] === '--' ? 0 : monthValue[i][0];
|
- 自动更新
token
百度统计的token
需要每个月更新一次,手动更新很麻烦,所以可以在代码中添加这一功能。首先更改downFile
函数:
1 2 3 4 5 6 7 8 9
| def downFile(url, fileName, prefix='./'): res = requests.get(url) res = json.loads(res.content)
if('error_code' in res.keys()): update(prefix=prefix) downFile(url, fileName, prefix) with open(prefix+fileName, 'w') as f: json.dump(res, f)
|
当发现token
错误时,我们执行update
函数:
1 2 3 4 5 6 7 8 9
| def update(prefix, url=updateUrl): res = requests.get(url) res = json.loads(res.content) print(res)
config['access'] = res['access_token'] config['refresh'] = res['refresh_token'] with open(prefix + 'token.json', 'w') as f: json.dump(config, f)
|
config
是通过json
加载了目录下的token.json
文件,格式如下所示,注意添加访问权限(例如Nginx
中添加该路径返回403
)。
1 2 3 4 5 6 7 8
| { "access": "access token", "refresh": "refresh token", "site_id": "site id", "api_key": "api key", "serect_key": "serect key" }
|
updateUrl
是自动更新的url
:
1
| updateUrl = 'http://openapi.baidu.com/oauth/2.0/token?grant_type=refresh_token&refresh_token=' + config['refresh'] + '&client_id=' + config['api_key'] + '&client_secret=' + config['serect_key']
|
完整的py
文件可以点击下载。
参考资料
- Hexo 博客访问统计图
- Hexo 博客实时访问统计图
- Hexo 博客访问日历图
- 百度统计用户手册
- Echarts文档