2022年8月,LeanCloud 国际版不再为来自中国大陆的 IP 提供服务,基于 LeanCloud 的站点统计因此失效,本文基于 Umami 的统计信息自建 PV UV 统计后台,解决上述问题。 前端、后端小白,对Python比较熟悉,后端代码用 Python 实现的,仅仅完成了功能,过程也比较繁琐,思路过程供大家参考
于是决定基于这款工具开发 PV UV 统计
Counter.0.jsonl
文件中,该文件主要内容为 json 格式,删去第一行稍加修改即可作为正常 json 文件使用依赖 Umami 的 API,需要搭建好 API 获取环境
import requests
import json
import mtutils as mt
from pathlib import Path
class Statistic:
root_url = "http://<url-to-umami>/api/website/1/"
header={
"Accept": "application/json",
"Access-Control-Allow-Origin": '*',
"Authorization": "Bearer <your-token>"
}
def __init__(self):
conter_path = Path(__file__).with_name('conter.json')
self.conter_dict = mt.json_load(conter_path)
@staticmethod
def make_json_str(pv, uv, act):
res_str = "(function vvdstatistics(){"+\
"var PVstatic='" +\
str(pv) +\
"';var dom=document.querySelector('#PVstatic');Array.isArray(dom)?dom[0].innerText=PVstatic:dom.innerText=PVstatic;"+\
"var UVstatic='" +\
str(uv) +\
"';var dom=document.querySelector('#UVstatic');Array.isArray(dom)?dom[0].innerText=UVstatic:dom.innerText=UVstatic;"+\
"var ACTstatic='" +\
str(act) +\
"';var dom=document.querySelector('#ACTstatic');Array.isArray(dom)?dom[0].innerText=ACTstatic:dom.innerText=ACTstatic;"+\
"})()"
return res_str
def active_num(self):
url = self.root_url + 'active'
res = requests.get(url=url, data=json.dumps({}), headers=self.header)
res_dict = json.loads(res.text)
act_str = max(1, res_dict[0]['x'])
return act_str
def PVUV_num(self):
url = self.root_url + 'stats?start_at=1350679719687&end_at=1990039038644'
res = requests.get(url=url, data=json.dumps({}), headers=self.header)
res_dict = json.loads(res.text)
pv = res_dict['pageviews']['value'] + self.conter_dict['site-pv']
uv = res_dict['uniques']['value'] + self.conter_dict['site-uv']
return pv, uv
def js_str(self):
pv, uv = self.PVUV_num()
act = self.active_num()
return self.make_json_str(pv, uv, act)
def post_pv(self, sub_url):
url = self.root_url + 'stats?start_at=1350679719687&end_at=1990039038644' + '&url=' + sub_url
res = requests.get(url=url, data=json.dumps({}), headers=self.header)
res_dict = json.loads(res.text)
pv = res_dict['pageviews']['value'] + self.conter_dict.get(sub_url, 0)
uv = res_dict['uniques']['value']
return pv, uv
root_url
和 header
中的 <url-to-umami>
和 <your-token>
为你自己的值- 利用 Umami API 获取需要的数据
- 整合成 js 字符串或直接返回数据
- js 串功能为修改ID 为 `PVstatic`, `UVstatic` 和 `ACTstatic` 的元素内容
from flask import Flask, request
from flask_cors import CORS
import mtutils as mt
from lib import statis_obj
port = '<your-port>'
log_file_path = '/usr/local/static/log.log'
app = Flask(__name__)
app.logger = mt.log_init(log_file_path)
CORS(app, supports_credentials=True)
@app.route("/statistics", methods=['GET','POST'])
def statistics():
res = statis_obj.js_str()
return res
@app.route("/poststats", methods=['GET','POST'])
def poststats():
url = request.data.decode('utf8')[1:-1]
pv, uv = statis_obj.post_pv(url)
return {'pv': pv, 'uv': uv}
if __name__ == '__main__':
app.logger("**************** Static Sever Start *******************")
app.run('0.0.0.0', port=port)
pass
<your-port>
换为你自己需要监听的端口- **statistics** 站点 PV UV 和 活跃用户数,返回内容为一段 js 代码 访问示例
- **poststats** 文章 PV UV 访问示例
代码调整好后需要让他在服务器自动运行
需要用到 systemctl 工具
此处 service 示例代码
[Unit]
Description = Service to count pv,uv for www.zywvvd.com
After = network.target
[Service]
ExecStart = /home/lighthouse/anaconda3/bin/python main.py
WorkingDirectory = /usr/local/static/
StandardOutput = inherit
StandardError = inherit
Restart = always
User = lighthouse
[Install]
WantedBy=multi-user.target
记得设置开机自动启动
sudo systemctl enable pvuv.service
我选择在 Fluid 主题配置文件中加入该部分前端代码
打开 Hexo/_config.fluid.yml
文件
关闭原始 PV、UV 统计
# 展示网站的 PV、UV 统计数
# Display website PV and UV statistics
statistics:
enable: false
# 统计数据来源,使用 leancloud 需要设置 `web_analytics: leancloud` 中的参数;使用 busuanzi 不需要额外设置,但是有时不稳定,另外本地运行时 busuanzi 显示统计数据很大属于正常现象,部署后会正常
# Data source. If use leancloud, you need to set the parameter in `web_analytics: leancloud`
# Options: busuanzi | leancloud
source: "leancloud"
pv_format: "总访问量 {} 次"
uv_format: "总访客数 {} 人"
由于 LeanCloud 仅在大陆无法访问,国外网友访问时还是会正常显示一行 PV,UV 统计,为了避免重复把原来的关掉
在 footer.content 中加入前端代码
<div style="font-size: 0.85rem;">
<span> 总访问量 <span style="color: #d7d8d9;" id="PVstatic">0</span> 次 </span>
<span> 总访客数 <span style="color: #d7d8d9;" id="UVstatic">0</span> 人 </span>
<span> 当前在线 <span style="color: #d7d8d9;" id="ACTstatic">0</span> 人 </span>
<script src="https://<url-to-app>/statistics" defer></script>
</div>
修改 fluid 主题配置文件 Hexo/_config.fluid.yml
,加入新的文章浏览计数来源,我起名叫 vvdpostpvuv
# 浏览量计数
# Number of visits
views:
enable: true
# 统计数据来源
# Data Source
# Options: busuanzi | leancloud | 自建基于 Umami 统计的 文章PV vvdpostpvuv
source: "vvdpostpvuv"
format: "{} 次"
打开文件 Hexo/themes/fluid/layout/_partials/post/meta-top.ejs
, 加入新的 else 分支:
<% } else if (theme.post.meta.views.source === 'busuanzi') { %>
<span id="busuanzi_container_page_pv" style="display: none">
<i class="iconfont icon-eye" aria-hidden="true"></i>
<%- views_texts[0] %><span id="busuanzi_value_page_pv"></span><%- views_texts[1] %>
</span>
<% import_js(theme.static_prefix.busuanzi, 'busuanzi.pure.mini.js', 'defer') %>
<% } else if (theme.post.meta.views.source === 'vvdpostpvuv') { %>
<span id="vvdpost_container_page_pvuv" style="display: none">
<i class="iconfont icon-eye" aria-hidden="true"></i>
<%- views_texts[0] %><span id="vvdpost_value_page_pv">0</span><%- views_texts[1] + '  ' %>
<i class="iconfont icon-users" aria-hidden="true"></i>
<%- views_texts[0] %><span id="vvdpost_value_page_uv">0</span><%- ' 人' %>
</span>
<script>
console.log(window.location.pathname)
var httpRequest = new XMLHttpRequest();
httpRequest.open('POST', 'https://101.43.39.125:6101/poststats', true);
httpRequest.setRequestHeader("Content-type","application/json");
httpRequest.send(JSON.stringify(window.location.pathname));
httpRequest.onreadystatechange = function () {//请求后的回调接口,可将请求成功后要执行的程序写在其中
if (httpRequest.readyState == 4 && httpRequest.status == 200) {//验证请求是否发送成功
var json = httpRequest.responseText;//获取到服务端返回的数据
var obj = JSON.parse(json);
console.log(obj.pv);
var pvCtn = document.querySelector('#vvdpost_container_page_pvuv');
console.log(pvCtn);
if (pvCtn) {
var pv_ele = document.querySelector('#vvdpost_value_page_pv');
console.log(pv_ele);
var uv_ele = document.querySelector('#vvdpost_value_page_uv');
console.log(uv_ele);
if (uv_ele && uv_ele) {
pv_ele.innerText = obj.pv;
uv_ele.innerText = obj.uv;
pvCtn.style.display = 'inline';
}
}
}
};
</script>
<% } %>