用 Django 打造網站後台統計
以「水井三寶」品牌網站為案例,從匿名訪客辨識、瀏覽事件、每日彙總,到 Django Admin 統計與 PythonAnywhere 部署,完成一套兼顧教學、實用與隱私的網站分析功能。
01 · Motivation網站上線之後,如何知道真的有人使用?
網站是否有人造訪,只看訂單或留言是不夠的。多數使用者會先閱讀故事、觀看影片、操作地圖,經過多次接觸後才採取行動。
水井三寶網站包含品牌故事、線上繪本、影音、互動地圖、編程任務與工藝商城。因此,除了「總瀏覽次數」,更需要知道使用者對哪種內容感興趣,以及教育活動是否真的被操作。
02 · Learning Goals本單元學習目標
- 理解「瀏覽次數」與「不重複訪客」的差異。
- 使用 Django Middleware 自動記錄公開頁面瀏覽。
- 使用 JavaScript 與 Django 端點記錄影片、閱讀、地圖及任務事件。
- 設計原始事件與每日彙總兩層資料模型。
- 在 Django Admin 呈現期間統計、熱門頁面、篩選及 CSV 匯出。
- 理解匿名化、資料最小化與保存期限等隱私原則。
- 將功能部署到 PythonAnywhere 並完成測試。
03 · Architecture資料如何從網站走到管理後台?
系統採用兩種蒐集方式:
page_view。04 · Data Model設計原始事件與每日彙總
原始事件 AnalyticsEvent
每次操作保存一筆事件,方便日後回答「何時發生、發生在哪一頁、涉及哪一項內容」。
class AnalyticsEvent(models.Model):
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
event_type = models.CharField(max_length=32, choices=EVENT_CHOICES)
path = models.CharField(max_length=500, blank=True, db_index=True)
content_type = models.CharField(max_length=40, blank=True)
content_id = models.CharField(max_length=80, blank=True)
content_title = models.CharField(max_length=200, blank=True)
visitor_hash = models.CharField(max_length=64, db_index=True)
referrer_domain = models.CharField(max_length=255, blank=True)
metadata = models.JSONField(default=dict, blank=True)
每日彙總 DailyAnalytics
同一天、同一事件與同一內容合併為一筆,保存操作次數與匿名訪客數。這能降低後台報表每次掃描大量原始事件的負擔。
class DailyAnalytics(models.Model):
date = models.DateField(db_index=True)
event_type = models.CharField(max_length=32, choices=EVENT_CHOICES)
path = models.CharField(max_length=500, blank=True)
content_type = models.CharField(max_length=40, blank=True)
content_id = models.CharField(max_length=80, blank=True)
total_count = models.PositiveIntegerField(default=0)
unique_visitors = models.PositiveIntegerField(default=0)
| 指標 | 意義 | 例子 |
|---|---|---|
| total_count | 事件總發生次數 | 同一人看首頁 3 次,計為 3 |
| unique_visitors | 不重複匿名訪客數 | 同一人看首頁 3 次,計為 1 |
| event_type | 使用者行為類別 | page_view、video_play、map_location |
05 · Middleware自動記錄公開頁面瀏覽
Middleware 位於請求與回應的共同通道,適合處理每個頁面都需要執行的統計邏輯。只有 GET 且回應狀態為 200 的公開頁面才列入統計。
class AnalyticsMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
prepare_visitor(request)
response = self.get_response(request)
should_track = (
request.method == "GET"
and response.status_code == 200
and not request.path.startswith(EXCLUDED_PREFIXES)
and not is_bot(request)
and not request.user.is_staff
)
if should_track:
record_event(request, "page_view")
set_visitor_cookie(request, response)
return response
在 settings.py 中,應放在 AuthenticationMiddleware 後方,才能辨識登入者是否為管理人員:
MIDDLEWARE = [
# ...
"django.contrib.auth.middleware.AuthenticationMiddleware",
"website.middleware.AnalyticsMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
]
06 · Event Tracking不只看頁面,也記錄學習互動
| 事件 | 觸發時機 | 教學意義 |
|---|---|---|
| reading_start | 進入線上讀物 | 知道作品被開啟幾次 |
| reading_page | 切換繪本頁面 | 觀察閱讀進度 |
| reading_complete | 抵達最後一頁 | 估算完成閱讀人數 |
| video_play/video_complete | 影片播放/播畢 | 比較點閱與完播 |
| map_location | 點選地圖景點 | 辨識熱門文化景點 |
| mission_start/mission_complete | 開始/完成編程任務 | 評估教材使用情況 |
| add_to_cart/order_complete | 購物流程 | 觀察商品轉換 |
HTML 元件用 data-* 描述事件,不必在每個按鈕重寫一套程式:
<button
data-analytics-event="map_location"
data-content-type="map_location"
data-content-id="{{ location.id }}"
data-content-title="{{ location.name }}">
{{ location.coordinate }}
</button>
JavaScript 透過同源 POST 傳送資料,並附上 CSRF Token:
sendAnalyticsEvent("video_play", {
content_type: "video",
content_id: video.dataset.contentId,
content_title: video.dataset.contentTitle
});
伺服器端必須再次檢查事件白名單與欄位長度。前端送來的內容永遠不能直接信任。
07 · Admin Dashboard把資料變成可用的後台資訊
後台「每日流量統計」首頁提供三個時間範圍,以及最近 30 日熱門頁面:
後台應具備的操作
- 依日期、事件、內容類型篩選。
- 搜尋頁面名稱、內容名稱及網址路徑。
- 查看熱門頁面排行榜。
- 選取資料並匯出 UTF-8 CSV,供 Excel 或研究報告使用。
- 瀏覽原始事件以除錯,但不允許一般後台操作任意修改統計。
@admin.register(DailyAnalytics)
class DailyAnalyticsAdmin(admin.ModelAdmin):
list_display = (
"date", "event_type", "page_title",
"content_title", "total_count", "unique_visitors"
)
list_filter = ("event_type", "date", "content_type")
search_fields = ("page_title", "content_title", "path")
actions = [export_as_csv]
08 · Privacy by Design統計功能也要尊重使用者
本案例不保存 IP 位址,也不建立姓名、Email 與瀏覽紀錄的關聯。瀏覽器只保存隨機識別碼,伺服器再以網站密鑰進行 SHA-256 雜湊。
visitor_hash = hashlib.sha256(
f"{settings.SECRET_KEY}:{visitor_id}".encode("utf-8")
).hexdigest()
python manage.py prune_analytics --days 180
09 · Deployment部署到 PythonAnywhere
將修訂檔上傳並解壓至專案根目錄後,在 Bash Console 執行:
cd /home/ShuijingTreasures/ShuijingTreasures
python manage.py migrate
python manage.py collectstatic --noinput
python manage.py check
python manage.py test
最後到 Web 頁面按下 Reload,再依序完成以下驗證:
- 用未登入的瀏覽器開啟首頁兩次。
- 登入 Django Admin,進入「每日流量統計」。
- 確認瀏覽次數為 2、訪客人數為 1。
- 播放影片、點選地圖景點,再確認對應事件。
- 管理者瀏覽後台或公開頁面時,不應增加統計。
10 · Classroom Practice課堂練習與延伸挑戰
練習一|判讀指標
某日首頁有 120 次瀏覽、45 位訪客;繪本有 60 次開始閱讀、18 次完成閱讀。請計算平均每位訪客瀏覽次數與閱讀完成率,並提出一項改善建議。
練習二|新增分享事件
新增 share_click 事件,在使用者按下社群分享按鈕時記錄分享平台及內容名稱。思考哪些 metadata 可以保存,哪些資料不應保存。
進階挑戰|完成率報表
在後台增加「繪本完成率」與「影片完播率」,並討論分母應使用事件次數還是不重複訪客數。
11 · Troubleshooting常見問題
後台完全沒有統計資料
確認已執行 migration、Middleware 已加入 settings、使用者不是後台管理員,且瀏覽器未送出 DNT(Do Not Track)。
影片播放沒有被記錄
確認新版 JavaScript 已執行 collectstatic、HTML 有 data-analytics-video,並在瀏覽器開發工具檢查事件 POST 是否回傳 200。
collectstatic 出現 PermissionError
chmod -R u+rwX static staticfiles
python manage.py collectstatic --noinput
訪客數看起來比預期多
使用者清除 Cookie、改用其他瀏覽器或裝置時,會被視為新的匿名訪客。此數字是近似值,不等同真實人數。
12 · Summary從「能上線」走向「能被理解」
網站開發不只是在頁面上呈現內容,也需要建立回饋循環:發布內容、觀察使用、分析結果,再改善設計與教材。
透過 Django Middleware、事件端點、每日彙總與 Admin 報表,水井三寶網站能在不保存 IP 的前提下,掌握文化內容、教育互動與商品流程的使用情況。這套案例同時串連 HTTP、Cookie、資料庫、JavaScript、後台管理、部署及資料倫理,是一個完整的網際網路課程實作單元。
DjangoPythonJavaScript網站分析PythonAnywhere資料隱私地方創生
沒有留言:
張貼留言