🚀 專案解構:從教學範例到生產級服務 —— FastAPI 非同步爬蟲的升級之路
🎯 專案動機:從「能跑」到「可部署、安全、可擴充」
作為一名資深全端工程師,我深知一個好的技術作品集必須超越基礎功能實現。它必須展示:系統級的架構思維、生產環境的安全性考量,以及面對 I/O 密集挑戰的解決方案。
這個專案的起點是一個簡單的 FastAPI + requests 教學範例。它的問題顯而易見:同步爬蟲會阻塞整個服務、使用 SQLite 缺乏擴展性、且沒有任何安全或自動化機制。
您可以透過以下 GitHub 連結檢閱本專案的原始碼:https://github.com/BpsEason/fastapi-news-crawler.git
我的目標是將它升級為一個具備以下特性的「作品集級別」後端服務:
高效能:全面採用非同步 I/O。
高穩定性:排程任務具備穩固的 Session 處理機制。
高安全性:實作 API Key 雜湊驗證,消除明文密鑰風險。
可自動化:具備定時爬取與自動測試 CI/CD 流程。
🧠 技術架構設計思維:I/O 密集型任務的全面非同步化
現代後端服務的核心瓶頸往往不在 CPU,而在於 I/O 操作(網絡請求、資料庫讀寫)。因此,我決定採用全面非同步化的架構,以確保整個系統的非阻塞性。
1. 核心技術棧選擇與優勢
| 技術模組 | 選擇 | 架構決策背後的原因 |
| 框架核心 | FastAPI | 基於 Starlette/Pydantic 的 ASGI 框架,原生支援非同步,天生適合 I/O 密集型服務。 |
| HTTP 客戶端 | httpx | 作為 requests 的非同步替代品,確保爬蟲在等待網絡回應時,能釋放控制權,讓服務器處理其他請求。 |
| 資料庫 | PostgreSQL + AsyncPG | PostgreSQL 是生產環境標準;AsyncPG 是一個專為 asyncio 設計的原生驅動,比傳統 ORM 轉接異步的方式效能更佳。 |
| ORM 介面 | SQLModel | 整合了 Pydantic 的資料模型驗證和 SQLAlchemy 的 ORM 功能,極大地簡化了程式碼的定義與序列化。 |
| 自動化排程 | APScheduler | 輕量且支持 asyncio,適合在單一服務實例中嵌入定時任務,避免引入 Celery 等複雜系統。 |
2. 模組化與職責分離
專案採用清晰的模組化結構,將 API 路由、業務邏輯、配置和數據庫連接完全解耦:
/app/core: 專注於安全性(security.py)和配置(config.py)。/app/services: 專注於業務邏輯(crawler.py和scheduler.py)。/app/api/v1: 專注於路由和 API 驗證。
這種設計極大地提高了單元測試的效率和未來的可擴充性。
🕷️ 實戰細節一:非同步爬蟲與數據品質控制
1. 異步 I/O:發揮 httpx 的最大效益
在 app/services/crawler.py 中,我們使用 async with httpx.AsyncClient() 發起請求。這使得我們能夠在單個 Python 進程中同時發出數十甚至數百個 HTTP 請求,將總體爬取時間縮短數倍。
# 爬蟲核心 (httpx)
async def fetch_news_titles(url: str) -> list[dict]:
# 使用 async with 確保連線正確關閉
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(url)
# ... BeautifulSoup 解析邏輯 ...
2. 數據品質與冪等性:實作去重邏輯
一個作品集級別的爬蟲必須處理數據重複問題。我採用的去重策略是 「資料庫唯一性檢查 + 異步事務」:
資料庫約束: 在
Article模型中,將url欄位設定為unique=True。業務邏輯去重: 在
save_articles服務中,對每個新抓取的文章,先執行異步SELECT查詢,檢查 URL 是否已存在。
這確保了數據的乾淨度。若未來需要處理高併發插入,則應考慮使用 PostgreSQL 的 ON CONFLICT DO NOTHING,但當前架構下的檢查更具程式碼可讀性。
🔐 實戰細節二:安全性升級 —— API Key 雜湊驗證
這是最能展現資深工程師思維的一環:消除 API Key 明文洩露的風險。
踩坑經驗:為何明文比較不可接受?
在原始教學範例中,API Key 驗證可能只是簡單的 if api_key == settings.API_KEY_SECRET。這種做法會將明文密鑰硬編碼或存儲在未加密的環境變數中。一旦配置洩露,系統將完全暴露。
解決方案:Bcrypt 雜湊驗證
在 app/core/security.py 中,我引入了 passlib[bcrypt] 實現密鑰雜湊:
配置存儲: 環境變數中存儲的是 API Key 的 Bcrypt 雜湊值(預先計算好)。
運行時驗證:
使用者傳入明文 Key。
驗證函數使用
pwd_context.verify(傳入Key, 預期雜湊值)。bcrypt在內部計算傳入 Key 的雜湊,並與預期雜湊值進行比較。
這種設計使得程式碼中永遠不會出現明文密鑰,大大提高了系統的安全性。我們透過 FastAPI 的 Security(APIKeyHeader(...)) 依賴注入,將這項安全措施無縫應用於所有受保護的 API 端點。
⏱️ 實戰細節三:穩定性與自動化 —— 排程任務的資源管理
挑戰:排程任務中的連線池洩露 (Connection Pool Leak)
APScheduler 作為一個獨立的線程或進程運行,當它嘗試從 FastAPI 的異步依賴生成器 (AsyncGenerator) 中獲取資料庫 Session 時,如果沒有正確處理,極易導致連接池資源洩露。
錯誤模式是在任務結束時忘記或無法可靠地呼叫 Session 生成器的 aclose() 方法。
解決方案:contextlib.asynccontextmanager
為了確保資源的穩健釋放,我在 app/services/scheduler.py 中使用了 Python 標準庫的 contextlib:
# 確保安全的 Session 管理
@asynccontextmanager
async def get_safe_db_session():
gen = get_db_session()
try:
session = await anext(gen)
yield session # 執行爬蟲/DB操作
finally:
# 關鍵:確保無論成功或失敗,都調用 aclose() 關閉 Session
await gen.aclose()
透過 async with get_safe_db_session() as session: 語句,我將定時任務包裹在一個異步上下文管理器中。這從架構上保證了 Session 在任務結束時會被安全釋放,徹底消除了連線池洩露的風險,實現了生產級的穩定性。
🧪 測試與部署:建立可信賴的 CI/CD 流程
1. 異步測試與環境模擬
為了測試整個系統,我們使用了 pytest-asyncio 和 httpx.AsyncClient。
API 測試:
tests/test_api.py驗證了 API Key 驗證邏輯(測試無效 Key 時是否返回403 FORBIDDEN)。CI/CD 技巧: 在 GitHub Actions 的 CI 流程中,我們利用環境變數將資料庫 URL 切換為 SQLite +
aiosqlite。這樣做的目的是在運行測試時,避免啟動笨重的 PostgreSQL 容器,大幅加快 CI 速度並簡化設置。只有在集成測試階段,我們才需要完整的 Docker Compose 環境。
2. Docker Compose:標準化部署
最終的 docker-compose.yml 將整個專案封裝為兩個可攜帶的服務:db (PostgreSQL) 和 web (FastAPI)。
這不僅簡化了開發環境的啟動,也讓專案具備了極高的可移植性——只需 docker compose up --build,即可在任何支持 Docker 的雲端平台(如 Render、Fly.io)上線,為後續的部署演示打下堅實基礎。
總結與未來展望
這個 FastAPI 爬蟲專案已經從一個基礎教學,蛻變為一個架構嚴謹、安全可靠、且具備自動化能力的後端服務。
它展示了作為資深工程師所需的關鍵技能:
非同步架構設計
連線池穩定性管理
生產級密鑰安全實踐
CI/CD 部署思維
沒有留言:
張貼留言