iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
Modern Web

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

Day 10 — 誰能拒絕貓咪?隨機貓圖下載實作

  • 分享至 

  • xImage
  •  

前幾天已經練習了天氣 API和笑話 API。

今天會換成比較有趣的題材:下載隨機的貓咪圖片並存檔。(誰可以拒絕貓咪呢?)

我將會透過這個練習,來學習如何處理圖片回應、正確存檔,以及加上錯誤處理。

為什麼選圖片 API?

  1. 免費且不需要 API 金鑰
  • 使用起來很方便,免註冊就能測試。
  1. 成果直觀好感受
  • 程式跑完後,馬上能看到一張圖片,很有成就感。
  1. 學會處理二進位資料(Binary Data)
  • 跟之前處理的 JSON 文字資料不一樣,圖片是二進位檔案格式。

  • 練習圖片 API 可以學會如何下載和保存二進位檔案,是個重要且實用的技能。

API 選擇與介紹:

使用 CATAAS (Cat as a Service)

  • 這個 API 網址是:https://cataas.com/cat

  • 每次呼叫都會獲得一張隨機貓咪圖片。

  • 不需要註冊或金鑰,使用門檻低。

下載圖片的基本流程

1. 呼叫 API 取得圖片資料

從 API 拿到的資料會包含圖片本身,這些資料是二進位資料,不是文字。
API 回傳時會有 Content-Type 標頭,告訴我們圖片的格式是什麼,例如 image/jpeg、image/png 或 image/webp。

2. 判斷檔案格式

根據 API 回傳的 Content-Type,決定圖片檔案的副檔名是 .jpg.png 還是其他格式。

3. 把圖片資料存成檔案

用二進位寫入模式打開檔案(例如 cat.jpg),把拿到的圖片資料寫進去,這樣程式就會把圖片保存到本地。

4. 顯示下載完成訊息

圖片成功存檔後,可以印出提示,告訴我們圖片已經下載好了。

實作流程

建立一個資料夾 day10

在 day10 資料夾裡,建立程式檔案 day10_cat_download.py

再建立一個 images 資料夾,用來放下載回來的圖片檔案。

mkdir day10
cd day10
ni day10_cat_download.py
mkdir images

requests 套件發送 requests.get(url) 抓取隨機貓咪圖片資料。

開一個新檔案 images/cat.jpg,用二進位寫入模式打開。

再把 API 回來的圖片內容 r.content(二進位資料)寫進去。

這樣就會在 images 資料夾裡存出一張貓咪圖片。

# day10_cat_download.py
import requests

url = "https://cataas.com/cat"  # 隨機貓圖,免金鑰
r = requests.get(url, timeout=10)
r.raise_for_status()             # 不是 2xx 會丟錯

with open("images/cat.jpg", "wb") as f: 
    f.write(r.content)

print("已下載:images/cat.jpg")

執行結果:

已下載:images/cat.jpg

可以在day10的資料夾裡,看到images 資料夾,再從中看到隨機生成的貓咪圖片。

接下來是練習增加自動判斷副檔名、錯誤處理、流式下載等功能。

(流式下載就是把伺服器資料拆成小區塊逐段接收並邊收邊處理,不用一次載入整個檔案。)

  • 設定基本資訊
import os # 管檔案路徑與資料夾
import time # 處理時間延遲
from datetime import datetime # 拿現在時間、做時間字串
import requests

CAT_URL = "https://cataas.com/cat"     # 每次呼叫都會回一張新的貓圖
SAVE_DIR = "images" # 圖片要存到 images 資料夾。
  • 根據回應的 Content-Type 決定副檔名
EXT_BY_CONTENT_TYPE = {
    "image/jpeg": ".jpg",
    "image/jpg":  ".jpg",
    "image/png":  ".png",
    "image/gif":  ".gif",
    "image/webp": ".webp",
}

伺服器會回應「這是什麼格式」;我們就用對應的副檔名來存。

如果沒對應到,就預設用 .jpg

def pick_ext(resp) -> str:
    ct = (resp.headers.get("Content-Type") or "").split(";")[0].lower()
    return EXT_BY_CONTENT_TYPE.get(ct, ".jpg")  # 不識別就用 .jpg

從回應標頭抓 Content-Type,例如 image/jpeg,拿來查表決定副檔名。

def download_cat(save_dir=SAVE_DIR, url=CAT_URL, timeout=15):
    os.makedirs(save_dir, exist_ok=True) # 沒有 images 也沒關係,自動幫忙建立。
    try:
        # 用 stream=True 邊收邊寫,比一次讀完省記憶體
        with requests.get(url, timeout=timeout, stream=True) as r: # 發出請求去抓圖

timeout 防止卡住,stream=True 開啟流式下載(一塊一塊寫檔,省記憶體)。

            r.raise_for_status()
            ext = pick_ext(r)
            # 用時間當檔名,避免覆蓋;加上毫秒防同秒重名
            ts = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3]
            filename = f"cat_{ts}{ext}"
            path = os.path.join(save_dir, filename)

依回應決定副檔名。

用時間當檔名,檔案就不會互相覆蓋,像:cat_20250922_153012_847.jpg

            total = 0 # 準備一個計數器,記錄目前已經寫入了幾個位元組
            with open(path, "wb") as f: # 以二進位寫入打開要存的檔案路徑 path。
                for chunk in r.iter_content(chunk_size=8192):
                # 從網路回應 r 裡一塊一塊拿資料(每塊 8192 bytes ≈ 8KB)。
                    if chunk: # 有些情況可能拿到空資料塊,這行用來略過空的。
                        f.write(chunk) # 把拿到的這塊資料寫進檔案。
                        total += len(chunk) 
                        # 把這塊資料的大小加到 total,累積出整個檔案的總 bytes。

        print(f"已下載:{path}({total} bytes)")
        return path # 把剛存好的檔案路徑回傳
  • 錯誤處理
    except requests.exceptions.Timeout:
        print("逾時:伺服器回應太慢,稍後再試")
    except requests.exceptions.ConnectionError:
        print("連線錯誤:請檢查網路或網址")
    except requests.exceptions.HTTPError as e:
        status = e.response.status_code if e.response else "N/A"
        print(f"伺服器錯誤(HTTP {status}):{e}")
    except Exception as e:
        print(f"其他錯誤:{e}")
    return None
if __name__ == "__main__": 

直接跑檔案時就下載一張圖。

執行結果:

已下載:images\cat_20250922_220902_278.jpg(202414 bytes)

怎麼連續下載多張圖?

if __name__ == "__main__":
    for _ in range(3): # 用 for 迴圈一次下載多張圖片
        download_cat()
        time.sleep(0.5)  # 每次下載後加入 time.sleep() 暫停,避免請求太頻繁被伺服器擋掉

這裡是練習連續下載三張圖。

執行結果:

已下載:images\cat_20250922_221914_113.jpg(153131 bytes)
已下載:images\cat_20250922_221919_038.jpg(73846 bytes)
已下載:images\cat_20250922_221924_362.jpg(51733 bytes)

今日總結

  • 學會處理 API 回傳的圖片

  • 學會存檔到本機並自動命名

  • 學會用 try-except 安全包裝圖片下載


上一篇
Day 09 — API 設計第一步:用書店的例子學 RESTful
下一篇
Day 11 — JSON vs XML:API 為什麼更愛 JSON
系列文
每天一點 API:打造我的生活小工具11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言