Skip to content

s22shadowl/cpbl_takao_today_BE

Repository files navigation

CPBL 數據後端專案

專案總覽 (Project Overview)

本專案是一個用於抓取中華職棒(CPBL)官方網站數據的後端服務,旨在提供一個通用、穩健且可擴展的數據平台。它採用容器化技術(Docker)封裝,並已成功部署至 Fly.io 雲端平台,且與 GitHub Actions 整合,實現了推送即部署的 CI/CD 流程與成本優化的自動化排程。

服務核心功能是自動化爬取比賽賽程、逐場比賽的詳細攻守數據(包含所有球員的逐打席紀錄),以及球員的球季數據歷史。架構上,採用非同步任務佇列(Dramatiq + Redis)來處理耗時的爬蟲任務,並透過一個受 API 金鑰保護的 RESTful API 提供從基本查詢到複雜情境分析的多種數據需求。

主要特色 (Features)

  • 通用數據抓取: 自動抓取並儲存賽季中所有球隊、所有球員的逐打席紀錄,而非針對特定目標。
  • 歷史數據追蹤: 記錄球員球季數據的歷史快照,可供分析其表現趨勢。
  • 高效能 API: 透過資料庫查詢優化與應用層快取,確保複雜的分析型 API 也能快速回應。
  • 穩健的背景任務: 使用 Dramatiq 與 Redis,將耗時的爬蟲工作與 API 伺服器分離。
  • 現代化依賴項管理: 全面採用 Poetry 進行精確、可重複的依賴項管理,以 pyproject.toml 作為唯一事實來源。
  • 簡化的開發者體驗: 提供 Makefile 將複雜的 Docker 指令封裝為 make bulk-scrape 等易於記憶的指令。
  • 豐富的 RESTful API: 基於 FastAPI,提供多層次的數據查詢與分析能力,並內建互動式 API 文件。
  • 容器化開發與部署: 使用 Docker 與 Docker Compose 建立標準化的開發與生產環境。
  • 資料庫版本控制: 使用 Alembic 管理資料庫結構的遷移。
  • 雲端原生架構: 部署於 Fly.io,將 API (web) 與爬蟲 (worker) 拆分為獨立服務,並整合 Xvfb 繞過反爬蟲機制。
  • 自動化品質與安全: 整合 pre-commit (Ruff) 與 GitHub Actions CI/CD 流程,並透過 pip-audit 進行依賴項安全掃描。

生產環境架構 (Production Architecture)

本專案在 Fly.io 上的生產環境由以下幾個核心元件組成:

  • Fly App (cpbl-takao-today-be): 專案的主應用程式容器。
    • Web Service (web): 運行 FastAPI 的 Uvicorn 伺服器,負責接收所有 API 請求,並將耗時任務發送至佇列。
    • Worker Service (worker): 運行 Dramatiq Worker,專門執行由 app/workers.py 中定義的背景任務。它在 Xvfb 虛擬顯示環境中運行,使其能以 headless=False 模式操作瀏覽器,應對複雜的網站反爬蟲機制。
  • Fly PostgreSQL: 由 Fly.io 管理的獨立 PostgreSQL 資料庫服務。
  • Aiven Redis: 作為外部第三方服務,同時肩負兩種職責:
    • 訊息代理 (Broker): 供 Dramatiq 使用 (db0),並啟用 Result Backend 以支援任務狀態查詢。
    • 應用層快取 (Cache): 供 Web 服務使用 (db1),實現操作隔離。
