這篇教學會帶你建立一個完整的 NBA 數據分析系統,包含兩個核心程式:

  1. 數據抓取器 (fetch_games.py) - 從 API 抓取比賽數據
  2. 戰績生成器 (standings_generator.py) - 計算並顯示戰績表

整體架構

API (balldontlie.io)  
      ↓  
fetch_games.py → games_2024.json  
      ↓  
standings_generator.py → 戰績表 + CSV

Part 1: 數據抓取器解析

使用的 API:balldontlie.io

你的程式用的是 balldontlie.io 這個第三方 NBA API。它的特點:

核心功能解析

1. API 請求設定

API_KEY = "3****1-****-****-****-d4****33"  
BASE_URL = "https://api.balldontlie.io/v1/games"  
  
headers = {"Authorization": API_KEY}  
params = {  
    "seasons[]": season_year,  # 例如 2024  
    "per_page": 100,           # 每次抓 100 筆  
    "start_date": "2024-10-22" # 可選:從特定日期開始  
}

2. 分頁抓取機制(重要!)

cursor = None  # 用來追蹤目前位置  
  
while True:  
    if cursor:  
        params["cursor"] = cursor  # 下一頁的游標  
      
    response = requests.get(BASE_URL, headers=headers, params=params)  
    data = response.json()  
      
    # 取得下一頁的游標  
    cursor = data.get("meta", {}).get("next_cursor")  
    if not cursor:  
        break  # 沒有下一頁了  
      
    time.sleep(12)  # 避免觸發速率限制

為什麼用 cursor 而不是 page number?

Cursor-based pagination 更穩定,即使在抓取過程中有新資料進來,也不會漏掉或重複。

3. 聰明的過濾邏輯

played_games_on_page = [  
    game for game in page_games  
    if game.get("home_team_score", 0) > 0 or game.get("visitor_team_score", 0) > 0  
]  
  
if page_games and not played_games_on_page:  
    print("已到達未開打的賽程,停止抓取。")  
    break

這段很聰明!它會:

4. 增量更新功能(超實用!)

if output_path.exists():  
    # 讀取現有資料  
    existing_games = data.get("games", [])  
      
    # 找出最後一場比賽的日期  
    latest_date = max(datetime.fromisoformat(g["date"]) for g in existing_games)  
      
    # 只抓新的比賽  
    start_date_for_fetch = (latest_date.date() + timedelta(days=1)).strftime('%Y-%m-%d')  
    print(f"將從 {start_date_for_fetch} 開始搜尋新比賽")

這表示:

5. 去重與排序

# 合併舊資料和新資料  
all_games = existing_games + newly_fetched_games  
  
# 用 dict 去重(以 game ID 為 key)  
unique_games = {game['id']: game for game in all_games}  
  
# 按日期排序  
sorted_games = sorted(unique_games.values(), key=lambda g: g['date'])

Part 2: API 回傳的 JSON 格式

balldontlie.io 回傳的資料長這樣:

{  
  "data": [  
    {  
      "id": 1234567,  
      "date": "2024-10-22T23:00:00.000Z",  
      "season": 2024,  
      "status": "Final",  
      "period": 4,  
      "time": "Final",  
      "postseason": false,  
      "home_team": {  
        "id": 2,  
        "conference": "East",  
        "division": "Atlantic",  
        "city": "Boston",  
        "name": "Celtics",  
        "full_name": "Boston Celtics",  
        "abbreviation": "BOS"  
      },  
      "visitor_team": {  
        "id": 20,  
        "conference": "East",  
        "division": "Atlantic",  
        "city": "New York",  
        "name": "Knicks",  
        "full_name": "New York Knicks",  
        "abbreviation": "NYK"  
      },  
      "home_team_score": 108,  
      "visitor_team_score": 104  
    }  
  ],  
  "meta": {  
    "next_cursor": "eyJpZCI6MTIzNDU2Nywic2Vhc29uIjoyMDI0fQ==",  
    "per_page": 100  
  }  
}

Part 3: 完整使用流程

步驟 1: 抓取賽季數據

# 抓取 2024-25 賽季(會自動判斷當前賽季)  
python fetch_games.py  
  
# 指定賽季  
python fetch_games.py --season 2023  
  
# 指定輸出檔案  
python fetch_games.py --season 2024 --output my_games.json

第一次執行

開始抓取 2024-2025 賽季的比賽資料...  
正在抓取第 1 頁...  
已抓取 87 場新的已開打比賽。  
等待 12 秒以避免觸發頻率限制...  
正在抓取第 2 頁...  
已抓取 187 場新的已開打比賽。  
...  
資料庫中總共有 456 場 2024-2025 賽季比賽。  
已將最新的比賽資料寫入 games_2024.json

之後執行(增量更新):

找到現有檔案 games_2024.json,將從上次進度開始更新...  
上次抓取到 2024-11-15,將從 2024-11-16 開始搜尋新比賽。  
開始抓取 2024-2025 賽季的比賽資料...  
搜尋開始日期: 2024-11-16  
正在抓取第 1 頁...  
已抓取 12 場新的已開打比賽。  
資料庫中總共有 468 場 2024-2025 賽季比賽。

步驟 2: 生成戰績表

# 使用剛抓的資料  
python standings_generator.py games_2024.json  
  
# 同時匯出 CSV  
python standings_generator.py games_2024.json --csv standings.csv

