本文 AI 產出,尚未審核

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 的網路魅力!