新增页面:新增博客访问统计页面

# 前言

参考文章:

  1. Hexo 博客实时访问统计图
  2. 基于 Butterfly 主题的首页置顶 gitcalendar

本教程将会泄漏属于百度统计的 站点 ID 和百度统计 AccessToken ,请先前往 百度统计用户手册了解,介意者请谨慎部署。

可能有时会出现跨域请求的问题

开通前条件:链接:https://tongji.baidu.com/web/help/article?id=123

需要自有网站昨日 PV 数大于 100

# 实现

# 加入百度统计

  1. 进入百度统计,注册或者登录,新增站点,填入信息;

  2. 获取统计代码:在管理 -> 代码管理 -> 代码获取

  3. 因为主题内置百度统计,只需要下图框内的代码:

    image-20211229135506339

  4. 找到主题 yml 文件中 baidu_analytics ,后面填入上图框中的问号后面的代码;

# 新建页面

hexo new page census

在根目录下的 source 下新建文件夹,命名为 census ,并在该目录下新建文件: index.md ,填入以下内容:

---
title: 博客访问统计
date: 2021-12-29 15:30:00
---

# 更改语言并添加到菜单

  1. 刚才新建的 index.md 中,如果是用命令创建, title 一行是英文,改成对应的中文就可以了;
  2. themes\shoka\languages\zh-CN.yml 中,找到 menu 一行,新增 census: 博客访问统计
  3. 在主题的 yml 文件里,在 menu 里添加到菜单中

# 获取网站统计数据

  1. 进入百度统计 - 管理 - 数据导出服务;

  2. 开通数据导出服务,获得 API KeySecret Key ,之后直接打开数据导出服务页面或者登录开发者服务管理获取 API KeySecret Key ;

  3. 通过如下 URL 进入百度账号登录页,此处的登录账号就是您登录百度统计查看报告数据的账号,登录完成后将跳转至获取 code 的页面:

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

    其中:

    • {CLIENT_ID} 填写您的 API Key
  4. 通过身份验证获取 ACCESS_TOKEN ;用户同意授权后,将跳转至一个获取 CODE 的页面。复制 CODE 值后可将其加入以下 URL 换取 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=oob

    其中:

    • {CLIENT_ID} 填写您的 API Key
    • {CLIENT_SECRET} 填写您的 Secret Key
    • {CODE} 填写您刚才拿到的 CODE

    返回如下(举例):

    {
        "expires_in": 2592000,
        "refresh_token":"2.385d55f8615fdfd9edb7c4b5ebdc3e39.604800.1293440400-2346678-124328",
        "access_token":"1.a6b7dbd428f731035f771b8d15063f61.86400.1292922000-2346678-124328",
        "session_secret":"ANXxSNjwQDugf8615OnqeikRMu2bKaXCdlLxn",
        "session_key":" 248APxvxjCZ0VEC43EYrvxqaK4oZExMB",
        "scope":"basic"
    }
  5. (更新 Access Token 时使用,第一次跳过此步)从上述步骤得到的数据中包含 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}

    其中:

    • {REFRESH_TOKEN} 填写您的 Refresh Token

    • {CLIENT_ID} 填写您的 API Key

    • {CLIENT_SECRET} 填写您的 Secret Key

    返回如下(举例):

    {
        "expires_in": 2592000,
        "refresh_token":"2.385d55f8615fdfd9edb7c4b5ebdc3e39.604800.1293440400-2346678-124328",
        "access_token":"1.a6b7dbd428f731035f771b8d15063f61.86400.1292922000-2346678-124328",
        "session_secret":"ANXxSNjwQDugf8615OnqeikRMu2bKaXCdlLxn",
        "session_key":" 248APxvxjCZ0VEC43EYrvxqaK4oZExMB",
        "scope":"basic"
    }
  6. 调用百度统计 API:

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

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

    其中:

    • {ACCESS_TOKEN} 填写您的 Access Token
  7. 也可以在 Tongji API 调试工具 输入 ACCESS_TOKEN 获取网址 ID;

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

# 博客访问统计代码

在新建的 census 下的 index.md 中添加以下内容(注意修改一些数据):

