FiniCounter: 静态网站访问量统计工具

静态博客如Hexo/Hugo/Jekyll近些年很流行,markdown写作,一键生成部署,无需后端,可托管在各种网站平台,非常方便。但正因为无后端,动态信息的存取就成为了痛点:文章阅读数统计,评论系统等等。本站采用的是Hexo+Waline的方式实现文章阅读数统计与评论系统,最近也去掉了LeanCloud的依赖,所有数据使用MongoDB存储。

突然发现缺少一个全站访问量统计的功能,Waline目前不支持。大多数静态网站使用不蒜子,但看到由于使用人数众多,常常出现502错误和服务不稳定的情况。遂考虑自行开发这样的一个服务:FiniCounter。使用Vercel Serverless Function作为Web框架,MongoDB为后端存储。用户访问任意页面时,通过Fetch API调用Serverless Function,在MongoDB中计数加1并返回最新计数,在前端展示。

本想开发个小工具自己用,后来发现天然支持多用户,独乐乐不如众乐乐,大家一起用吧 :-)。效果展示:FiniCounter

FiniCounter Demo

统计API后端实现

后端非常简单,只需要接收一个GET请求,在MongoDB中计数加1即可。Node.js不熟,主要精力都花在了如何在Vercel中使用Python Web框架Sanic了,趟了不少坑。

API为Http GET请求,格式如下:https://finicounter.vercel.app/counter?host=xxx.com ,按hostname进行计数。

核心代码实现:

MONGODB_URL = os.environ.get("MONGODB_URL")
CORS_ORIGINS = os.environ.get("CORS_ORIGINS")
DB_NAME = os.environ.get("DB_NAME") if os.environ.get("DB_NAME") is not None else "MyCounter"
COLLECTION_NAME = "Counter"

cors_headers = {"Access-Control-Allow-Origin": CORS_ORIGINS, "Access-Control-Allow-Method": "POST, GET, OPTIONS"}

mongoClient = pymongo.MongoClient(MONGODB_URL)
db = mongoClient[DB_NAME]
collection= db[COLLECTION_NAME]

app = Sanic("finicounter")

@app.route("/counter")
async def counter(request):
    args = request.get_args()
    host = args["host"][0]
    result = collection.find_one_and_update({"host": host}, {"$inc": {"views": 1}, "$set": { "updateTime": datetime.datetime.utcnow()}}, upsert = True, return_document = pymongo.collection.ReturnDocument.AFTER, maxTimeMS=50)
    return response.json({"views": result['views']}, headers = cors_headers)


app.run(host="0.0.0.0", port=8000, fast=True)

自行部署

自行部署非常方便,仅需一个Vercel账户和一个MongoDB。

fork 代码库 https://github.com/finisky/finicounter 到你的GitHub账户,在Vercel中新建Project并导入该库,并设置如下三个环境变量即可:

Value
CORS_ORIGINS * (or your domain)
DB_NAME MongoDB DB name
MONGODB_URL MongoDB Connection String

数据存储在 DB DB_NAME 中的 Counter Collection。

Vercel Env Setting

前端实现

前端采用Fetch API实现,获取计数完成后会将网页中id为 finicount_views 元素对应的innerText 设置为计数,前端API的javascript实现:

async function getViews() {
    let url = "https://finicounter.vercel.app/counter?host=" + window.location.hostname;
    try {
        let res = await fetch(url);
        return await res.json();
    } catch (error) {
        console.log(error);
    }
}

async function renderViews() {
    let res = await getViews();
    let elem = document.getElementById("finicount_views");
    if (typeof elem !== 'undefined' && elem !== null) {
        elem.innerText = formatNumber(res.views);
    }
}

function formatNumber(num) {
    return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
}

renderViews();

其中 formatNumber 函数会将较大的数字按逗号分隔显示,如 12,345,678 。使用的同学可以按需定制修改。

如何使用

基本用法

  1. 添加一行javascript到网页:
<script async src="//finicounter.vercel.app/finicounter.js"></script>
  1. 添加任意容器来显示访客数,id 必须为 finicount_views :
<span id="finicount_views"></span>

搞定!访客数已经可以在你的网站中显示了,就像这样:12,345,678

Hexo NexT集成

如果使用NexT主题,可采用 Injects 的方法进行集成,将网站访问数显示在页脚:

FiniCounter Hexo NexT Demo

在Hexo根目录的 scripts/ 中添加新文件 totalpageview.js 即可:

hexo.extend.filter.register('theme_inject', function(injects) {
  injects.footer.raw('totalpageview', '<div><span><a href="https://finicounter.vercel.app/" target="_blank">Total Pageview:</a></span><span id="finicount_views" style="display:inline;padding-left:5px;"></span><div> <script async src="//finicounter.vercel.app/finicounter.js"></script>', {}, {cache: false});
});

如果受欢迎,可以开发一个Hexo NexT Plugin进一步简化集成操作 :-) 。

Demo

Demo网站

本站也正在使用此服务 :-) ,页脚有显示,本站的 Demo网站

完整代码

完整代码见此 代码库