一. 前言 #
嗨,大家好!我是拍拍君 🎉
你是不是每次寫 Python 要發 HTTP 請求時,都直覺地 import requests?requests 確實是經典中的經典,但它誕生於 2011 年,在這個非同步當道、HTTP/2 普及的年代,有沒有更現代的選擇呢?
答案就是 httpx !
httpx 的 API 幾乎和 requests 一模一樣(無痛轉換!),卻多了這些超強功能:
- ✅ 完整支援 async/await 非同步請求
- ✅ 原生支援 HTTP/2
- ✅ 內建 串流下載 與 串流上傳
- ✅ 支援 timeout 細粒度控制
- ✅ 100% type-annotated,IDE 提示超友善
今天拍拍君就帶你從零開始,一步步掌握 httpx 的所有必備技巧!
二. 安裝 #
使用 pip 安裝:
pip install httpx
如果你用 uv(拍拍君大推!):
uv add httpx
要啟用 HTTP/2 支援,需要額外安裝:
pip install httpx[http2]
確認安裝成功:
import httpx
print(httpx.__version__)
# 0.28.1
三. 基本用法:和 requests 幾乎一樣 #
如果你熟悉 requests,那 httpx 上手零門檻!
GET 請求 #
import httpx
response = httpx.get("https://httpbin.org/get")
print(response.status_code) # 200
print(response.json()) # 回傳 JSON dict
POST 請求 #
import httpx
data = {"name": "拍拍君", "skill": "Python"}
response = httpx.post("https://httpbin.org/post", json=data)
print(response.json()["json"])
# {'name': '拍拍君', 'skill': 'Python'}
帶查詢參數 #
import httpx
params = {"q": "python httpx", "page": 1}
response = httpx.get("https://httpbin.org/get", params=params)
print(response.url)
# https://httpbin.org/get?q=python+httpx&page=1
自訂 Headers #
import httpx
headers = {"Authorization": "Bearer my-secret-token"}
response = httpx.get("https://httpbin.org/headers", headers=headers)
print(response.json()["headers"]["Authorization"])
# Bearer my-secret-token
是不是跟 requests 完全一樣的感覺?沒錯,這就是 httpx 的設計理念 — 零學習成本轉換。
四. Client 物件:連線效能大提升 #
每次用 httpx.get() 都會建立新連線。如果你要連續發很多請求,用 Client 可以重用 TCP 連線,速度飛快!
基本用法 #
import httpx
with httpx.Client() as client:
# 這三個請求共用同一條 TCP 連線
r1 = client.get("https://httpbin.org/get")
r2 = client.get("https://httpbin.org/ip")
r3 = client.get("https://httpbin.org/user-agent")
print(r1.status_code, r2.status_code, r3.status_code)
# 200 200 200
設定預設值 #
Client 可以設定全域的 headers、base_url、timeout 等等:
import httpx
with httpx.Client(
base_url="https://api.github.com",
headers={"Accept": "application/vnd.github.v3+json"},
timeout=10.0,
) as client:
# 自動加上 base_url 和 headers
repos = client.get("/users/python/repos")
print(f"Python 有 {len(repos.json())} 個公開 repo")
這樣寫 API 客戶端超乾淨!
五. Async 非同步請求:httpx 的殺手鐧 #
這是 httpx 相比 requests 最大的優勢 — 原生支援 async/await!
基本非同步請求 #
import httpx
import asyncio
async def fetch_data():
async with httpx.AsyncClient() as client:
response = await client.get("https://httpbin.org/get")
return response.json()
result = asyncio.run(fetch_data())
print(result["url"])
# https://httpbin.org/get
並行請求:速度起飛 🚀 #
同時發出多個請求,不用等前一個完成:
import httpx
import asyncio
import time
urls = [
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/2",
]
async def fetch_all():
async with httpx.AsyncClient() as client:
tasks = [client.get(url) for url in urls]
responses = await asyncio.gather(*tasks)
return [r.status_code for r in responses]
start = time.time()
results = asyncio.run(fetch_all())
elapsed = time.time() - start
print(f"狀態碼: {results}")
print(f"耗時: {elapsed:.1f} 秒")
# 同步要 10 秒,非同步只要約 2 秒!
五個各要 2 秒的請求,同步得花 10 秒,但非同步並行只要約 2 秒。這就是非同步的威力!
搭配 asyncio.as_completed #
如果你想先到先處理:
import httpx
import asyncio
async def fetch_and_print():
async with httpx.AsyncClient() as client:
tasks = {
asyncio.create_task(client.get(f"https://httpbin.org/delay/{i}")): i
for i in [3, 1, 2]
}
for coro in asyncio.as_completed(tasks.keys()):
response = await coro
data = response.json()
print(f"完成: delay={data['url'].split('/')[-1]}s")
asyncio.run(fetch_and_print())
# 完成: delay=1s
# 完成: delay=2s
# 完成: delay=3s
六. Timeout 精細控制 #
requests 只能設一個 timeout 數字,httpx 可以分別控制不同階段:
import httpx
# 簡單用法(和 requests 一樣)
response = httpx.get("https://httpbin.org/get", timeout=5.0)
# 進階用法:分別控制各階段 timeout
timeout = httpx.Timeout(
connect=5.0, # 建立連線最多 5 秒
read=10.0, # 讀取回應最多 10 秒
write=5.0, # 發送請求最多 5 秒
pool=5.0, # 等待連線池最多 5 秒
)
with httpx.Client(timeout=timeout) as client:
response = client.get("https://httpbin.org/delay/3")
print(response.status_code) # 200
處理 Timeout 例外 #
import httpx
try:
response = httpx.get("https://httpbin.org/delay/5", timeout=2.0)
except httpx.TimeoutException:
print("請求超時了!拍拍君等不及啦 😤")
except httpx.HTTPError as e:
print(f"HTTP 錯誤: {e}")
七. HTTP/2 支援 #
HTTP/2 可以在同一條 TCP 連線上多工傳輸,對 API 密集應用很有幫助:
import httpx
with httpx.Client(http2=True) as client:
response = client.get("https://www.google.com")
print(response.http_version)
# HTTP/2
💡 記得先安裝
httpx[http2]才能啟用喔!
八. 串流下載:處理大檔案 #
下載大檔案時,不想把整個檔案載入記憶體?用串流!
import httpx
url = "https://speed.hetzner.de/100MB.bin"
with httpx.stream("GET", url) as response:
total = int(response.headers.get("content-length", 0))
downloaded = 0
with open("big_file.bin", "wb") as f:
for chunk in response.iter_bytes(chunk_size=8192):
f.write(chunk)
downloaded += len(chunk)
pct = (downloaded / total * 100) if total else 0
print(f"\r下載中... {pct:.1f}%", end="", flush=True)
print("\n下載完成!🎉")
非同步版本:
import httpx
import asyncio
async def download_large_file():
async with httpx.AsyncClient() as client:
async with client.stream("GET", "https://speed.hetzner.de/100MB.bin") as response:
with open("big_file.bin", "wb") as f:
async for chunk in response.aiter_bytes(chunk_size=8192):
f.write(chunk)
print("非同步下載完成!🚀")
asyncio.run(download_large_file())
九. 實戰範例:打造簡易 API 客戶端 #
來整合前面學的,做一個 GitHub API 客戶端:
import httpx
import asyncio
from dataclasses import dataclass
@dataclass
class GitHubRepo:
name: str
stars: int
language: str | None
url: str
class GitHubClient:
def __init__(self, token: str | None = None):
headers = {"Accept": "application/vnd.github.v3+json"}
if token:
headers["Authorization"] = f"Bearer {token}"
self._client = httpx.AsyncClient(
base_url="https://api.github.com",
headers=headers,
timeout=httpx.Timeout(connect=5.0, read=15.0, write=5.0, pool=5.0),
)
async def __aenter__(self):
return self
async def __aexit__(self, *args):
await self._client.aclose()
async def get_user_repos(self, username: str) -> list[GitHubRepo]:
response = await self._client.get(
f"/users/{username}/repos",
params={"sort": "stars", "per_page": 5},
)
response.raise_for_status()
return [
GitHubRepo(
name=repo["name"],
stars=repo["stargazers_count"],
language=repo["language"],
url=repo["html_url"],
)
for repo in response.json()
]
async def get_repo_info(self, owner: str, repo: str) -> dict:
response = await self._client.get(f"/repos/{owner}/{repo}")
response.raise_for_status()
return response.json()
async def main():
async with GitHubClient() as gh:
# 取得使用者的熱門 repo
repos = await gh.get_user_repos("python")
print("🐍 Python 組織的熱門 Repo:")
for repo in repos:
lang = repo.language or "N/A"
print(f" ⭐ {repo.stars:>6,} | {repo.name} ({lang})")
# 查詢特定 repo
info = await gh.get_repo_info("encode", "httpx")
print(f"\n📦 httpx: ⭐ {info['stargazers_count']:,} stars")
asyncio.run(main())
執行結果(數字會隨時間變化):
🐍 Python 組織的熱門 Repo:
⭐ 65,432 | cpython (C)
⭐ 12,345 | mypy (Python)
⭐ 8,765 | typeshed (Python)
⭐ 5,432 | peps (reStructuredText)
⭐ 3,210 | devguide (reStructuredText)
📦 httpx: ⭐ 14,567 stars
結語 #
httpx 可以說是 requests 的完美升級版:
| 功能 | requests | httpx |
|---|---|---|
| 同步請求 | ✅ | ✅ |
| 非同步請求 | ❌ | ✅ |
| HTTP/2 | ❌ | ✅ |
| Timeout 細粒度控制 | ❌ | ✅ |
| Type hints | 部分 | 完整 |
| API 相容性 | — | 幾乎相同 |
如果你正在開始新專案,拍拍君強烈建議直接用 httpx!尤其是需要非同步的場景(Web 框架、爬蟲、批量 API 呼叫),httpx 會讓你的程式碼又快又優雅。
下次寫程式要發 HTTP 請求時,試試把 import requests 換成 import httpx 吧!你會愛上它的 ❤️
延伸閱讀 #
- httpx 官方文件
- httpx GitHub
- Python typing 完全攻略 — httpx 搭配 type hints 更好用
- Python uv 完全攻略 — 用 uv 管理你的 httpx 專案