Python

API 串接與異步:讓你的 Python 程式連上世界

Python 串接 API 的標準做法是用 requests 送出請求,加上 timeout 與 raise_for_status() 確保錯誤不會被吃掉,再用 .get() 安全取出 JSON 值。批次呼叫多個 API 時,改用 asyncio 搭配 httpx 並行處理,速度會從等待總和變成只等最慢的那一個。
API 串接與異步:讓你的 Python 程式連上世界

API 串接與異步:讓你的 Python 程式連上世界

OpenAI、Claude、Google Maps、LINE,你用的每一個 AI 服務,背後都是一次一次的 API 呼叫。這一課帶你看懂 AI 寫的 API 串接程式在做什麼:怎麼用 requests 送出請求、怎麼安全地取出 JSON 裡的值、怎麼用 asyncio 把十次呼叫的等待時間從十秒壓到兩秒,以及最容易被忽略但後果最嚴重的一件事:API 金鑰不能寫死在程式碼裡。

看完這一課,你會有一句可以隨時拿出來檢查 AI 寫的 API 程式的口訣:這是 GET 還是 POST?API Key 放在哪裡?回傳的 JSON 結構是什麼?有沒有處理 HTTP 錯誤?

你將學到什麼

requests 的標準寫法

GET 取資料、POST 送資料,headers 放金鑰、params 放查詢條件。

JSON 回應怎麼安全取值

用 .get() 代替 [] 取值,加上完整的例外處理,防止程式無預警崩潰。

asyncio 讓批次呼叫變快

同步等十秒,非同步只等最慢的那個,httpx + asyncio.gather 的實際寫法。

API 金鑰不能寫死

用 .env 檔案存放金鑰,看懂 AI 寫的程式裡哪裡藏著洩漏風險。

7-1 requests:HTTP 請求的標準寫法

requests 是 Python 最常用的第三方 HTTP 套件(pip install requests),AI 寫的 API 串接程式幾乎全用它。GET 用來取資料,POST 用來送資料,金鑰統一放在 headers 裡,而不是寫進網址或程式碼。

import requests

# GET 請求(取資料)
response = requests.get(
    "https://api.example.com/users",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    params={"page": 1, "limit": 20}
)

# 確認狀態碼再取值
response.raise_for_status()    # 非 2xx 自動拋出例外
data = response.json()         # 回傳 dict 或 list
print(data)

# POST 請求(送資料)
response = requests.post(
    "https://api.example.com/users",
    headers={
        "Authorization": "Bearer YOUR_TOKEN",
        "Content-Type": "application/json"
    },
    json={"name": "Alice", "email": "alice@example.com"}
)
response.raise_for_status()
print(response.status_code)    # 201 Created
Vibe Coder 觀察重點.raise_for_status() 是防護必加的一行。沒有它,API 回 404 或 500 時程式不會報錯,會悄悄拿著錯誤資料繼續跑下去。看到 AI 寫的 API 程式,第一件事就是確認有沒有這一行。

7-2 JSON 回應處理與錯誤管理

API 幾乎都回傳 JSON。你要能逐層取出需要的值,也要能處理回傳格式不如預期的情況,兩件事同樣重要。

# 假設 API 回傳:
# {"status": "ok", "data": {"users": [{"id": 1, "name": "Alice"}]}}

response = requests.get("https://api.example.com/users")
response.raise_for_status()
result = response.json()

# 逐層取值(用 .get() 防 KeyError)
status = result.get("status")
users  = result.get("data", {}).get("users", [])
for user in users:
    print(user.get("name", "未知"))

遇到超時、連線失敗、格式不對這些情況,要用完整的例外處理逐一接住,而不是讓整個程式崩潰。

try:
    response = requests.get(url, timeout=10)  # 設超時!
    response.raise_for_status()
    data = response.json()
except requests.exceptions.Timeout:
    print("請求超時")
except requests.exceptions.HTTPError as e:
    print(f"HTTP 錯誤:{e.response.status_code}")
except requests.exceptions.ConnectionError:
    print("網路連線失敗")
except ValueError:
    print("回傳格式不是 JSON")
一定要設 timeout沒有 timeout,API 不回應時程式會永遠等待。生產環境最少加 timeout=10(秒)。這是 AI 初版程式最常忘記的一行。

7-3 asyncio 非同步:批次 API 呼叫

同步呼叫十個 API,要等十次一秒,總共十秒;非同步並行呼叫,只要等最慢的那一個,通常不到兩秒。批次處理 AI API 呼叫,asyncio 是必學技能。

