iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0
Modern Web

每天一點 API:打造我的生活小工具系列 第 6

Day 06 — 不怕程式崩潰!學會 API 錯誤處理技巧

  • 分享至 

  • xImage
  •  

API 有時候會呼叫失敗?今天的目標是要學會「錯誤處理」,讓程式更安全、更穩定。

為什麼要處理錯誤?

網路並不是永遠順暢,API 呼叫有時會失敗。

常見情況比如:

  • 網路斷線了,沒辦法連上伺服器

  • 伺服器當機或正在維護

  • 回傳的資料格式不是預期的 JSON

如果不處理這些錯誤,程式會直接停止運行,可能讓使用者看到錯誤訊息或是整個程式崩潰。

而你如果有寫錯誤處理程式碼,出錯時可以:

  • 告訴使用者「發生問題請稍後再試」

  • 自動重新嘗試連線或呼叫

  • 記錄錯誤資訊,方便日後追蹤修正

錯誤處理就是程式的「安全網」,能讓程式在遇到問題時不會崩潰,還能提供更好的使用體驗。

常見的錯誤類型有哪些?

連線錯誤(ConnectionError)
這是當網路斷了,或電腦找不到想連結的伺服器時會發生的錯誤。就像電話打不通。

逾時錯誤(Timeout)
伺服器太慢回應,超過你設定的等待時間,程式就會離開不等了。好比打電話等很久沒人接。

伺服器錯誤(HTTPError)
當伺服器回傳錯誤訊息時,例如:

  • 4xx:客戶端錯誤,可能是網址錯或沒權限。

  • 5xx:伺服器錯誤,表示伺服器沒做好工作。

JSON 解析錯誤(ValueError)
當要求伺服器回傳 JSON,但實際收到的不是 JSON 資料(可能是錯誤頁面 HTML 或空白),就會發生這個錯誤。

這些錯誤代表在網路資料傳輸時,難免會遇到狀況,需要在寫程式時做好預防,避免程式因錯誤當機。

try-except 基本寫法(用來處理錯誤)

寫程式時,呼叫網路 API 有可能會出錯(像網路斷線、伺服器沒反應等),這時候用 tryexcept 來包住可能出錯的程式碼,可以防止程式直接當掉,並且告訴你發生了什麼錯誤。

import requests

try:  # try 裡面是我們想做的事情,用來請求網路資源。
    r = requests.get("https://jsonplaceholder.typicode.com/todos/1", timeout=5)  # timeout=5 是告訴程式最多等 5 秒,如果超過會自動丟逾時錯誤。
    r.raise_for_status()  # 檢查伺服器回應狀態碼,發現不是成功(200)會丟出錯誤。
    data = r.json()       # 解析 JSON
    print("成功:", data)

發生錯誤時,立刻跳到對應的 except 裡面,印出錯誤訊息,程式不會當掉。

except requests.exceptions.Timeout:
    print("發生逾時錯誤,伺服器回應太慢了")

except requests.exceptions.ConnectionError:
    print("發生連線錯誤,網路沒連上或伺服器找不到")

except requests.exceptions.HTTPError as e:
    print("發生 HTTP 錯誤,錯誤訊息:", e)

except ValueError:
    print("JSON 格式錯誤,伺服器回傳的不是正確 JSON")

小實作:故意製造網路錯誤來測試

在寫抓取網路資料的程式時,我們可以故意製造錯誤,確保程式能正確處理並回報錯誤,避免程式崩潰。

  1. 連線錯誤(ConnectionError)
    使用一個不存在的網址,會出現找不到伺服器的錯誤。
    範例:請求 http://不存在的網站.abc

  2. 逾時錯誤(Timeout)
    請求一個會延遲回應的網址,但我們設定的等待時間(timeout)很短,會收到超時錯誤。
    範例:請求 https://httpbin.org/delay/5,但 timeout 設成 2 秒。

  3. 伺服器錯誤(HTTPError)
    有時候伺服器會故意回錯誤碼模擬失敗,例如 500(伺服器內部錯誤)。
    範例:請求 https://httpbin.org/status/500

  4. JSON 解析錯誤(ValueError)
    有些網址回傳的內容不是 JSON 格式,例如純文字或 HTML,程式嘗試用 .json() 解析會爆錯。
    範例:請求 https://httpbin.org/html,它回 HTML 頁面。

接下來,我們將以這四種錯誤為範例,練習今天的小實作。

  • 匯入套件
import requests # 用來發送 HTTP 請求
from json import JSONDecodeError # 當把不是 JSON 的東西硬要用 r.json() 解析時,會丟這個錯誤。
  • 範例:連線錯誤(DNS 找不到)
def demo_connection_error():
    print("\n[1] 連線錯誤(DNS 找不到)")
    try:
        # .invalid 是保留網域,保證不存在 → 一定會連線失敗
        requests.get("http://no.such.domain.invalid", timeout=5)
    except requests.exceptions.ConnectionError as e:
        print("→ 捕捉到 ConnectionError:", e)

這裡用一個保證不存在的網址 .invalid,一定會連不上伺服器。

抓到的錯誤是 ConnectionError,代表「網路層就失敗了」(像是找不到主機、連不上)。

  • 範例:逾時錯誤(回應太慢)