graph TD
    subgraph "使用者/觸發器"
        Client[外部客戶端 / 前端]
        GHA_Deploy["GHA: CI/CD<br/>(部署成功時觸發)"]
        GHA_Cron["GHA: Cron Job<br/>(每日定時觸發)"]
    end

    subgraph "GitHub Actions 工作流"
        GHA_E2E["e2e_workflow_health_check.yml"]
        GHA_Daily["daily_crawl.yml"]
    end

    subgraph "Fly.io 雲端平台 (Scale-to-Zero)"
        subgraph "Fly App: cpbl-takao-today-be"
            Web[Web Service / FastAPI]
            Worker["Worker Service / Dramatiq<br/>(運行於 Xvfb 環境)"]
        end
        DB[(Fly PostgreSQL)]
    end

    subgraph "第三方服務"
        Redis[(Aiven Redis db0: Broker db1: Cache)]
    end

    subgraph "外部網站"
        CPBL[CPBL 官網]
    end

    Client -- "API 請求" --> Web

    GHA_Deploy -- "觸發" --> GHA_E2E
    GHA_Cron -- "觸發" --> GHA_Daily

    GHA_E2E -- "1. 啟動機器" --> Web
    GHA_E2E -- "1. 啟動機器" --> Worker
    GHA_E2E -- "2. 觸發 E2E 測試 API" --> Web
    GHA_E2E -- "3. 輪詢任務狀態 API" --> Web
    GHA_E2E -- "5. 關閉機器" --> Web
    GHA_E2E -- "5. 關閉機器" --> Worker

    GHA_Daily -- "執行類似 E2E 的流程<br/>但觸發真實爬蟲任務" --> Web

    Web -- "資料庫查詢" --> DB
    Web -- "快取讀寫" --> Redis
    Web -- "發送背景任務" --> Redis
    Redis -- "4. 任務入隊" --> Worker
    Worker -- "執行爬蟲" --> CPBL
    Worker -- "將結果寫入資料庫" --> DB
    Worker -- "清除快取(可選)" --> Web
Loading

維運與自動化 (Operations & Automation)

本專案導入了一系列 GitHub Actions 工作流,以實現高度自動化、強健且具成本效益的維運模式。

自動化排程與成本優化 (Scale-to-Zero)

為了解決傳統排程器依賴服務常駐運行的問題,本專案已將排程的觸發與控制權完全轉移至 GitHub Actions,實現了按需啟停 (Scale-to-Zero) 的架構,大幅降低維運成本。

核心工作流程 (.github/workflows/daily_crawl.yml) 如下:

  1. 啟動: 每日固定時間,workflow 自動執行 fly machines start 指令,啟動 webworker 機器。
  2. 觸發: 機器啟動後,workflow 會呼叫 POST /api/system/trigger-daily-crawl 端點,將每日爬蟲任務送入 Dramatiq 佇列並取得 task_id
  3. 監控: 接著,workflow 會進入輪詢階段,週期性地呼叫 GET /api/system/task-status/{task_id} 端點,主動監控任務的真實執行狀態 (succeeded, failed)。
  4. 關閉: 一旦任務完成(無論成功或失敗),workflow 會立刻執行 fly machines stop 指令關閉所有機器,確保運算資源只在必要時運行。

此架構不僅提升了排程的健壯性,更透過精準的按需控制,將服務的每日運行時間從 24 小時縮減至約 30 分鐘。

部署後 E2E 健康檢查

在每次成功部署到 main 分支後,.github/workflows/e2e_workflow_health_check.yml 工作流會被自動觸發,執行一個與每日排程類似的生命週期,來驗證生產環境的端對端健康度:

  1. 啟動測試機器: 啟動一台 web 和一台 worker
  2. 驗證 API 與背景任務: 透過 API 觸發一個輕量級的 E2E 測試任務 (task_e2e_workflow_test)。
  3. 監控與斷言: 輪詢任務狀態,直到確認其在預期時間內成功完成。
  4. 自動清理: 無論成功或失敗,最後都會關閉所有機器。

此流程確保了每次部署的核心功能(API 接收、任務入隊、Worker 執行、結果回報)都正常運作。

CI/CD 全面遷移至 Poetry

.github/workflows/ci.yml 工作流已全面改用 Poetry 來執行所有步驟(安裝依賴、Linter、測試、安全掃描),取代了原先的 piprequirements.txt。這帶來了以下好處:

  • 單一事實來源: pyproject.tomlpoetry.lock 成為唯一的依賴項定義,確保了 CI 環境與本地開發環境的完全一致。
  • 指令統一: 所有指令都透過 poetry run <command> 執行,更為簡潔清晰。

技術棧 (Tech Stack)

