本文 AI 產出,尚未審核

Python 網路與 API(Networking & APIs)

主題:urllibrequests 模組


簡介

在現代的程式開發中,與外部服務溝通是不可或缺的需求。無論是取得天氣資訊、呼叫第三方支付 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() 取得回應內容。
  • 需要自行 捕捉例外,否則網路錯誤會拋出 URLErrorHTTPError

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()textcontent 等屬性。
  • 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-AgentAccept 等自訂資訊。
  • auth 支援 BasicDigestOAuth 等多種驗證方式(後續章節會說明)。

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.URLErrorrequests.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()requestsparams= 參數自動編碼。

# 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.contentresponse.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) urllibrequests(視團隊規範) 若內部服務不需要額外功能,兩者皆可;若需重試與連線池,選 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 的網路魅力!