快取回應並處理 429 錯誤

429 rate_limit_exceeded 表示要等待 Retry-After 標頭指定的時間後再重試;402 insufficient_credits 表示餘額已用完,需要儲值。一個小快取可以讓重複請求不產生費用。快取的過期時間應與資料的更新頻率相符。

使用 API

  1. 準備金鑰 取得免費 API 金鑰,並儲存在 NARWHAL_API_KEY 環境變數中。Python 腳本會讀取該變數,並把金鑰以 Authorization: Bearer YOUR_KEY 的形式傳送到 https://api.narwhalapi.com。
  2. 為每種資料類型選擇過期時間 假日日曆可以快取幾天到幾週,經濟資料發布快取到下一次排定發布,殖利率曲線快取一天。每日外匯匯率可以快取到下一個 UTC 日;選擇存活時間(TTL)時請使用這些界線。
  3. 使用快取與重試輔助函式 快取鍵由路徑和排序後的參數組合而成,所以參數不同的請求會各有獨立的快取項目。腳本用相同的路徑和 TTL 兩次讀取黃金價格;只要該項目仍在 TTL 內,第二次讀取就使用快取的回應。
    Python:快取加 Retry-After 處理
    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)")
  4. 讀取回應標頭 成功的資料回應包含 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

發布於 。

後續步驟