2026 iThome 鐵人賽的每日觀察儀表板。靜態站,GitHub Actions 定時抓資料,Cloudflare Pages 免費託管。
🔗 線上站:https://ithome-ironman-observer.happyhacking.ninja/
網站有四頁:
- 賽事觀察台(首頁):每支參賽系列一張卡片,看得到進度和人氣
- 團隊計分板:把組團視為參賽單位,總瀏覽、人均、平均進度、今日發文與落後警示
- 名人堂:表列具公眾知名度的參賽作者(如高見龍),附一句話介紹、可驗證來源連結與其系列文章
- Insights 分析:整屆賽事的分析,發文時段、人氣分佈、組別、標題文字等圖表
本專案源自 qrtt1/ithome-ironman 的 ITHome 鐵人觀察家(觀戰區), 因原專案已停止維護,故另開新專案接手這個概念,以現代工具鏈(Bun + Astro + GH Actions + Cloudflare Pages)重寫。
這是一個實驗:以 Command Code 的 $1/月 訂閱,搭配 DeepSeek v4 Flash, 能否真的做出一個有趣、可用的東西?
整個專案(scraper、儀表板、Insights 分析、CI/CD、部署、測試)都由這個組合完成。
第一個核心功能版本的費用:
| 項目 | 數值 |
|---|---|
| Total Tokens | 57.5M |
| 費用 | $0.04 |
| Runs | 839 |
- 年度切換(年度清單以
meta.json的years為準) - 組別篩選 + 我的收藏分頁(以系列 ID 為 key 跨年度共用,只存在本裝置 localStorage)
- 排序:參賽進度、最多觀看、最新發文、當篇觀看(今日)
- 搜尋(標題/作者/組別/團隊,token AND)
- 團隊計分板:把組團視為參賽單位,總瀏覽/人均/平均進度/今日發文 + 落後警示(今日缺發/停更/未開賽),可展開成員並跳轉該隊系列流;另有獨立分頁
/teams/(header 右上角入口) - 名人堂(/hall-of-fame/):表列具公眾知名度的參賽作者(如高見龍),附一句話介紹、可驗證來源連結與其系列文章。
- 卡片狀態標示:DAY n/30 進度條、完賽、已刪文、尚未開賽
- 靜態頁每 60 秒客端重讀年度 JSON;抓取錯誤顯示在 scrapeLog 狀態列
- 深色/淺色主題(預設隨系統,可手動切換)、響應式
用 ECharts 呈現整屆賽事的資料分析,支援客端切換年度:
- 發文時段直方圖、發文星期、星期 × 小時熱力圖
- 瀏覽數分佈(分桶 + 百分位 CDF)、訂閱數 Top 系列
- 組別分析(系列數/文章數/平均瀏覽/總訂閱)
- 標題關鍵字詞頻、標題長度分佈
- 棄賽進度斷崖、互動轉換率排行、斷更風險名單
分析計算都是純函式(web/src/lib/insights.ts),附單元測試;時段和星期用臺北時間(UTC+8)算,不依賴瀏覽器時區。
- SEO:
robots.txt+llms.txt,方便爬蟲和 AI agent 讀取 - 無障礙: 循序標題層級、muted/badge 配色符合 WCAG AA 對比
- 效能: 內嵌 critical CSS、SSR 卡片分批切片、
content-visibility: auto、分塊渲染、iThome preconnect - Lighthouse: Performance 99、Accessibility 100、Best Practices 100、SEO 100(報告在
docs/lighthouse-report.html)
- 沒有後端、沒有資料庫,JSON 就是資料庫。
- 每次抓取輸出
data/{year}.json和data/meta.json,並寫一份每日歷史快照到data/history/{year}/{date}.json。 快照每次都寫(看似浪費,實際上與同一輪的data/{year}.json內容完全相同,git 內容定址只存一份 blob)。 - 硬限制:近乎零成本,靠 Cloudflare Workers/Pages 免費額度 + GH Actions 公開 repo 免費 runner + 自有網域撐著。
- Non-goals:即時更新(只有每 10 分鐘一次的批次)。
bun install
bun run scripts/scrape.ts # 依 series-manifest 陣列逐年度抓取;全失敗零寫入、exit 1
bun run scripts/scrape.ts --full # 完整校正(不走 RSS 快速路徑,重讀每個系列頁)
cd web && bun install && bun run dev系列頁回傳 HTTP 403 或無效 HTML 時,RSS fallback 會依文章 ID 合併快取,保留 RSS 未包含的舊文章。
此結果記錄為 [stale]:文章完整性與互動統計尚未驗證;空白/無效 RSS 保留快取,無快取則 [failed]。
rssFallback 標記會保留到完整 HTML 抓取成功(如每日 --full 校正)。
增量抓取採 RSS-First 策略:先前已降級且 RSS 無新文章、天數未增加者,直接複用 RSS 摘要避免無效敲 HTML 系列頁(節省 50% 請求並防範 WAF)。
僅在系列頁與 RSS 皆遭遇 403 阻斷或無法取得時,才計入連續 403 中止與熔斷器保護,兼顧大規模封鎖防護與海外機房正常排程更新。
web/public/data/ 是建置產物(web/scripts/copy-data.mjs 由 data/*.json 產生),
已 gitignore;bun run dev 和 bun run build 都會先跑一次 copy。
卡片右上角星號可收藏系列;「我的收藏」分頁只顯示已收藏系列,沿用排序器。 收藏以系列 ID 為 key 跨年度共用,只存在本裝置瀏覽器(localStorage),不同裝置和瀏覽器之間不互通。
bun run check # lint + typecheck + 單元測試 + astro check(CI 跑的同一組)
bun test # 只跑單元測試
bun run lint # 只跑 BiomeCI(.github/workflows/ci.yml)在 push 到 main 與所有 PR 上跑這整組。
Biome 只開 linter、不開 formatter,理由見 docs/tooling.md。
-
Cloudflare Pages 專案
ironman-observer-next(workflow 會自動建立) -
GitHub repo secrets:
CLOUDFLARE_API_TOKEN(Pages Edit 權限)、CLOUDFLARE_ACCOUNT_ID -
自有網域在 Cloudflare dashboard → Pages 專案 → Custom domains 設定
-
Cloudflare Worker
ironman-observer-trigger(cron0 */2 * * *,secrets:GITHUB_TOKEN、GITHUB_REPO、DISPATCH_SECRET)定時觸發 workflow;也可gh workflow run scheduled-update手動觸發POST /dispatch需要Authorization: Bearer $DISPATCH_SECRET(該端點會觸發一整輪爬蟲, 不能開放匿名呼叫);GET /維持公開健康檢查。secrets 設定見.github/workflows/README.md。
