這篇教學會帶你建立一個完整的 NBA 數據分析系統,包含兩個核心程式:
- 數據抓取器 (fetch_games.py) - 從 API 抓取比賽數據
- 戰績生成器 (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。它的特點:
- 免費但有速率限制
- 需要 API Key
- 資料結構清楚且穩定
核心功能解析
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
這段很聰明!它會:
- 只保留已開打的比賽(分數大於 0)
- 一旦碰到整頁都是未開打的比賽,就停止抓取
- 節省時間和 API 配額
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} 開始搜尋新比賽")
這表示:
- 第一次執行:抓整個賽季
- 之後執行:只抓新的比賽
- 超級省時間和 API 配額!
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跟不斷地抱怨而已🏀





















