静态博客如Hexo/Hugo/Jekyll近些年很流行,markdown写作,一键生成部署,无需后端,可托管在各种网站平台,非常方便。但正因为无后端,动态信息的存取就成为了痛点:文章阅读数统计,评论系统等等。本站采用的是Hexo+Waline的方式实现文章阅读数统计与评论系统,最近也去掉了LeanCloud的依赖,所有数据使用MongoDB存储。
突然发现缺少一个全站访问量统计的功能,Waline目前不支持。大多数静态网站使用不蒜子,但看到由于使用人数众多,常常出现502错误和服务不稳定的情况。遂考虑自行开发这样的一个服务:FiniCounter。使用Vercel Serverless Function作为Web框架,MongoDB为后端存储。用户访问任意页面时,通过Fetch API调用Serverless Function,在MongoDB中计数加1并返回最新计数,在前端展示。
本想开发个小工具自己用,后来发现天然支持多用户,独乐乐不如众乐乐,大家一起用吧 :-)。效果展示:
FiniCounter
。

统计API后端实现
后端非常简单,只需要接收一个GET请求,在MongoDB中计数加1即可。Node.js不熟,主要精力都花在了如何在Vercel中使用Python Web框架Sanic了,趟了不少坑。
API为Http GET请求,格式如下:https://finicounter.eu.org/counter?host=xxx.com ,按hostname进行计数。
核心代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| 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。

前端实现
前端采用Fetch API实现,获取计数完成后会将网页中id为 finicount_views 元素对应的innerText 设置为计数,前端API的javascript实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| async function getViews() {
let url = "https://finicounter.eu.org/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 。使用的同学可以按需定制修改。
如何使用
基本用法
- 添加一行javascript到网页:
1
| <script async src="//finicounter.eu.org/finicounter.js"></script>
|
- 添加任意容器来显示访客数,id 必须为
finicount_views :
1
| <span id="finicount_views"></span>
|
搞定!访客数已经可以在你的网站中显示了,就像这样:12,345,678 。
Hexo NexT集成
如果使用NexT主题,可采用
Injects
的方法进行集成,将网站访问数显示在页脚:

在Hexo根目录的 scripts/ 中添加新文件 totalpageview.js 即可:
1
2
3
| hexo.extend.filter.register('theme_inject', function(injects) {
injects.footer.raw('totalpageview', '<div><span><a href="https://finicounter.eu.org/" target="_blank">Total Pageview:</a></span><span id="finicount_views" style="display:inline;padding-left:5px;"></span><div> <script async src="//finicounter.eu.org/finicounter.js"></script>', {}, {cache: false});
});
|
如果受欢迎,可以开发一个Hexo NexT Plugin进一步简化集成操作 :-) 。
Demo
Demo网站
本站也正在使用此服务 :-) ,页脚有显示,本站的
Demo网站
。
完整代码
完整代码见此
代码库
。