類別 技術
後端框架 FastAPI, Uvicorn
資料庫 PostgreSQL, SQLAlchemy (ORM), Alembic
背景任務 / 快取 Dramatiq, Redis (Aiven)
網頁爬蟲 Playwright, BeautifulSoup4, Requests
容器化 Docker, Docker Compose
雲端平台 Fly.io
依賴項管理 Poetry
CI/CD 與程式碼品質 GitHub Actions, pre-commit, Ruff
測試框架 pytest, pytest-mock, pytest-playwright
負載測試 Locust
設定管理 pydantic-settings
日誌 python-json-logger
虛擬顯示 Xvfb (X virtual framebuffer)
依賴項安全 pip-audit

本地開發環境設定 (Local Development Setup)

請遵循以下步驟在你的本地機器上設定並運行此專案。

步驟一:取得專案程式碼

git clone <YOUR_REPOSITORY_URL>
cd <PROJECT_DIRECTORY>

步驟二:設定環境變數

本專案透過 .env 檔案管理本地開發環境的設定。請從範例檔案複製一份來開始:

cp .env.example .env

接著,請修改 .env 檔案的內容。所有必要的環境變數都定義在 app/config.py 中,並透過 Pydantic 進行驗證。

組態管理原則 (Configuration Management Principles)

本專案的組態管理遵循職責分離原則:

  • 敏感資訊 (Secrets): 如 DATABASE_URL, API_KEY 等。這類資訊絕不能提交至版本控制。

    • 生產環境: 由 Fly.io 的 Secrets 功能管理 (fly secrets set)。
    • CI/CD 流程: 由 GitHub Actions 的 Secrets 提供。
    • 本地開發: 存放於 .env 檔案中 (此檔案已被 .gitignore 排除)。
  • 非敏感組態 (Configuration): 如 TARGET_TEAMS 等應用層面的設定。這類資訊應受版本控制。

    • 生產環境: 定義於 fly.toml[env] 區塊中。
    • 本地開發: 同樣存放於 .env 檔案中,方便覆寫與測試。

重要: fly.tomlTARGET_TEAMSTARGET_PLAYERS 這類列表型別的變數,必須使用標準的 JSON 陣列字串格式(且內部引號需轉義),以確保 Pydantic 能正確解析。

步驟三:啟動並初始化服務

本專案使用 Docker Compose 管理所有服務,並透過 Makefile 簡化了常用指令。

  1. 啟動所有服務容器:

    # 首次啟動或 Dockerfile/docker-compose.yml 變更後,使用 --build
    docker compose up -d --build
  2. 初始化資料庫 (僅首次設定必要): 此指令會執行 Alembic,建立所有必要的資料表。

    make migrate
  3. 首次初始化賽程: 此指令會觸發背景任務,抓取整年度的賽程。

    make bulk-update-schedule

API 端點 (API Endpoints)

本專案採用 FastAPI 框架,它會自動生成互動式的 API 文件。這份文件是查詢所有可用端點、參數及請求範例最準確的來源。

當本地開發環境啟動後,請直接在瀏覽器中開啟以下任一網址:

/docs 頁面中,你可以直接在線上測試每一個 API 端點,並查看其請求與回應的詳細結構。

常用工具與指令 (Makefile)

本專案提供一個 Makefile 來簡化所有常見的開發與維護任務。開發者應優先使用 make 指令,而非直接執行冗長的 docker composepython 指令。

批次匯入 (Bulk Import)

  • 爬取指定日期範圍的比賽資料:

    # 爬取今天的比賽資料
    make bulk-scrape
    
    # 爬取從 2025-05-01 到今天的資料
    make bulk-scrape start=2025-05-01
    
    # 爬取 2025-03-01 到 2025-04-30 的資料
    make bulk-scrape start=2025-03-01 end=2025-04-30
  • 爬取所有球員的生涯數據:

    make bulk-scrape-career
  • 更新賽程:

    make bulk-update-schedule
  • 上傳暫存資料至生產資料庫:

    make bulk-upload

