Python 網路與 API(Networking & APIs)
主題:urllib 與 requests 模組
簡介
在現代的程式開發中,與外部服務溝通是不可或缺的需求。無論是取得天氣資訊、呼叫第三方支付 API,或是爬取公開資料,都必須透過 HTTP/HTTPS 協議與遠端伺服器交換資料。Python 內建的 urllib 系列提供了最基本的 URL 處理與 HTTP 請求功能,而社群廣受好評的第三方套件 requests 則以簡潔、直觀的 API 讓開發者能更快速、可靠地完成網路請求。
本篇文章將從 概念、實作範例、常見陷阱與最佳實踐 三個層面,帶領讀者深入了解這兩個模組的差異與使用時機,並提供可直接套用於專案的程式碼示例,協助初學者到中階開發者在實務上快速上手。
核心概念
1. urllib 系列概覽
urllib 是 Python 標準庫的一部分,主要包含四個子模組:
| 子模組 | 功能 | 常用函式 |
|---|---|---|
urllib.request |
發送 HTTP/HTTPS 請求 | urlopen(), Request() |
urllib.parse |
URL 編碼、解析 | urlencode(), urlparse() |
urllib.error |
例外類別 | URLError, HTTPError |
urllib.robotparser |
讀取 robots.txt | RobotFileParser |
重點:
urllib只提供 底層 的請求功能,若要處理 Session、檔案上傳、JSON 解析等需求,需要自行額外撰寫程式碼或搭配其他套件。
1.1 基本 GET 請求
import urllib.request
import urllib.error
url = "https://api.github.com/repos/python/cpython"
try:
with urllib.request.urlopen(url) as response:
# 讀取回傳的位元組資料,然後解碼為字串
data = response.read().decode("utf-8")
print(data[:200]) # 只顯示前 200 個字元
except urllib.error.HTTPError as e:
print(f"HTTP 錯誤: {e.code} - {e.reason}")
except urllib.error.URLError as e:
print(f"URL 錯誤: {e.reason}")
urlopen()直接返回一個類似檔案的物件,可使用read()取得回應內容。- 需要自行 捕捉例外,否則網路錯誤會拋出
URLError或HTTPError。
1.2 POST 請求與表單編碼
import urllib.request
import urllib.parse
url = "https://httpbin.org/post"
payload = {
"username": "alice",
"password": "secret"
}
# 將字典編碼為 application/x-www-form-urlencoded 格式
data = urllib.parse.urlencode(payload).encode("utf-8")
req = urllib.request.Request(url, data=data, method="POST")
req.add_header("User-Agent", "urllib-demo/1.0")
with urllib.request.urlopen(req) as resp:
result = resp.read().decode("utf-8")
print(result)
urlencode()會自動把字典轉成key=value&...形式,並以utf-8編碼成位元組。- 使用
Request物件可以自行設定 HTTP 方法、Header 等資訊。
1.3 處理 JSON 回應
import urllib.request, json
url = "https://api.github.com/repos/python/cpython"
with urllib.request.urlopen(url) as resp:
raw = resp.read()
# 直接使用 json.loads() 解析位元組或字串
data = json.loads(raw)
print(f"Stars: {data['stargazers_count']}")
json模組是標準庫,配合urllib可輕鬆完成 JSON 解析。
2. requests:讓 HTTP 請求變得「人性化」
requests 由 Kenneth Reitz 開發,設計哲學是 「讓人類的 HTTP 請求更簡單」。它把許多繁瑣的步驟(如編碼、例外處理、Session 管理)封裝在直覺的 API 中。
安裝方式
pip install requests
2.1 基本 GET
import requests
url = "https://api.github.com/repos/python/cpython"
resp = requests.get(url, timeout=5) # timeout 讓請求不會無限等待
resp.raise_for_status() # 若狀態碼非 2xx,會拋出 HTTPError
data = resp.json() # 直接把回應解析為 dict
print(f"Stars: {data['stargazers_count']}")
requests.get()直接回傳 Response 物件,內建json()、text、content等屬性。raise_for_status()是檢查 HTTP 錯誤的好幫手,省去手動比對status_code。
2.2 POST + JSON
import requests
url = "https://httpbin.org/post"
payload = {"username": "bob", "age": 30}
resp = requests.post(url, json=payload) # 會自動把 payload 轉成 JSON 並設定 Content-Type
print("Status:", resp.status_code)
print("Response JSON:", resp.json())
- 只要傳入
json=參數,requests會自動 序列化 並設定Content-Type: application/json,免去手動json.dumps()。
2.3 下載檔案(流式寫入)
import requests
url = "https://speed.hetzner.de/100MB.bin"
local_path = "100MB.bin"
with requests.get(url, stream=True) as r:
r.raise_for_status()
# 以 1 MB 為單位寫入檔案,避免一次讀入過大記憶體
with open(local_path, "wb") as f:
for chunk in r.iter_content(chunk_size=1024 * 1024):
f.write(chunk)
print("下載完成!")
stream=True讓回應內容不會一次載入記憶體,適合大型檔案下載。iter_content()會依照chunk_size產生資料流。
2.4 Session:保持 Cookie 與連線池
import requests
session = requests.Session()
login_url = "https://httpbin.org/cookies/set/sessioncookie/123456"
protected_url = "https://httpbin.org/cookies"
# 第一次請求會取得 cookie
session.get(login_url)
# 後續請求自動帶上 cookie
resp = session.get(protected_url)
print(resp.json()) # {"cookies": {"sessioncookie": "123456"}}
Session物件會自動 管理 Cookie,並共用底層的 連線池,提升效能。
2.5 自訂 Header、驗證與 Timeout
import requests
url = "https://api.github.com/user"
headers = {"Accept": "application/vnd.github.v3+json"}
auth = ("your_username", "your_token") # 基本認證(不建議明碼存放)
resp = requests.get(url, headers=headers, auth=auth, timeout=10)
print(resp.status_code)
headers可以加入User-Agent、Accept等自訂資訊。auth支援 Basic、Digest、OAuth 等多種驗證方式(後續章節會說明)。
3. urllib vs requests:何時選擇哪個?
| 比較項目 | urllib |
requests |
|---|---|---|
| 安裝需求 | 標準庫,無需額外安裝 | 需 pip install requests |
| 語法簡潔度 | 較冗長,需要手動處理編碼、例外 | 直觀、內建 JSON、Session 等功能 |
| 功能完整度 | 基本 HTTP 請求,缺少自動重試、連線池 | 支援重試、連線池、流式下載、檔案上傳 |
| 效能 | 輕量、無額外抽象層 | 多層抽象,對大多數情境仍足夠快速 |
| 適用情境 | 嵌入式環境、無法安裝第三方套件 | 大部分 Web API、爬蟲、測試腳本 |
結論:若僅需一次性簡單 GET/POST,且環境限制只能使用標準庫,
urllib完全足夠;若開發正式服務或需要頻繁與 API 互動,建議直接使用requests,可大幅降低程式碼複雜度與錯誤率。
常見陷阱與最佳實踐
1. 忽略 Timeout
陷阱:預設情況下 urllib.request.urlopen() 與 requests.get() 會 無限等待,在網路不穩或遠端伺服器掛掉時會卡住整個程式。
最佳實踐:永遠設定 timeout,且根據需求調整合理的秒數。
# urllib
urllib.request.urlopen(url, timeout=5)
# requests
requests.get(url, timeout=5)
2. 未處理 例外
陷阱:直接呼叫 urlopen() 或 requests.get(),當 DNS 解析失敗、連線被拒絕或回傳非 2xx 時程式會直接拋例外。
最佳實踐:使用 try/except 捕捉 urllib.error.URLError、requests.exceptions.RequestException,並適度 重試(可搭配 tenacity 套件)。
import requests
from requests.exceptions import RequestException
def fetch(url):
for i in range(3): # 最多重試 3 次
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
return resp.json()
except RequestException as e:
print(f"第 {i+1} 次嘗試失敗: {e}")
raise RuntimeError("所有重試皆失敗")
3. 直接拼接 URL 而不 URL Encode
陷阱:若參數中含有空格、中文或特殊符號,直接拼接會導致 400 Bad Request。
最佳實踐:使用 urllib.parse.urlencode() 或 requests 的 params= 參數自動編碼。
# urllib
params = {"q": "台北 天氣", "lang": "zh-TW"}
url = "https://example.com/search?" + urllib.parse.urlencode(params)
# requests
resp = requests.get("https://example.com/search", params=params)
4. 忽略 SSL 驗證
陷阱:在測試環境常見 verify=False,但在正式環境若關閉驗證,會增加 中間人攻擊 的風險。
最佳實踐:除非確定環境安全,否則 保持 SSL 驗證,若需自訂 CA 憑證,可使用 verify="path/to/ca.pem"。
5. 大檔案一次讀入記憶體
陷阱:使用 response.content 或 response.text 直接讀取 1 GB 檔案會耗盡記憶體。
最佳實踐:使用 流式 (stream) 下載 並分塊寫入磁碟。
with requests.get(url, stream=True) as r:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
實際應用場景
| 場景 | 建議模組 | 範例說明 |
|---|---|---|
| 取得天氣 API(JSON) | requests |
使用 requests.get(..., params={...}),直接呼叫 resp.json() 解析。 |
| 下載大型影像或檔案 | requests (stream) |
以 stream=True 分塊寫入磁碟,避免記憶體炸裂。 |
| 簡易爬蟲(只抓一次頁面) | urllib |
若環境不允許安裝第三方套件,使用 urllib.request.urlopen() 即可。 |
| 需要維持登入狀態的多次請求 | requests.Session |
Session 會自動管理 Cookie,適合登入後的資料抓取。 |
| 與內部服務(只支援 HTTP) | urllib 或 requests(視團隊規範) |
若內部服務不需要額外功能,兩者皆可;若需重試與連線池,選 requests。 |
| 測試 API 回傳錯誤碼 | requests + raise_for_status() |
直接拋出 HTTPError,測試框架可捕捉驗證。 |
小技巧:在開發階段可以使用
httpbin.org這類測試服務,快速驗證 GET、POST、Header、Cookie 等行為是否符合預期。
總結
urllib為 標準庫,適合輕量、單次的 HTTP 請求或在無法安裝第三方套件的環境中使用。requests則以 簡潔、功能完整 為設計目標,提供自動編碼、JSON 解析、Session、流式下載等高階功能,讓開發者能更專注於業務邏輯。- 在實務開發中,設定 timeout、妥善捕捉例外、使用 URL Encode、保留 SSL 驗證 是避免常見問題的關鍵。
- 依照需求選擇合適的模組,並遵循最佳實踐,即可在 Python 中輕鬆完成 網路與 API 的各種任務。
祝你玩得開心 🎉
只要掌握了上述概念與範例,無論是爬取資料、呼叫雲端服務,或是建立自己的 RESTful API 客戶端,都能得心應手。歡迎把本文的程式碼直接貼到自己的專案中,立即體驗 Python 的網路魅力!