{% raw %}
<script src="https://cdn.jsdelivr.net/npm/echarts@5.2.2/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@4.9.0/map/js/china.js"></script> <!-- 绘制地图需要另外添加 china.js -->
<!-- 访问地图 -->
<div id="map-chart" style="border-radius: 8px; height: 600px; padding: 10px;"></div>
<!-- 访问趋势 30 日 -->
<div id="trends-chartday" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
<!-- 访问趋势月 -->
<div id="trends-chartmonth" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
<!-- 访问来源 -->
<div id="sources-chart" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
<!-- 外部来源 -->
<div id="links-chart" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
<script>
var start_date = '20211020' // 开始日期
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 = 'xxxxxxxxx' // accessToken
var site_id = 'xxxxxxxx' // 网址 id
var dataUrl = '(后续自建的API网址)/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') === null ? '#000' : '#fff'
// 访问地图
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: ['#8a2be2', '#afeeee'],
          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 trendsChartDay () {
  let script = document.createElement("script")
  let paramUrl = '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=trend/time/a&gran=day'
  fetch(dataUrl + paramUrl)
    .then(data => data.json())
    .then(data => {
      let dayArr = []
      let dayValueArr = []
      let dayName = data.result.items[0]
      let dayValue = data.result.items[1]
      for (let i = Math.min(dayName.length, 30) - 1; i >= 0; i--) {
        dayArr.push(dayName[i][0].substring(0, 13))
        if (dayValue[i][0] !== '--') {
          dayValueArr.push(dayValue[i][0])
        } else {
          dayValueArr.push(null)
        }
      }
      let dayArrJson = JSON.stringify(dayArr)
      let dayValueArrJson = JSON.stringify(dayValueArr)
      script.innerHTML = `
        var trendsChartDay = echarts.init(document.getElementById('trends-chartday'), 'light');
        var trendsOptionDay = {
          textStyle: {
            color: '${color}'
          },
          title: {
            text: '博客访问统计图(30日)',
            x: 'center',
            textStyle: {
              color: '${color}'
            }
          },
          tooltip: {
            trigger: 'axis'
          },
          xAxis: {
            name: '日期',
            type: 'category',
            axisTick: {
              show: false
            },
            axisLine: {
              show: true,
              lineStyle: {
                color: '${color}'
              }
            },
            data: ${dayArrJson}
          },
          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: ${dayValueArrJson},
            markLine: {
              data: [{
                name: '平均值',
                type: 'average'
              }]
            }
          }]
        };
        trendsChartDay.setOption(trendsOptionDay);
        window.addEventListener("resize", () => { 
          trendsChartDay.resize();
        });`
      document.getElementById('trends-chartday').after(script);
    }).catch(function (error) {
      console.log(error);
    });
}
// 访问趋势月
function trendsChartMonth () {
  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 trendsChartMonth = echarts.init(document.getElementById('trends-chartmonth'), 'light');
        var trendsOptionMonth = {
          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'
              }]
            }
          }]
        };
        trendsChartMonth.setOption(trendsOptionMonth);
        window.addEventListener("resize", () => { 
          trendsChartMonth.resize();
        });`
      document.getElementById('trends-chartmonth').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 linksChart () {
  let script = document.createElement("script")
  let paramUrl = '&start_date=' + start_date + '&end_date=' + end_date + '&metrics=' + metrics + '&method=source/link/a';
  fetch(dataUrl + paramUrl)
    .then(data => data.json())
    .then(data => {
      monthArr = [];
      let linksName = data.result.items[0]
      let linksValue = data.result.items[1]
      let linksArr = []
      for (let i = 0; i < 20; i++) {
        linksArr.push({ name: linksName[i][0].name, value: linksValue[i][0] })
      }
      let linksArrJson = JSON.stringify(linksArr)
      script.innerHTML = `
        var linksChart = echarts.init(document.getElementById('links-chart'), 'light');
        var linksOption = {
          textStyle: {
            color: '${color}'
          },
          title: {
            text: '外部链接访问TOP20',
            x: 'center',
            textStyle: {
              color: '${color}'
            }
          },
          legend: {
            top: 'bottom',
            type: 'scroll',
            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: ${linksArrJson},
            itemStyle: {
              emphasis: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(255, 255, 255, 0.5)'
              }
            }
          }]
        };
        linksChart.setOption(linksOption);
        window.addEventListener("resize", () => { 
          linksChart.resize();
        });`
      document.getElementById('links-chart').after(script);
    }).catch(function (error) {
      console.log(error);
    });
}
function switchVisitChart () {
  // 这里为了统一颜色选取的是 “明暗模式” 下的两种字体颜色,也可以自己定义
  let color = document.documentElement.getAttribute('data-theme') === null ? '#fff' : '#000'
  try {
    let mapOptionNew = mapOption
    mapOptionNew.title.textStyle.color = color
    mapOptionNew.visualMap.textStyle.color = color
    mapChart.setOption(mapOptionNew)
  } catch (error) {
    console.log(error)
  }
  try {
    let trendsOptionDayNew = trendsOptionDay
    trendsOptionDayNew.title.textStyle.color = color
    trendsOptionDayNew.xAxis.axisLine.lineStyle.color = color
    trendsOptionDayNew.yAxis.axisLine.lineStyle.color = color
    trendsOptionDayNew.textStyle.color = color
    trendsChartDay.setOption(trendsOptionDayNew)
  } catch (error) {
    console.log(error)
  }
   try {
    let trendsOptionMonthNew = trendsOptionMonth
    trendsOptionMonthNew.title.textStyle.color = color
    trendsOptionMonthNew.xAxis.axisLine.lineStyle.color = color
    trendsOptionMonthNew.yAxis.axisLine.lineStyle.color = color
    trendsOptionMonthNew.textStyle.color = color
    trendsChartMonth.setOption(trendsOptionMonthNew)
  } 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)
  }
  try {
    let linksOptionNew = linksOption
    linksOptionNew.title.textStyle.color = color
    linksOptionNew.legend.textStyle.color = color
    linksOptionNew.textStyle.color = color
    linksChart.setOption(linksOptionNew)
  } catch (error) {
    console.log(error)
  }
}
if (document.getElementById('map-chart')) mapChart()
if (document.getElementById('trends-chartday')) trendsChartDay()
if (document.getElementById('trends-chartmonth')) trendsChartMonth()
if (document.getElementById('sources-chart')) sourcesChart()
if (document.getElementById('links-chart')) linksChart()
document.getElementsByClassName("theme")[0].addEventListener("click", function () { setTimeout(switchVisitChart, 100) })
</script>
{% endraw %}

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

# 自建 API

上面代码中的有一处需要自建 API,下面来介绍如何自建 API:

  1. 注册 Vercel,尽量用国外邮箱;

  2. 进入 dashboard,新建项目

    image-20211229144508557

  3. 导入 github 项目:

    image-20211229144616829

    image-20211229144711036

  4. 填入 ErukonGitHub api 项目:https://github.com/Eurkon/baidu-tongji-api
    项目网址:

https://github.com/Eurkon/baidu-tongji-api
  1. 如果提示是否是你的仓库,选Vercel 会自动帮你 fork 这个仓库到你的 Github 账号里。

  2. 导入静态页面仓库之前,需要为你的 Github 安装 Vercel ,此处建议选择 All repositories ,意为为所有仓库安装,当然,你也可以选择只为当前仓库安装,也就是 Only select repositories

    如果哪天反悔了,可以在 github-> 头像 (右上角)->settings->Applications(列表下数上第四个)->Installed Github Apps 里修改。

  3. 之后会识别出项目文件,单击 Continue

  4. VercelPROJECT NAME 可以自定义,不用太过在意,但是之后不支持修改,若要改名,只能删除 PROJECT 以后重建一个了。下方三个选项保持默认就好。

  5. 等待一会,完成部署,会出现以下界面:

    image-20211229150141726

  6. 记住 DOMAINS 后面的域名,修改上面代码中 dataUrl 的域名部分。

# 解决 pjax 加载问题

新增文章统计页面一样的方法,配置一次就可以了,如果第一次配置,参考新增文章统计页面的对应位置,或者看下面(水一次),如果已经配置过,请跳过!

如果做到此步,就已经成功了,但是会发现,第一次会加载不出来,显示空白,只有再次点击或者刷新后才会显示,下面来解决此问题

  1. 在主题的 yml 配置文件中,找到最下面的 vendors ,在 js 一栏新增 echarts 引入:
vendors:
	js:
		……
		echarts: npm/echarts@5.2.2/dist/echarts.min.js

代码:

echarts: npm/echarts@5.2.2/dist/echarts.min.js
  1. 找到 themes\shoka\scripts\generaters\script.js ,大概 24 行,找到 js ,新增 echarts 内容,内容为下:

    auto_scroll: theme.auto_scroll,
        js: {
          valine: theme.vendors.js.valine,
          chart: theme.vendors.js.chart,
          copy_tex: theme.vendors.js.copy_tex,
          fancybox: theme.vendors.js.fancybox,
          echarts: theme.vendors.js.echarts, // 新增
        },
        css: {

    代码:

    echarts: theme.vendors.js.echarts,
  2. 找到 themes\shoka\scripts\helpers\assets.js ,找到大概 50 行,新增 echartsconfig

    if (!config) return '';
      //Get a font list from config
      let vendorJs = ['pace', 'pjax', 'fetch', 'anime', 'algolia', 'instantsearch', 'lazyload', 'quicklink'].map(item => {   // 这里在 quicklink 后新增
        if (config[item]) {
          return config[item];
        }
        return '';
      });

    改后

    if (!config) return '';
      //Get a font list from config
      let vendorJs = ['pace', 'pjax', 'fetch', 'anime', 'algolia', 'instantsearch', 'lazyload', 'quicklink', 'echarts'].map(item => {
        if (config[item]) {
          return config[item];
        }
        return '';
      });

# 其他

本文中最长的 script 标签中的代码过于长,可以直接像本文一样加在 index.md 中,也可以单独保存这部分代码,上传到 cdn 中,通过外部引入;
如果想要修改图表的样式,请参考 echarts 官网:https://echarts.apache.org/zh/index.html,根据自行喜好对 ECharts 统计图进行修改。

完成!

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Lavender 微信支付

微信支付

Lavender 支付宝

支付宝