635 字
3 分鐘
博客瀏覽量統計:Cloudflare Workers + D1 的極簡方案

0. TL;DR#

問題結論
用什麼統計?Cloudflare Workers + D1,單文件 Worker
怎麼去重?IP + 日期,同一 IP 一天只計一次
要錢嗎?不用,D1 免費額度(每天 10 萬次讀取)綽綽有餘
數據在哪?自己的 D1 數據庫,隨時導出

1. 為什麼不用 GA?#

Google Analytics 功能強大,但:

  • 加載第三方 JS,拖慢首屏
  • 隱私政策麻煩(歐盟用戶要彈 cookie 橫幅)
  • 數據在 Google 手裡

博客只需要兩個數字:每篇文章多少人看過,全站總共多少瀏覽。自己寫個 100 行的 Worker 就夠了。


2. 架構#

瀏覽器打開文章頁
↓ fetch POST /hit {path}
Worker
├─ 取 CF-Connecting-IP
├─ 查 D1:今天這個 IP 看過這篇文章嗎?
├─ 沒看過 → views+1,記錄 IP+日期
└─ 看過 → 直接返回當前數
↓
前端顯示閱讀數

2.1 數據表#

CREATE TABLE views (
path TEXT PRIMARY KEY,
count INTEGER DEFAULT 0
);
CREATE TABLE seen (
path TEXT,
ip_hash TEXT,
date TEXT,
PRIMARY KEY (path, ip_hash, date)
);

seen 表存”誰今天看過哪篇”,用 IP 的 SHA256 哈希(不存原始 IP,隱私考慮)。

2.2 API#

  • POST /hit {path} → 記錄一次瀏覽,返回 {count}
  • GET /count?path= → 查單篇閱讀數
  • GET /batch?paths= → 批量查(列表頁用)
  • GET /total → 全站總數(頁腳顯示)

2.3 前端#

文章頁加載時調 /hit,頁腳調 /total。都是簡單的 fetch,失敗了也不影響頁面(靜默降級)。


3. 去重邏輯#

const today = new Date().toISOString().slice(0, 10);
const ipHash = await sha256(ip);
const seen = await db.prepare(
"SELECT 1 FROM seen WHERE path=? AND ip_hash=? AND date=?"
).bind(path, ipHash, today).first();
if (!seen) {
await db.batch([
db.prepare("INSERT INTO seen VALUES (?,?,?)").bind(path, ipHash, today),
db.prepare("INSERT INTO views(path,count) VALUES(?,1) ON CONFLICT(path) DO UPDATE SET count=count+1").bind(path),
]);
}

同一 IP 同一天看同一篇文章,只計一次。換天重新計(算活躍讀者)。


4. 隱私考慮#

  • 不存原始 IP:只存 SHA256 哈希,加鹽
  • 不存 User-Agent:不需要
  • 不跨站追蹤:每個博客獨立,沒有第三方 cookie
  • 數據可刪除:D1 是自己的,一條 SQL 清空

5. 成本#

項目用量免費額度
Worker 請求每天幾百次每天 10 萬次
D1 讀取每天幾千次每天 10 萬次
D1 寫入每天幾百次每天 5 萬次
D1 存儲幾 KB5 GB

個人博客隨便用,連免費額度的零頭都用不完。


6. 部署#

Terminal window
# 建 D1
wrangler d1 create blog-views
# 建表
wrangler d1 execute blog-views --file schema.sql
# 部署
wrangler deploy

前端加兩行 JS 就行。完整代碼 100 多行,一個文件搞定。


輕量、隱私、免費——個人博客的瀏覽量統計就該這麼簡單。

博客瀏覽量統計:Cloudflare Workers + D1 的極簡方案
https://blog.ouneed.tech/posts/blog-views-stats/
作者
winter
發佈於
2026-10-17
許可協議
CC BY-NC-SA 4.0