cormort/procurement-crawler-py
0
🕷️ 政府採購網 - 雲端多功能工作站
查詢台灣政府電子採購網的標案、批次取得完整公告欄位、匯出 Excel。 資料來源以 g0v 社群維護的 [pcc-api.openfun.app](https://pcc-api.openfun.app) 為主 (不碰官網,因此沒有驗證碼問題),需要時仍可切回官網爬取模式。
四種模式
為什麼這樣改(實測依據)
這一版的重點是減少請求數與尊重對方 API 的流量政策,不是把併發開大:
- N 次請求 → 1 次:
searchbytitle等搜尋端點支援columns[]。 實測searchbytitle?query=農田水利署&columns[]=採購資料:預算金額…一次就回傳 99 筆(含指定詳細欄位)。原本的寫法是「勾幾筆就打幾次/api/tender」。 - API 有速率限制,而且很緊:後端
APILimiter.php的規則是 10 秒內 >10 次或60 秒內 >60 次就回429。實測連續打 15 次會被擋到幾乎全部 429 (而且會連帶把後續幾十秒的請求一起拖下水)。因此: - 客戶端自我限速在 8 次/10 秒、50 次/60 秒(留安全邊際,不靠「被擋再退避」);
- 被 429 時冷卻 12 秒再重試(12 秒足以讓 10 秒窗口清空,又不會多等一整個長窗口);
- 支援 Bearer Token(官方文件明載:帶 token 可解除流量限制)—— 需要大量抓取時請自行申請。
- 86 欄只拿 7 欄:
/api/tender一筆回傳 86 個欄位,舊版只映射 7 個,UI 卻寫著 「80+ 完整欄位」。新版「取得完整欄位」會把 86 欄全部攤平進 Excel(並逐筆快取,重跑不重抓)。 - 官網模式同一次搜尋打兩次:舊版先用
safe_crawl_with_backoff打一次拿狀態, 轉頭又打第二次拿 HTML —— 等於把觸發驗證碼的機率與等待時間翻倍。新版一次取得 HTML 後直接解析。 - Streamlit 每次互動都會重跑整份腳本,舊版因此在每次點擊/換頁都重打一次搜尋 API。 新版對搜尋結果加上 15 分鐘 TTL 快取,同一組條件不會重複打。
- 錯誤處理:舊版遇到非 JSON 回應會在
resp.json()丟出JSONDecodeError—— 那不是RequestException,不會被except接住,整個頁面會直接掛掉。 新版統一包成ApiError並在 UI 顯示原因。 - 部署:
requirements.txt全部釘死版本(浮動版本=某天突然建不起來)、 基底映像python:3.9-slim→python:3.12-slim(3.9 已 EOL)、改成非 root(uid 1000)執行、COPY . .改為只複製必要檔案。
實測數字
使用方式
- 左側選「📡 開放資料 API」→ 輸入關鍵字 → 🔎 API 搜尋。 列表在同一次請求就帶回預算金額與截止投標,不必再逐筆抓。
- 需要完整公告內容時,勾選列 → ⚡ 取得完整欄位(86 欄),完成後可下載 Excel (按鈕上會先顯示預估秒數)。
- 大量查詢建議在左側填入 data.openfun.tw 的 Bearer Token(可解除流量限制); 留空也能用,只是會自我限速。
資料保存
⚠️ HF Space 的容器檔案系統是暫時性的:重新建置或重啟就會清空 crawled_history.txt。 側邊欄的「案號去重資料庫」提供匯出/匯入 CSV,請自行備份重要清單。
開發與測試
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
python3 selftest.py # 離線測試(限速器、429 退避、欄位攤平、錯誤處理、官網解析)
streamlit run app.pypcc_core.py 放的是網路、限速、欄位處理等純邏輯(不 import streamlit),所以可以直接測試; app.py 只負責 UI 與流程。
資料來源與授權
- 資料:政府電子採購網(行政院公共工程委員會),API 由 g0v 社群 (openfunltd/pcc.g0v.ronny.tw)維護。 使用時請遵循原始資料來源的著作權聲明;商業利用請走官方資料集或 M2M 授權。
- 本程式:Apache-2.0。