輸出:

目標賽季: 2024  
  
正在讀取檔案: games_2024.json...  
找到 468 場比賽,過濾後剩下 468 場 2024 賽季的有效例行賽。  
已初始化 30 支球隊的統計資料。  
已完成所有比賽的數據統計。  
  
EASTERN CONFERENCE  
   TEAM              W   L   PCT    GB   CONF    DIV    HOME   ROAD   L10  STR     PF     PA   DIFF  
1  Cleveland Cava   15   0  1.000   —    10-0   5-0    8-0    7-0    10-0  W15  119.7  109.2   10.5  
2  Boston Celtics   12   3  .800   3.0   9-2    4-1    7-1    5-2    7-3   W2   120.4  110.8    9.6  
...

Part 4: 自動化腳本

寫一個 bash 腳本 update_standings.sh

#!/bin/bash  
  
echo "=== NBA 戰績表自動更新 ==="  
echo ""  
  
# 步驟 1: 更新比賽數據  
echo "步驟 1: 從 API 抓取最新比賽..."  
python fetch_games.py  
  
# 檢查是否成功  
if [ $? -ne 0 ]; then  
    echo "❌ 數據抓取失敗"  
    exit 1  
fi  
  
echo ""  
echo "✓ 數據抓取完成"  
echo ""  
  
# 步驟 2: 生成戰績表  
echo "步驟 2: 生成戰績表..."  
python standings_generator.py --csv standings.csv  
  
if [ $? -ne 0 ]; then  
    echo "❌ 戰績表生成失敗"  
    exit 1  
fi  
  
echo ""  
echo "✓ 戰績表生成完成"  
echo ""  
echo "=== 更新完成! ==="  
echo "- 比賽數據: games_2024.json"  
echo "- 戰績表: standings.csv"

使用方法:

chmod +x update_standings.sh  
./update_standings.sh

Part 5: 進階技巧

技巧 1: 定時自動更新

cron 設定每天自動更新:

# 編輯 crontab  
crontab -e  
  
# 加入這行(每天早上 8 點執行)  
0 8 * * * cd /path/to/project && ./update_standings.sh

技巧 2: 錯誤處理與通知

def fetch_with_retry(url, headers, params, max_retries=3):  
    """帶重試機制的請求"""  
    for attempt in range(max_retries):  
        try:  
            response = requests.get(url, headers=headers, params=params, timeout=10)  
            response.raise_for_status()  
            return response.json()  
        except requests.exceptions.RequestException as e:  
            if attempt == max_retries - 1:  
                raise  
            print(f"請求失敗,{3-attempt} 秒後重試... ({e})")  
            time.sleep(3)

技巧 3: 速率限制監控

def check_rate_limit(response):  
    """檢查 API 配額"""  
    remaining = response.headers.get('X-RateLimit-Remaining')  
    if remaining and int(remaining) < 10:  
        print(f"⚠️  API 配額剩餘: {remaining}")

技巧 4: 數據驗證

def validate_game_data(game):  
    """驗證比賽資料完整性"""  
    required_fields = ['id', 'date', 'home_team', 'visitor_team']  
    for field in required_fields:  
        if field not in game:  
            return False, f"缺少欄位: {field}"  
      
    if game.get('home_team_score', 0) == 0 and game.get('visitor_team_score', 0) == 0:  
        return False, "比賽尚未開打"  
      
    return True, "OK"

Part 6: 常見問題排解

Q1: API Key 失效怎麼辦?

balldontlie.io 註冊新的 API Key,然後替換程式碼中的:

API_KEY = "你的新 API Key"

Q2: 速率限制問題

balldontlie.io 免費版限制:每分鐘 30 次請求

如果太頻繁會收到 HTTP 429 錯誤。你的程式已經處理了(每次等 12 秒),但如果還是有問題:

# 增加等待時間  
time.sleep(15)  # 改成 15 秒

Q3: JSON 檔案損壞

如果 games_2024.json 損壞,程式會自動重新抓取:

except (json.JSONDecodeError, KeyError) as e:  
    print(f"讀取或解析現有檔案時出錯: {e}。將重新抓取整個賽季。")  
    existing_games = []

Q4: 比賽數據不一致

有時候 API 會回傳重複的比賽,你的程式已經處理了:

# 用 game ID 去重  
unique_games = {game['id']: game for game in all_games}

Part 7: 完整專案結構

nba-standings/  
├── fetch_games.py          # API 抓取器  
├── standings_generator.py  # 戰績表生成器  
├── update_standings.sh     # 自動化腳本  
├── games_2024.json        # 比賽數據(自動生成)  
├── standings.csv          # 戰績表(自動生成)  
├── requirements.txt       # 相依套件  
└── README.md             # 說明文件

requirements.txt:

requests>=2.31.0

總結

這個系統的設計很優秀:

增量更新 - 不會重複抓取已有的資料

錯誤處理 - API 失敗時保存現有資料

速率限制 - 自動等待避免被封鎖

資料驗證 - 過濾未開打的比賽

去重排序 - 確保資料品質

模組化設計 - 兩個程式各司其職

你可以每天執行一次 update_standings.sh,就能得到最新的 NBA 戰績表!

🏀有任何問題不要問我 ,程式AI生成的,文章也是AI生成的,我只負責叫他串API跟不斷地抱怨而已🏀