429 rate_limit_exceeded 表示要等待 Retry-After 標頭指定的時間後再重試;402 insufficient_credits 表示餘額已用完,需要儲值。一個小快取可以讓重複請求不產生費用。快取的過期時間應與資料的更新頻率相符。
使用 API
- 準備金鑰 取得免費 API 金鑰,並儲存在
NARWHAL_API_KEY環境變數中。Python 腳本會讀取該變數,並把金鑰以Authorization: Bearer YOUR_KEY的形式傳送到https://api.narwhalapi.com。 - 為每種資料類型選擇過期時間 假日日曆可以快取幾天到幾週,經濟資料發布快取到下一次排定發布,殖利率曲線快取一天。每日外匯匯率可以快取到下一個 UTC 日;選擇存活時間(TTL)時請使用這些界線。
- 使用快取與重試輔助函式 快取鍵由路徑和排序後的參數組合而成,所以參數不同的請求會各有獨立的快取項目。腳本用相同的路徑和 TTL 兩次讀取黃金價格;只要該項目仍在 TTL 內,第二次讀取就使用快取的回應。
import os, random, time import requests API = "https://api.narwhalapi.com" session = requests.Session() session.headers["Authorization"] = f"Bearer {os.environ['NARWHAL_API_KEY']}" _cache: dict[str, tuple[float, dict]] = {} def get(path: str, ttl: int = 300, **params): """GET with a small in-memory cache, Retry-After on 429s and backoff on 5xx.""" key = path + repr(sorted(params.items())) hit = _cache.get(key) if hit and time.time() - hit[0] < ttl: return hit[1] for attempt in range(4): r = session.get(API + path, params=params, timeout=10) if r.status_code == 402: raise RuntimeError("Out of credit; add credit in the dashboard.") if r.status_code == 429: time.sleep( float(r.headers.get("Retry-After", 2)) ) # short-window limit: wait, then retry continue if r.status_code in (500, 503): time.sleep( 2**attempt + random.random() ) # bounded exponential backoff with jitter continue r.raise_for_status() _cache[key] = (time.time(), r.json()) return _cache[key][1] raise RuntimeError(f"Gave up on {path}") print(get("/v1/commodities/spot/XAU/USD", ttl=60)["price"]) print(get("/v1/commodities/spot/XAU/USD", ttl=60)["price"], "(from cache)") - 讀取回應標頭 成功的資料回應包含
X-Request-ID、每分鐘的X-RateLimit-Limit、X-RateLimit-Remaining和X-RateLimit-Reset,以及與額度相關的X-Narwhal-Cost和X-Narwhal-Balance。
使用免費額度的帳戶每分鐘最多可傳送 50 次請求,使用付費額度的帳戶為 500 次。
輔助函式只在請求成功後才儲存回應。未過期的項目會在發起 HTTP 呼叫之前直接回傳,已過期的項目則會再次進入請求迴圈。它的快取位於記憶體中,所以當你的 Web 服務需要共用快取時,請使用共用快取方案。
預設 TTL 為 300 秒;黃金價格的呼叫用 ttl=60 覆寫了它。每個請求的逾時為 10 秒,迴圈最多嘗試 4 次。對於短時間視窗的限制,輔助函式會把 Retry-After 讀作以秒為單位的延遲,並在下一次嘗試前等待。如果沒有該標頭,則使用 2 秒。嘗試次數用完後,輔助函式會拋出帶有所請求路徑的錯誤。其他失敗狀態會進入 raise_for_status(),並且不會向快取新增回應。
不寫程式碼處理重試
在 Zapier、Make 或 n8n 中,新增一個短暫的延遲步驟,或啟用平台的出錯重試功能。這些平台本來就會顯示 HTTP 狀態。遇到 429 時,先讀取問題回應本文中的 code,再決定是延遲後重試還是停止。
依所用資料的特性,以合適的間隔安排請求。同樣的快取時機也適用於自動化流程:假日日曆可以隔幾天到幾週再讀取,經濟資料發布可以快取到下一次排定發布。如果請求集中爆發,請把它們錯開,使其保持在每分鐘限制之內。
各種情況適合的方式
| 情況 | 最適合的方式 | 原因 |
|---|---|---|
| 腳本每天執行一次 | 無需快取 | 腳本本身已將讀取間隔了一天 |
| Web 服務重複發出請求 | 記憶體快取或共用快取 | 依資料類型設定 TTL,可使重複讀取不產生費用 |
| 大量請求集中爆發 | 把請求錯開 | 每分鐘限制仍然適用 |
TTL 決定一個回應可以被重複使用多久,請求間隔則控制呼叫何時送達 API。Web 服務可能兩者都需要:快取應對相同路徑和參數的重複讀取,間隔控制則處理仍會送達 API 的呼叫。即使使用一小時的快取,路徑或參數不同的請求依然是各自獨立的呼叫。判斷一陣請求是否需要錯開時,即使其他讀取使用了快取的回應,也要把這些呼叫計算在內。
429 或 402 會計費嗎?
不會,錯誤從不計費,無論是 429 速率限制,還是餘額為空的 402。錯誤回應本文使用 application/problem+json,並包含 status、code、detail 和 request_id。code 標明了該回應的原因。
餘額用完後,請在主控台中儲值。遇到 402 時,輔助函式會拋出錯誤而不是繼續重試迴圈,因為短暫等待並不能增加額度。
共用同一位址的用戶端會被一起限流嗎?
會,共用同一位址的用戶端可能被一起限流:另有一個防濫用的限流桶,對同一個網路識別每分鐘允許 120 次請求,突發上限為 20。請求集中爆發時,帳戶限制和這個共用限流桶都需要考慮。
還有哪些錯誤應該重試?
對冪等請求,在 500 或 503 之後用有上限的指數退避加抖動進行重試。輔助函式會逐次增加嘗試之間的延遲並加入隨機成分;在有上限的請求迴圈結束後如果仍沒有成功的回應,就會拋出錯誤。
不要重試 400,也不要用同一把金鑰重試 401。404 series_not_found 可能在某次發布之後變得可用,所以要把這種情況與退避迴圈處理的伺服器錯誤分開。
計算範例:儀表板的成本
按每 1,000 次呼叫 $2 計算,$10 的免費額度可涵蓋每月 5,000 次呼叫,約合每天 166 次。先看一個每 15 分鐘讀取 6 個值的儀表板。這樣的重新整理排程使每個值每天讀取 96 次,所以用 6 乘以 96,得到每天 576 次請求。
然後把每個值快取一小時。這一小時內的儀表板重新整理會重複使用已儲存的回應,API 請求減少到每天 144 次。如果儀表板的花費超出你的預期,也可以選擇放慢重新整理頻率。
| 計算 | 每日請求數 |
|---|---|
| $10 免費額度按每 1,000 次呼叫 $2 折算為每日平均值 | 約 166 |
| 6 個值 × 15 分鐘重新整理時的 96 次讀取 | 576 |
| 同樣的 6 個值,各快取一小時 | 144 |
發布於 。