同步:一個接一個,等待時間累加API 1API 2API 3……API 1010 次依序執行,總共等待 10 秒非同步:同時送出,只等最慢的那個API 1API 2API 3(最慢)……同時送出10 次同時送出,只等最慢的一個,總共約 2 秒
同步呼叫依序等待每一個 API;非同步呼叫同時送出,總時間只取決於最慢的那一個。
import asyncio
import httpx   # pip install httpx(async 版的 requests)

async def fetch_user(client, user_id):
    """非同步取得單一用戶資料。"""
    response = await client.get(
        f"https://api.example.com/users/{user_id}"
    )
    response.raise_for_status()
    return response.json()

async def fetch_all(user_ids):
    """並行取得所有用戶。"""
    async with httpx.AsyncClient(timeout=10) as client:
        tasks = [fetch_user(client, uid) for uid in user_ids]
        results = await asyncio.gather(*tasks, return_exceptions=True)
    return results

# 執行
ids = [1, 2, 3, 4, 5]
results = asyncio.run(fetch_all(ids))
for r in results:
    if isinstance(r, Exception):
        print(f"錯誤:{r}")
    else:
        print(r.get("name"))
Vibe Coder 觀察重點return_exceptions=Truegather 不會因為一個 API 失敗就中斷全部。失敗的呼叫會變成一個 Exception 物件,你可以用 isinstance(r, Exception) 判斷哪些成功、哪些失敗。

7-4 API 金鑰安全管理與常見陷阱

危險:API Key 寫死在程式裡api_key = "sk-abc123realkey" 一旦這行程式碼上傳到 GitHub,金鑰就等於公開了。
正確:用 .env 檔案存金鑰把金鑰寫進 .env 檔(加進 .gitignore,不上傳),程式用 python-dotenv 讀取:load_dotenv() 之後 os.getenv("OPENAI_API_KEY") 取值,金鑰本身永遠不出現在程式碼裡。
症狀原因修正
API 不回應,程式卡住沒有設 timeout加 timeout=10
404 但程式不報錯沒有 raise_for_status每次請求後必加
KeyError 取 JSON 值直接用 [] 取值改用 .get(key, default)
API Key 在 log 裡出現直接 print 了 headerslog 前遮蔽敏感欄位

三道遞進題:讀懂、改寫、抓錯

讀懂:追蹤 API 呼叫的完整流程。改寫:把同步程式改成非同步、把金鑰移到 .env。抓錯:找出 timeout、raise_for_status、直接用 [] 取 JSON 這三類常見問題。

題目一:讀懂(基礎)

逐行說明以下程式的執行流程。

import requests, os
from dotenv import load_dotenv

load_dotenv()
API_KEY = os.getenv("WEATHER_API_KEY")

def get_weather(city):
    try:
        r = requests.get(
            "https://api.weather.com/v1/current",
            headers={"X-API-Key": API_KEY},
            params={"city": city},
            timeout=8
        )
        r.raise_for_status()
        data = r.json()
        return data.get("temperature")
    except requests.exceptions.RequestException as e:
        print(f"API 錯誤:{e}")
        return None

任務:說明 load_dotenvraise_for_statustimeout=8 各自的用途,以及 API 回傳 500 時會發生什麼。

答案拆解load_dotenv() 讀取 .env 檔,把環境變數載入;os.getenv() 再取出指定 key 的值,金鑰不寫在程式碼裡。
timeout=8 設定八秒超時;raise_for_status() 讓非 2xx 狀態碼自動拋出 HTTPError。
③ API 回 500 時,raise_for_status 拋出 HTTPError,except 捕捉,印出錯誤訊息,回傳 None。

題目二:改寫(進階)

AI 給的初版 API 呼叫幾乎都是同步的,適合單次呼叫。但實際場景常需要批次呼叫,例如處理一百個用戶、翻譯五十條文字,這時候同步會慢五十到一百倍。懂得請 AI 改成非同步,是效能優化的關鍵技能。

請 AI 把以下同步程式改成非同步版本。

import requests

def translate(text, target_lang):
    r = requests.post(
        "https://api.translate.com/v1",
        json={"text": text, "target": target_lang}
    )
    return r.json()["translated"]

