統計,其實很簡單
原文由 Nikita Prokopov 于 發布,訂閱此部落格
我跟統計數字有種微妙的關係:一方面,我盡量不常去看,大概一年只看個一兩次。因為數據分析其實沒什麼可操作的:我的文章有一千人看還是一萬人看,又有什麼差別?
我的意思是,你當然可以試著去猜讀者的口味,只寫那些熱門的東西,但那很快就會把你的靈魂消磨殆盡。
另一方面,只要有什麼東西沒有被記錄、存檔,以備日後查考,我就會感到不安。我現在或許不需要,但要是十年後我改變主意了呢?
能看到自己的讀者,也能讓你知道自己不是對著虛空寫作。所以我其實不需要太多東西,一些最基本的就好:像是每天/每篇文章有多少讀者,或許就夠了。
拼圖的最後一塊是:我的網站專案都是自行架站的,而且我用的是有點老派的網頁伺服器,而不是把這件事交給 Nginx。
靜態網站很受歡迎,而且是有道理的:它們快速、輕量,又能完成該做的事。但我這邊可能還有一兩個未完成的完形:我想在提供網頁時感受到電腦完整的威力,能做些超越靜態頁面的有趣事情。我需要那種手邊有完整程式語言可用的自由。我想用程式語言自己寫一個網頁伺服器(用 Clojure,抱歉了各位)。
現有的選擇
這一切讓我踏上了尋找一套完全符合我需求的統計方案之旅。Google Analytics 首先出局:臃腫、不重視隱私、使用者體驗糟糕、Google 很邪惡,等等。

其他用 JS 的方案或許也可行,但仍有疑慮:是 SaaS 嗎?要付費嗎?十年後還會在嗎?要自行架站嗎?它們的 Cookie 符合 GDPR 嗎?要怎麼計算 RSS 訂閱?
Nginx 有存取日誌,所以我試過直接讀取這些日誌的伺服器端統計工具(也就是 Goatcounter)。設定很簡單,但接著我得為它們建立網域、管理帳號、監控處理程序,而且以我的伺服器/請求量來說,它的效能還不夠好!
我的解法
所以我最後決定自己做一套。如果你的需求跟我類似,也歡迎一起來用。長這樣:

它很陽春,但做到了幾個對我來說很重要的事。
安裝設定
設定極其簡單。我是真心把它當成一項特色。
只要把我們的 middleware 加到你的 Ring 堆疊裡,就會自動搞定一切:資料收集與報表。
(def app
(-> routes
...
(ring.middleware.params/wrap-params)
(ring.middleware.cookies/wrap-cookies)
...
(clj-simple-stats.core/wrap-stats))) ;; <-- just add this這是真正意義上的零設定:沒有東西要設定、沒有東西要監控、依賴極少。它馬上就能開始運作,而且永遠不會跟你多要什麼。
想想看,你本來就已經有自己的網頁伺服器了,何不直接沿用你已經為它做好的那些設定?
請求類型
我們會區分請求的類型。就我而言,我只在乎真人,所以會把真人的請求跟 RSS 訂閱請求、favicon 請求、重新導向、錯誤網址以及機器人分開計算。機器人這陣子特別活躍。畢竟總得從哪裡弄到 AI 的訓練資料嘛。
從某種意義上說,RSS 訂閱者也是真人,所以我們特別花了功夫來正確計算。同一個讀者一天內請求 feed.xml 100 次,也只會算成一次請求。
託管型的 RSS 閱讀器常常會在 User-Agent 裡回報使用者數量,像這樣:
Feedly/1.0 (+http://www.feedly.com/fetcher.html; 457 subscribers; like FeedFetcher-Google)
Mozilla/5.0 (compatible; BazQux/2.4; +https://bazqux.com/fetcher; 6 subscribers)
Feedbin feed-id:1373711 - 142 subscribers向這份名單上的每一位致上我個人的敬意與謝意。我有看到你們。

圖表
視覺化很重要,選對圖表類型也同樣重要。這樣是錯的:

連續的折線會讓人聯想到內插。看起來就像在凌晨 5 點的 1 次造訪和 6 點的 11 次造訪之間,曾經有 2、3、5、9 次造訪的點,甚至可能有 5.5 次!但事實並非如此。
語意上正確的圖表應該長這樣:

座標軸的標示我們也花了心思,讓它維持在合理的範圍。你不會看到像 117、234、10875 這種數字。我們總是會依比例選擇適合的整數:100、200、500、1K 等等。
不用說,所有的圖表都有相同的垂直比例尺,而且水平捲動是同步的。
洞察
我們提供的功能不多(因為我本來就不需要太多),但你可以依頁面、查詢參數、來源網站、使用者代理,以及任意日期區間來篩選報表。
尚未實作(目前)
如果能對「這次流量高峰是怎麼來的?」有些洞察就好了。
能有基本的國家/地區分布也不錯。我確實有 IP 位址(不管還有多少參考價值),但我需要想辦法把 GeoIP 資料打包到一個合理的大小(最好在 1 Mb 以下;稍微犧牲一點精確度也沒關係)。
最後,我真正感興趣的一件事是「誰寫到了我?」我有來源網站的資料,問題只在於如何從雜訊中分離出有用的訊號。
效能方面。DuckDB 很厲害:它會壓縮資料並執行欄位查詢,所以每列多存幾個欄位也不會影響查詢效能。不過,每次開啟儀表板都還是要對整個資料庫做一次查詢,而目前(約三年的資料)資料庫大小約為 600 MiB。我確實得研究一下建立一些預先計算好的彙整資料。
改天再說吧。
如何取得
請前往 github.com/tonsky/clj-simple-stats 並依照說明操作:

歡迎告訴我你的想法!對你來說好用嗎?還有什麼可以改進的地方?
隨機一篇部落格
留言
登入後參與討論