一題講師示範、五題學員自選。所有資料源都在 2026/8/21 實際打過 API 驗證, 包含它們各自會坑你的地方。
這件事必須先講清楚,因為它不是偏好問題,是限制問題。臺北市資料大平臺的 API 不允許瀏覽器直接讀取——這是實測結果,不是猜測。
在 https://example.com 的頁面上執行 fetch(),結果如下。
| 資料源 | 瀏覽器直接 fetch | 實測回應 | 意思是 |
|---|---|---|---|
| YouBike 2.0 即時 tcgbusfs.blob… |
可以 | 200,1,794 筆,0.99 秒 | 單檔 HTML 就能做完整個專案 |
| data.taipei 全部 API AED/事故/人口/垃圾 |
被擋 | TypeError: Failed to fetch |
抓取這段必須在 Colab 做 |
fetch(url, {mode:'no-cors'}) 會拿到
{type:"opaque", status:0}——連得上、有回應,只是瀏覽器不讓 JavaScript 讀。
伺服器沒有回 Access-Control-Allow-Origin,就是這個結果。
這個區別值得在課堂上示範一次,因為學員(和 AI)都會把它誤判成「網址壞了」。
找出流程中不可逆的那一刀在小專案上的樣子。
五個階段,每一階段都有一個給 AI 的指令、一個驗收動作、一個預期會踩到的坑。 最後一個階段是刻意讓它失敗的——那是整場的樞紐。
示範題_YouBike儀表板.html)讓學員看 30 秒,再關掉。
知道終點長什麼樣,中間的每一步才有意義。
目標不是拿到資料,是逼 AI 先去看資料長什麼樣,而不是憑記憶猜欄位。
sna sarea act Quantity
available_rent_bikes available_return_bikes
latitude longitude。
如果 AI 直接交給你一個「已經做好分析和圖表」的版本,代表它跳過了這一步。 退回重做——這個退回動作本身就是示範。
fs、require)。指令要明講「單檔 HTML,用瀏覽器打開」。sbi / tot 這些欄位——那是 YouBike 1.0 的舊欄位,2.0 已經改名。這是最好的「AI 記憶不等於現況」實例。注意我沒有給它任何欄位說明,我要它自己去看。因為 API 文件常常跟實際回傳不一樣——待會你們就會親眼看到一個。
這裡有一個 AI 幾乎必踩的坑。不要提醒它,讓它踩下去,再帶學員抓出來。
act 是字串 "0" / "1",不是數字。
而 JavaScript 裡字串 "0" 是 truthy——
AI 若寫成 filter(s => s.act) 或 Boolean(s.act),
一站都濾不掉,而且不會噴任何錯誤。
指令第 1 條刻意要求「先印出值跟型別」,就是為了讓它先看見。
最重要的一分鐘:當場把 s.act === "1" 改成 s.act,
讓大家看數字跳回 1,794。程式一樣跑得動、一樣不噴錯,只是答案錯了。
sarea 印出來有 13 個值,其中一個是 「臺大公館校區」——它不是行政區。
問學員:該不該把它當成一區?
沒有標準答案。重點是這個決定必須由人來做,而 AI 不會主動問你。
AI 沒有寫錯。它寫的程式跑得動、不噴錯、看起來完全正常。它只是做了一件跟你想的不一樣的事。 這就是為什麼「它跑起來了」不等於「它做對了」。
示範一件 AI 不能替你做的事:決定定義。
如果 AI 沒回答問題就直接開始寫程式,退回。這也是示範的一部分。
這題沒有標準答案,但有標準流程:先知道差多少,再決定用哪個。 AI 可以幫你算差多少,不能幫你決定要用哪個——因為那取決於你想回答什麼問題。
出畫面的一段,也是示範「回歸」的一段——加功能最容易弄壞既有的東西。
這就是三種免工具驗證裡的「回歸」:每次改完,回頭看前一步的答案有沒有變。
#map{height:460px}display:none 的區塊裡 → 顯示後要呼叫 map.invalidateSize(),否則只渲染一角。讓學員在你手上先撞一次,他們待會自己做的時候才不會卡死在這裡。
Failed to fetch 或紅色的 CORS 錯誤訊息。
AI 寫的程式碼完全正確。同一段邏輯搬到 Python 裡就跑得動。 這不是 AI 的錯——它看不到你的執行環境。
這就是你們等一下自己選題會遇到的第一道牆。data.taipei 上的 API 全部都這樣。 所以你們的題目,「抓資料」那一段要在 Colab 做。我現在示範怎麼三行解決。
接續示範:Colab 三行版
import pandas as pd, requests
url = "https://data.taipei/api/v1/dataset/438c61ad-24f6-4e54-a1cc-e2cfe0e7051e?scope=resourceAquire&limit=1000&offset=0"
df = pd.DataFrame(requests.get(url).json()["result"]["results"])
df.head()
然後指出:這樣只拿到 1000 筆,全部有 2,729 筆。 分頁的通用寫法(五題都會用到,直接給學員):
import requests, pandas as pd, time
def fetch_all(rid, page=1000):
"""data.taipei 通用分頁抓取。limit 上限實測為 1000。"""
rows, offset = [], 0
while True:
url = (f"https://data.taipei/api/v1/dataset/{rid}"
f"?scope=resourceAquire&limit={page}&offset={offset}")
r = requests.get(url, timeout=30).json()["result"]
rows += r["results"]
print(f"已抓 {len(rows)} / {r['count']}")
if len(r["results"]) < page or len(rows) >= r["count"]:
break
offset += page
time.sleep(0.2)
return pd.DataFrame(rows)
df = fetch_all("438c61ad-24f6-4e54-a1cc-e2cfe0e7051e")
df.to_csv("aed.csv", index=False, encoding="utf-8-sig") # sig 才不會在 Excel 開出亂碼
已抓 2729 / 2729。
實測抓 22,762 筆的事故資料需要 23 頁、約 6.5 秒。
難度從一顆星到四顆星。每一題的資料都實際抓過,筆數和欄位名是實測值不是文件值。 第 1 題是保底題——最沒把握的組給這個,一定收得掉。
哪個月丟掉最多傢具和彈簧床?這個規律十年來有變過嗎?
| 資料集 | 筆數 | 期間 | 欄位 |
|---|---|---|---|
| 臺北市巨大垃圾統計 | 120 | 逐月,到民國 115 年 7 月 | 年度 月份 總計(噸) 傢具(噸) 彈簧床(噸) 腳踏車(噸) 樹枝(噸) |
(噸),不是半形。AI 寫 df['總計(噸)'] 會 KeyError。"1002")。不轉型別就畫圖,長條會照字串排序。年度 是民國年(115),畫時間軸前要決定怎麼處理。四年來人口往哪一區移動?哪一區的高齡比例上升最快?
| 資料集 | 筆數 | 期間 | 關鍵欄位 |
|---|---|---|---|
| 各行政區人口數及戶數 | 715 | 111/1 – 115/7 | 年份 月份 行政區 戶數 人口數_合計數量 人口數_男數量 人口數_女數量 |
| 各里人口數按年齡分 | 60,501 | 112 年起 | 區域別 性別 總計 0歲數量 … 100歲以上數量 |
行政區 欄位的值有三種空白變體:" 總計 "、"總計"、" 總計"。
不做 .str.strip() 直接 group by,13 個區會變成 39 組。
而且圖上不會報錯,只會多出一堆看起來很像的區名。AI 幾乎不會主動處理這件事。
哪一區的 AED 密度最低?分佈跟人口對得上嗎?
| 資料集 | 筆數 | 座標 | 欄位 |
|---|---|---|---|
| 臺北市 AED 設置地點 | 2,729 | WGS84,實測全部有效 | 場所名稱 場所地址 區域代碼 緯度 經度 場所分類 場所類型 aed放置地點 |
區域代碼(如 63000030)。
實測共 12 種,要自己從 場所地址 或另建對照表還原區名——這一題的主要工作量在這裡。AED放置地點,API 實際回的是小寫 aed放置地點。
永遠以實際回傳的 key 為準。全市即時的空站與滿站在哪裡?一整天下來,車子是怎麼在城市裡流動的?
| 資料集 | 筆數 | 更新 | 關鍵欄位 |
|---|---|---|---|
| YouBike2.0 即時資訊 | 1,794 | 每分鐘 | sna sarea act Quantity available_rent_bikes available_return_bikes latitude longitude mday |
這題是示範題。留給學員時要求他們做出示範沒做過的東西,例如加時間累積、或改成回答「該從哪一站調度到哪一站」。
哪個路口最危險?死亡事故有沒有集中在特定時段、天候或路型?
| 資料集 | 筆數 | 年度 | 關鍵欄位 |
|---|---|---|---|
| 道路交通事故斑點圖 | 22,762 | 114 年(108–114 各一支) | 發生時間 處理別 肇事地點 座標-x 座標-y |
| 死傷交通事故資料 | 22,762 | 114 年(101–114 各一支) | 發生時-hours 天候 光線 速限-速度限制 道路型態 年齡 飲酒情形 … |
-(U+FF0D),不是半形 -;而且英文被轉成小寫(metadata 寫 座標-X,API 實回 座標-x)。手打幾乎一定打錯,要複製貼上。處理別 1(A1 死亡)57 筆、2(A2)22,705 筆學員做出來的數字對不上這些,就是清理出了問題。這是全部五題裡唯一有標準答案可以對的。
這幾件事本身就是很好的教材:不是每個掛在開放資料平台上的資料集都有 API 可以抓。
?scope=resourceAquire 只對平台真的入庫的表格型資源有效。若資源只是一個外部連結(CSV、SHP、Excel),
API 會回空陣列 [] 而不是報錯——AI 拿到空陣列常會自己編一組假資料填進去。
實測 9 個 rid 有 6 個回空。先用這支看清楚再決定:
https://data.taipei/api/frontstage/tpeod/dataset.view?id={資料集頁的 id}
→ 看每個 resource 的 has_datastore 是不是 true
公車、捷運 OD 這類資料集,API 回的是一筆記錄,裡面的「介接網址」才是真資料。 捷運分時 OD 的 API 回的是 115 筆每月 CSV 的清單,單月檔超過 30 MB。這也是這兩題沒被排進五題的原因。
全形破折號、全形括號、英文自動轉小寫、值的前後空白——這四種在五題裡各出現過。 metadata 和實際回傳不一致是常態,而 AI 傾向相信 metadata。
| 想做的題目 | 結果 | 原因 |
|---|---|---|
| 空氣品質實際測值 | 不可用 | data.taipei 三個候選資料集都是 has_datastore:false。要做請改用環境部
data.moenv.gov.tw。平台上只有 17 個測站的點位,沒有測值。 |
| 垃圾清運量/資源回收率 | 不可用 | 「一般廢棄物處理工作」34 個欄位完全符合需求,但只有 Excel 沒有 API。故改用巨大垃圾統計。 |
| 停車場即時剩餘車位 | 未採用 | 網址存活但格式是 octet-stream,且座標是 TWD97 二度分帶要轉換。要用請上課前先自行確認編碼。 |
| 公車站牌/動態 | 未採用 | 實際資料全是 .gz 壓縮檔,多一層解壓對初學者是不必要的門檻。 |
2026 年 8 月 21 日,透過瀏覽器實際發出請求驗證。
筆數與欄位名為當日實際回傳值。limit 上限實測為 1000(填 2000 仍只回 1000)。
上課前建議重跑一次分頁函式確認資料仍在。