texts = ["Hello", "World", "Python"]
results = [translate(t, "zh") for t in texts]
給 AI 的提示詞請用 httpx + asyncio 把 translate 函式改成非同步,並行翻譯所有文字。加上 timeout=10、raise_for_status、用 .get() 取 JSON 值、return_exceptions=True,每行加中文註解。
答案拆解① 定義改成 async def translate(client, text) 這樣的非同步函式,內部呼叫改用 await client.post() 送出,並在函式內加 await。
asyncio.gather(*tasks) 並行執行所有翻譯,速度從三秒降到約一秒。
r.json()["translated"] 改用 .get() 語法取值,key 不存在時回傳 None,而不是直接崩潰。

題目三:抓錯(高階)

API 程式的錯誤最難除錯:不是程式碼寫錯,而是網路、金鑰、格式三層問題同時交錯,你要能逐層排查。

找出以下程式碼的所有問題。

import requests

API_KEY = "sk-abc123secret"   # 問題一

def get_data(endpoint):
    r = requests.get(           # 問題二
        f"https://api.example.com/{endpoint}",
        headers={"Authorization": API_KEY}
    )
    data = r.json()             # 問題三
    return data["result"]       # 問題四

任務:找出四個問題,說明危險原因。

答案拆解問題一:API Key 硬寫在程式碼裡,上傳 GitHub 就洩漏,應改用 os.getenv("API_KEY") 從 .env 讀取。
問題二:沒有設 timeout,API 不回應時程式永遠等待,加 timeout=10
問題三:沒有呼叫 r.raise_for_status() 檢查,API 回 404 或 500 時不報錯,直接對錯誤回應呼叫 .json() 可能崩潰。
問題四:data["result"] 直接用 [] 取值,key 不存在時拋 KeyError,改用 data.get("result") 取值。

重點整理與完成清單

步驟程式碼說明
發送請求r = requests.get(url, headers={}, timeout=10)GET 或 POST
確認狀態r.raise_for_status() 非 2xx 拋例外
取 JSONdata = r.json()dict 或 list
取值data.get("key", default) 防 KeyError
Vibe Coder API 四問GET 還是 POST?API Key 在 .env 嗎?有沒有 timeout 和 raise_for_status?取 JSON 值用 .get() 嗎?

模組七完成清單

能用 requests 發送 GET 和 POST 請求,帶 headers 和 timeout。
每次請求後加 raise_for_status 檢查,取 JSON 值用 .get 語法。
能用 .env 加 python-dotenv 安全管理 API Key。
了解 asyncio 加 httpx 的基本用法和適用情境。
完成作業題目一:說明 load_dotenv、raise_for_status、timeout 的作用。
完成作業題目二:請 AI 把同步 API 改成 asyncio 並行版本。
完成作業題目三:找出硬寫 Key、沒有 timeout、沒有 raise_for_status、直接 [] 四個問題。

延伸學習

把這篇文章分享給需要的人FacebookLINEThreadsX

常見問答

requests 和 httpx 差在哪裡,兩個都要學嗎?
requests 是同步的標準 HTTP 套件,語法簡單,單次呼叫用它就好;httpx 語法幾乎一樣,但支援 async/await,批次並行呼叫要靠它。兩個常常一起出現在同一個專案裡,各司其職。
為什麼一定要加 timeout,不加會怎樣?
沒有設定 timeout,當 API 伺服器不回應時,程式會卡在那一行永遠等下去,使用者只看到畫面沒反應。生產環境的請求最少要加 timeout=10(秒),這是 AI 初版程式最常漏掉的一行。
.get() 取值和直接用中括號 [] 取值有什麼不一樣?
data["key"] 在 key 不存在時會直接拋出 KeyError,讓程式當掉;data.get("key", 預設值) 在 key 不存在時回傳你指定的預設值,程式可以繼續往下執行,不會無預警崩潰。
API 金鑰真的不能直接寫在程式碼裡嗎?
不行。程式碼一旦上傳到 GitHub 之類的地方,金鑰就等於公開了,可能被盜用產生費用。正確做法是把金鑰存進 .env 檔案,並把 .env 加進 .gitignore,程式再用 python-dotenv 讀取,金鑰本身永遠不會出現在程式碼或版本紀錄裡。
asyncio.gather() 裡的 return_exceptions=True 是做什麼用的?
批次並行呼叫多個 API 時,只要有一個失敗,預設整批呼叫就會中斷。加上 return_exceptions=True 之後,失敗的那個會變成一個 Exception 物件被放進結果清單,其他呼叫照常完成,你再用 isinstance(r, Exception) 判斷哪些失敗即可。