資料維護與其他工具 (Data Maintenance & Other Tools)

  • 回填指定日期範圍的球員歷史數據:

    # 為 2024-05-01 到 2024-05-10 的每一個有比賽日建立歷史數據快照
    make backfill-history start=2024-05-01 end=2024-05-10
    
    # 為 2024 整年度的每一個有比賽日建立歷史數據快照
    make backfill-history start=2024-01-01
  • 重設本地開發資料庫:

    # 警告:此操作會刪除所有資料
    make reset-db
    
    # 只重設指定的資料表 (以逗號分隔)
    make reset-db tables=player_season_stats_history,game_results
  • 執行壓力測試:

    # 注意:此指令需要在你的本機 (非 Docker) 安裝 Locust
    make load-test
  • 建立金絲雀測試樣本資料:

    make create-canary
  • 重新產生此份 README 文件:

    make gen-readme

資料庫遷移 (Database Migrations)

本專案使用 Alembic 來管理資料庫結構的變更。

  • 當你修改 app/models.py 中的模型後,你需要產生一個新的遷移腳本:

    # 在 worker 容器中自動產生遷移腳本
    docker compose run --rm worker alembic revision --autogenerate -m "描述你的變更"
  • 將變更應用到資料庫:

    # 在 worker 容器中執行 upgrade
    make migrate

程式碼品質與 CI/CD

  • pre-commit: 本專案使用 pre-commit 在每次 git commit 時自動執行 Ruff (linter + formatter),以確保所有提交的程式碼都符合一致的風格與品質標準。初次設定請運行 pre-commit install

  • GitHub Actions: 每次推送到 main 分支時,會觸發 CI/CD 工作流程,包含:

    1. 程式碼品質與格式檢查。

    2. 執行完整的 pytest 測試套件(排除 e2e 與 canary 測試)。

    3. 若測試通過,自動部署應用至 Fly.io。

執行特定測試

本專案使用 Pytest Marker 來分類測試 (e2e, canary)。你可以使用 -m 參數來篩選要執行或跳過的測試。

  • 僅執行單元測試 (CI 的主要流程):

    pytest -m "not e2e and not canary"
  • 僅執行 e2e 測試:

    pytest -m e2e
  • 僅執行金絲雀測試:

    pytest -m canary

部署 (Deployment)

自動化部署

本專案已設定 CI/CD,任何推送到 main 分支且通過所有測試的提交,都會被自動部署到 Fly.io。這是標準的部署流程。

備用路徑:手動部署 (用於緊急修復或特殊情況)

在 CI/CD 流程無法使用或需要緊急部署時,可以透過 flyctl CLI 工具手動部署。

  1. 設定秘密 (Secrets): 在首次部署或 Secret 變更時,你仍需手動設定生產環境的敏感資訊。這些資訊絕不應存放在版本控制中。

    fly secrets set \
      DATABASE_URL="<YOUR_FLY_POSTGRES_URL>" \
      DRAMATIQ_BROKER_URL="<YOUR_AIVEN_REDIS_URL>" \
      API_KEY="<YOUR_PRODUCTION_API_KEY>" \
  2. 部署應用: 設定完 secrets 後,在專案根目錄執行以下指令即可觸發部署。

    fly deploy

    flyctl 會讀取 fly.toml 檔案,在本機建置 Docker 映像檔,並將其推送到 Fly.io 平台,更新 webworker 服務。

本地日誌查看技巧 (Local Log Viewing)

由於主控台日誌已改為結構化的 JSON 格式,建議搭配使用命令列工具 jq 來美化、上色及過濾日誌。

前置需求: 請先確保你的系統已安裝 jq (例如在 Ubuntu/WSL 中執行 sudo apt-get install jq)。

美化輸出:

# 查看 web 服務的美化日誌
docker compose logs web | jq

# 持續追蹤 worker 服務的美化日誌
docker compose logs -f worker | jq

依 request_id 過濾: 當你需要追蹤單一 API 請求的完整生命週期時,可利用我們新增的 request_id 欄位進行過濾。

# 只顯示特定 request_id 的日誌記錄
docker compose logs web | jq 'select(.request_id == "YOUR_REQUEST_ID_HERE")'

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages