iT邦幫忙

0

HTTP 200 不等於成功:我替 27 個台灣政府資料 skill 抓出的五種「假成功」

  • 分享至 

  • xImage
  •  

先講結論:政府資料 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)

下面是四道門各自擋下的真案子,最後多一個:連檢查本身也會假成功。

門 2:200 的擋人頁

台銀牌告匯率,我本來想做匯率查詢。2026-09-11 實測:HTTP 200,內容是 Challenge Validation,也就是 bot 防護的驗證頁。只看狀態碼,這支 API 完美通過,然後我的程式會把一頁 HTML 當成匯率去 parse。

對比之下,YouBike 統一 feed 被 Incapsula 擋住時回的是 503,加 885 bytes 的挑戰頁。講句公道話,503 至少誠實,CI 會紅,我會知道。最危險的牆,是會對你說 200 的那一種。台銀這個來源後來寫進 README 的「評估後否決」區塊,不上架。

門 3:這一期、這一版

發票領獎期限。 統一發票最新中獎號碼頁,2026-09-09 實測時,最新號碼已經是 115 年 05-06 月期,但頁面上的「領獎期間 115/06/06 至 115/09/07」是上一期(03-04 月)留下來的。如果照單全收,使用者問「我這張還能兌嗎」,我會自信滿滿地給他一個已經過期的期限。現在的規則是:領獎期間只引用和期別標籤在同一個區塊的文字;抓不到就用「開獎次月 6 日起算 3 個月」推算,並明講請回官網確認。

行事曆有兩個版本。 人事行政總處 114 年的行事曆在 114/10/20 更新過,加了教師節、光復節、行憲紀念日的休假與補假。拿到舊版 CSV,2025 年 9 月之後的假日它都會回你「要上班」。資料沒有錯,你只是拿到了過期的檔案。

門 4:完整

號碼被 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 站數這種每天在變的數字,用「完全相等」去驗也本來就不合理。修法是把紅燈留給真的問題:

  • 健保署、教育部統計處這類擋 CI 的來源:WARN 並跳過(skip),文件寫明要從台灣 IP 手動驗
  • 變動快的數字:差距超過 ±5% 才警告,不再要求相等
  • 數字類的範例輸出,另外有一支重新驗算的腳本放進 CI,加上 17 個測試
  • 連線失敗或 5xx 先重試一次,再判定

同一輪還抓到一個更難堪的:發票解析器把特獎號碼報錯了。它「成功」跑完、回了一組號碼、格式也對,只是那組號碼不對。這種錯,只有拿真實開獎結果逐位對過才看得出來。

代價與限制

這套做法不是免費的:

  • 每個 skill 都多了幾十行檢查和說明,而且每個資料源的「長得像資料」都要各寫一次,沒有通用解法
  • WARN + skip 的意思是:CI 看不到的東西,就是看不到。健保署、統計處這幾條至今靠人工在台灣 IP 驗
  • 四道門擋不住「資料源頭本身就錯」。它只能確保我拿到的,是官方真的發出來的那份
  • 有三個想做的項目(國道即時路況、公共圖書館館藏、其他縣市垃圾車)因為來源連不上或驗不了,直接放進「保留」區。驗不到的來源,不上架

帶走的一條規則

下次你寫一個去抓政府資料的小工具,在 status == 200 後面多問三句:

  1. 這是資料,還是擋人頁?
  2. 這是這一期、這一版嗎?
  3. 數量對嗎?有沒有被截斷、被切開?

這三句比任何重試機制都值錢。

另外,這些案例都來自 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


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言