先講結論:政府資料 API 回 200,只代表「對方的伺服器今天還醒著」,不代表你拿到的是對的資料。
我這一個月在做 baodao-skill,一個用台灣政府公開資料做的 AI 代理技能集,目前 27 個 skill。回頭整理踩過的坑,最痛的幾次都不是 404、不是 500,而是一個漂漂亮亮的 200,裡面裝的卻是錯的。最後我給每個 fetch 都立了同一條規矩:
# 示意:每次抓資料,過四道門才算成功
def trust(resp):
assert resp.status == 200 # 門 1:通了(只是最低門檻)
assert looks_like_data(resp.body) # 門 2:長得像資料,不是擋人頁
assert is_fresh(resp.body) # 門 3:是這一期、這一版
assert is_complete(resp.body) # 門 4:沒被截斷、沒被切開
return parse(resp.body)
下面是四道門各自擋下的真案子,最後多一個:連檢查本身也會假成功。
台銀牌告匯率,我本來想做匯率查詢。2026-09-11 實測:HTTP 200,內容是 Challenge Validation,也就是 bot 防護的驗證頁。只看狀態碼,這支 API 完美通過,然後我的程式會把一頁 HTML 當成匯率去 parse。
對比之下,YouBike 統一 feed 被 Incapsula 擋住時回的是 503,加 885 bytes 的挑戰頁。講句公道話,503 至少誠實,CI 會紅,我會知道。最危險的牆,是會對你說 200 的那一種。台銀這個來源後來寫進 README 的「評估後否決」區塊,不上架。
發票領獎期限。 統一發票最新中獎號碼頁,2026-09-09 實測時,最新號碼已經是 115 年 05-06 月期,但頁面上的「領獎期間 115/06/06 至 115/09/07」是上一期(03-04 月)留下來的。如果照單全收,使用者問「我這張還能兌嗎」,我會自信滿滿地給他一個已經過期的期限。現在的規則是:領獎期間只引用和期別標籤在同一個區塊的文字;抓不到就用「開獎次月 6 日起算 3 個月」推算,並明講請回官網確認。
行事曆有兩個版本。 人事行政總處 114 年的行事曆在 114/10/20 更新過,加了教師節、光復節、行憲紀念日的休假與補假。拿到舊版 CSV,2025 年 9 月之後的假日它都會回你「要上班」。資料沒有錯,你只是拿到了過期的檔案。
號碼被 HTML 切開。 頭獎 07225810,在原始碼裡長這樣:072</span><span ...>25810。把標籤換成空白再抓數字,號碼會碎成 072、258、10 三塊,對獎全部歪掉。解法很土:標籤整個刪掉(換成空字串),讓碎片接回去,再抓 8 碼。
API 一次只給 1,000 筆。 醫院資料集的下載就卡在這裡:200、JSON 合法、格式漂亮,只是後面的醫院沒了。現在改成分頁抓,一共 38 頁。這種錯最陰,因為回來的每一筆都是對的,錯的是「少了」。
編碼和換行。 同一份行事曆,115、116 年是 UTF-8 帶 BOM,114 更新版卻是 Big5;而且是 CRLF 換行,所以 awk 比對最後一欄時,"補行上班" 永遠對不上 "補行上班\r"。先用 file 看編碼,先 sub(/\r$/,""),再談比對。
這一段是最丟臉的。我有個排程的 health-check,每天去戳所有文件裡的網址。9/20 到 9/25,它連續紅了六天,我沒發現。
紅燈連亮六天沒人看,就代表紅燈已經不等於「有事」了。健保署對 GitHub Actions 的 IP 擋得很兇,教育部統計處也是;這種「CI 根本驗不到」的紅燈,不該和「資料真的對不上」混成同一種紅。停車場數量、YouBike 站數這種每天在變的數字,用「完全相等」去驗也本來就不合理。修法是把紅燈留給真的問題:
同一輪還抓到一個更難堪的:發票解析器把特獎號碼報錯了。它「成功」跑完、回了一組號碼、格式也對,只是那組號碼不對。這種錯,只有拿真實開獎結果逐位對過才看得出來。
這套做法不是免費的:
下次你寫一個去抓政府資料的小工具,在 status == 200 後面多問三句:
這三句比任何重試機制都值錢。
另外,這些案例都來自 baodao-skill,MIT 授權,keyless、免登入,數字盡量附實測日期,歡迎直接拿去用或踩回來:
npx --yes skills add tahodev/baodao-skill --all -g
GitHub:https://github.com/tahodev/baodao-skill
我用 30 天鐵人賽把它一篇篇寫下來,每個坑的完整過程在這裡:https://ithelp.ithome.com.tw/users/20184082/ironman/9552