API 有時候會呼叫失敗?今天的目標是要學會「錯誤處理」,讓程式更安全、更穩定。
網路並不是永遠順暢,API 呼叫有時會失敗。
常見情況比如:
網路斷線了,沒辦法連上伺服器
伺服器當機或正在維護
回傳的資料格式不是預期的 JSON
如果不處理這些錯誤,程式會直接停止運行,可能讓使用者看到錯誤訊息或是整個程式崩潰。
而你如果有寫錯誤處理程式碼,出錯時可以:
告訴使用者「發生問題請稍後再試」
自動重新嘗試連線或呼叫
記錄錯誤資訊,方便日後追蹤修正
錯誤處理就是程式的「安全網」,能讓程式在遇到問題時不會崩潰,還能提供更好的使用體驗。
連線錯誤(ConnectionError)
這是當網路斷了,或電腦找不到想連結的伺服器時會發生的錯誤。就像電話打不通。
逾時錯誤(Timeout)
伺服器太慢回應,超過你設定的等待時間,程式就會離開不等了。好比打電話等很久沒人接。
伺服器錯誤(HTTPError)
當伺服器回傳錯誤訊息時,例如:
4xx:客戶端錯誤,可能是網址錯或沒權限。
5xx:伺服器錯誤,表示伺服器沒做好工作。
JSON 解析錯誤(ValueError)
當要求伺服器回傳 JSON,但實際收到的不是 JSON 資料(可能是錯誤頁面 HTML 或空白),就會發生這個錯誤。
這些錯誤代表在網路資料傳輸時,難免會遇到狀況,需要在寫程式時做好預防,避免程式因錯誤當機。
寫程式時,呼叫網路 API 有可能會出錯(像網路斷線、伺服器沒反應等),這時候用 try
和 except
來包住可能出錯的程式碼,可以防止程式直接當掉,並且告訴你發生了什麼錯誤。
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")
在寫抓取網路資料的程式時,我們可以故意製造錯誤,確保程式能正確處理並回報錯誤,避免程式崩潰。
連線錯誤(ConnectionError)
使用一個不存在的網址,會出現找不到伺服器的錯誤。
範例:請求 http://不存在的網站.abc
。
逾時錯誤(Timeout)
請求一個會延遲回應的網址,但我們設定的等待時間(timeout)很短,會收到超時錯誤。
範例:請求 https://httpbin.org/delay/5
,但 timeout 設成 2 秒。
伺服器錯誤(HTTPError)
有時候伺服器會故意回錯誤碼模擬失敗,例如 500(伺服器內部錯誤)。
範例:請求 https://httpbin.org/status/500
。
JSON 解析錯誤(ValueError)
有些網址回傳的內容不是 JSON 格式,例如純文字或 HTML,程式嘗試用 .json()
解析會爆錯。
範例:請求 https://httpbin.org/html
,它回 HTML 頁面。
接下來,我們將以這四種錯誤為範例,練習今天的小實作。
import requests # 用來發送 HTTP 請求
from json import JSONDecodeError # 當把不是 JSON 的東西硬要用 r.json() 解析時,會丟這個錯誤。
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
。
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
。
把狀態碼印出來,除錯更清楚。
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 函式。
當我們要從網路抓資料時,有時會遇到網路不穩、伺服器回不來,這時候寫一個有「重試」功能的函式很重要,能幫忙自動重新嘗試幾次。
這個函式會幫忙做什麼?
用 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 的函式,它已經幫忙處理好所有網路錯誤和例外狀況。
這樣在別的地方要使用 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、重試、退避、錯誤訊息。