def demo_timeout_error():
    print("\n[2] 逾時錯誤(對方回應太慢)")
    try:
        # 這個端點會延遲 5 秒才回應,我們只等 2 秒
        requests.get("https://httpbin.org/delay/5", timeout=2)
    except requests.exceptions.Timeout as e:
        print("→ 捕捉到 Timeout:", e)

https://httpbin.org/delay/5 會延遲 5 秒才回應。

我們把 timeout 設成 2 秒,刻意造成逾時,會拋 Timeout

  • 範例:伺服器錯誤(HTTP 500)
def demo_server_error():
    print("\n[3] 伺服器錯誤(HTTP 500)")
    try:
        r = requests.get("https://httpbin.org/status/500", timeout=5)
        r.raise_for_status()  # 非 200~299 就拋 HTTPError
    except requests.exceptions.HTTPError as e:
        status = getattr(e.response, "status_code", None)
        print(f"→ 捕捉到 HTTPError: 狀態碼={status}, 訊息={e}")

這個網址會直接回 HTTP 500(伺服器內部錯誤)。

raise_for_status():只要不是 200–299 成功範圍,就會拋 HTTPError

把狀態碼印出來,除錯更清楚。

  • 範例:JSON 解析錯誤(內容不是 JSON)
def demo_json_error():
    print("\n[4] JSON 解析錯誤(回來不是 JSON)")
    try:
        r = requests.get("https://httpbin.org/html", timeout=5)
        r.raise_for_status()
        _ = r.json()  # 這裡會失敗,因為回的是 HTML
    except JSONDecodeError as e:
        print("→ 捕捉到 JSONDecodeError:", e)
    except ValueError as e:
        # 有些環境 r.json() 會丟 ValueError,這裡一併保險處理
        print("→ 捕捉到 ValueError(JSON 解析失敗):", e)

https://httpbin.org/html 回的是 HTML,不是 JSON。

r.json() 嘗試解析時會失敗:多數情況丟 JSONDecodeError,有些環境會丟 ValueError,所以兩種都接住。

  • 程式進入點(一次跑完四種錯誤示範)
if __name__ == "__main__":
    demo_connection_error()
    demo_timeout_error()
    demo_server_error()
    demo_json_error()
    print("\n 小實作結束:每一種錯誤都被穩穩接住了!")

依序呼叫四個 demo,觀察每種錯誤都被「正常捕捉」,程式就不會崩潰。

  • 執行結果:

接下來會介紹一個安全好用的 fetch_json 函式。

fetch_json 函式

當我們要從網路抓資料時,有時會遇到網路不穩、伺服器回不來,這時候寫一個有「重試」功能的函式很重要,能幫忙自動重新嘗試幾次。

這個函式會幫忙做什麼?

  • 用 requests 抓資料,設定 timeout,避免程式一直等下去。

  • 如果網路錯誤或逾時,會稍微等一下再重試,重試時間會越來越長(0.5秒 → 1秒 → 2秒)。

  • 如果遇到其他錯誤,會印出錯誤並結束。

程式範例:

def fetch_json(url, retries=2, backoff=0.5): # url:要請求的網址 # retries:可重試次數 
# backoff:退避的起始秒數,之後會乘以 2、4、8
    import time, requests
    attempt = 0
    
    while attempt <= retries: # 還有嘗試機會就一直試
        try:
            r = requests.get(url, timeout=5)   # 發 GET,最多等5秒
            r.raise_for_status()                # 只要不是 2xx,立刻丟 HTTPError
            
            return r.json()                    # 成功就把 JSON 解析成 Python 直接回傳
        except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
        網路連不上或回太慢 , 啟動「重試」。
            wait = backoff * (2 ** attempt)    # 計算等待秒數(0.5、1.0、2.0…)
            print(f"失敗了,{wait:.1f}秒後重試...")
            time.sleep(wait) # sleep 等一下再試
            attempt += 1
        except Exception as e: # 其他錯誤, 印出來並回 None。
            print("其他錯誤:", e)
            return None 
    return None # 迴圈跑完仍失敗 return None

去指定 URL 抓 JSON,如果遇到「連不上或逾時」就自動重試,而且用指數退避(越試等越久)。成功就回傳已解析好的 Python 物件;一直失敗就回 None

在專案中如何使用 fetch_json 函式?

假設我們寫了一個 fetch_json 的函式,它已經幫忙處理好所有網路錯誤和例外狀況。

這樣在別的地方要使用 API,只要直接調用這個函式就可以了,不用再寫一堆 try-except。

data = fetch_json("https://jsonplaceholder.typicode.com/posts/1")

if data:
    print("標題:", data.get("title"))
else:
    print("讀取失敗")
  • 呼叫 fetch_json,它會自己去連網抓資料並且處理錯誤。

  • 如果成功,它會回傳資料,我們就可以安心用 data.get("title") 拿要的內容。

  • 如果失敗,會回傳 None,可以根據這個結果,告訴使用者「讀取失敗」或做其他處理。

今日總結

  • 學會了 4 種常見錯誤與對應處理方式。

  • 學會 try-except 包裝 API 呼叫。

  • 練習了「fetch_json」:timeout、重試、退避、錯誤訊息。


上一篇
Day05 — JSON 解析入門:掌握取值、篩選與輸出
下一篇
Day 07 — 天氣 API 實作:用 Open-Meteo 顯示台北氣溫
系列文
每天一點 API:打造我的生活小工具11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言