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 存儲 | 幾 KB | 5 GB |
個人博客隨便用,連免費額度的零頭都用不完。
6. 部署
# 建 D1wrangler 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/