AI 協作實例

高中升學營隊搜尋平台製作說明

用 Claude Code 接力做出一個聚合三來源、涵蓋 82 所大學、158 個營隊的搜尋平台,含 6 段可複製的指令範本與 4 個踩雷紀錄

← 返回 AI 應用 看完整成品 →
關於本頁的提示詞

本頁的 6 段指令範本,標「原文」者為當下對話的逐字稿;其餘為事後整理的清晰版,讓學習者能直接複製改寫套用到自己的專案。整體製作流程則照實還原當時的順序與踩雷情形。

1

案例背景:高中生找升學營隊有多麻煩

高三上學期準備學測之餘,還要找升學體驗營隊「先去學群裡走一遍」是常見的做法。但實際操作會發現:全台 80 多所大學每年寒暑假總共開出 150 多個營隊(本平台目前涵蓋 82 所、158 個),資訊散在各大學官網、各系所臉書、各招生聚合站,沒有一個地方能一次看完並依條件篩選。

高中生與家長的 4 個痛點

1
資料散在各處:臺大資工營在系所網頁、清大電機營在招生網、政大新聞營在大學問,要看完得開 30-50 個分頁。沒有一個地方能依「學群」「地區」「報名截止日」一次篩。
2
聚合站不夠完整:現有的聚合平台(像 E-port 賦能港)資料整齊,但只涵蓋部分大學;許多私立大學、教育大學、技職科大開的營隊沒被收進去。
3
過期資料混雜:Google 搜尋常常跳出去年甚至更早的營隊頁面,點進去才發現報名截止日已過,白費時間。
4
學群分類不一致:每所學校自己定義學系所屬學群,「資訊工程」有人放「工程學群」、有人放「資訊學群」、有人歸「電機資訊」,跨校比較時很容易漏掉。

完成後的樣子

營隊搜尋平台介面:頂部依地區、學群、費用做標籤式篩選,中段顯示目前抓取進度,下方表格列出 158 筆營隊含截止日、學校、地區、科系、營隊名稱、日期、費用、連結
完成後的介面:依地區、學群、費用做標籤式篩選,158 筆營隊一頁可見,截止日近的會用紅字提醒
此案例的閱讀定位

本案例屬於第 7 章「Vibe Coding 入門」的工具型成果,跟案 3 醫療品質儀表板同類但技術堆疊更深(後端爬蟲 + AI 補抓 + 資料正規化 + 靜態打包)。學員讀完後可套用到:畢業旅行行程聚合、研究室招生資訊匯整、學會活動行事曆、任何「資料散在多個來源、需要統一視角」的場景。

2

開發六階段:從骨架到上線

整個製作流程約 2 天密集對話。核心策略是「先搭最小可動骨架,再一輪一輪找對的資料源,最後處理品質問題」。其中階段 3 與階段 6 是兩個關鍵轉折點(以粉色標出),其他四個階段是順著做的進度。

1
Claude Code
搭最小骨架

請 Claude Code 一次性生成 Flask(讓 Python 變成可瀏覽器打開的網頁)+ 假爬蟲 + Gemini 補抓佔位的雛形。16 個檔案、約 700 行,先讓「網頁打得開、按鈕按得動」。

2
WebSearch + Requests
找對的聚合來源

要求 Claude 補上「教育部 ColleGo!、學校聯合活動站」這類穩定來源。Claude 先用 WebSearch + WebFetch 收集,發現 WebFetch 給的是摘要看不到頁面結構,改用 requests + BeautifulSoup 直接抓 HTML 看實際 DOM。

3
關鍵轉折
發現 E-port JSON API

從聚合站 Lucker 的卡片屬性反推到 E-port,再從網頁原始碼 grep 出 /api/event/search/camp。一個 GET 拿回 194 筆完整 JSON。整個爬蟲重寫,Gemini 從「主力」降級成「補抓」,成本下降 90%。

4
Apple HIG
介面 + 靜態打包

要求做成 Apple HIG(蘋果的官方介面設計準則,強調可讀性與一致性)風格、能分享到教學平台。先 curl 確認 E-port API 允許跨域抓取(CORS,Cross-Origin Resource Sharing,瀏覽器的安全規則:規定「A 網站的網頁能不能呼叫 B 網站的 API」,開放才能讓單檔 HTML 直接打 API),於是設計成「Flask 版」與「standalone.html 單檔」雙軌,共用同一份 CSS。

5
除錯
修費用解析與去重

費用解析撞到「6,500 / 2,600 / 9,220」多價格字串會串成 NT$ 6500260092200 怪數字;去重閾值 0.7 對中文太鬆,把「現代醫學課程 A / B / D」誤合成一筆。兩處改用「強正規化後完全相同才合併」。

6
關鍵轉折
查證涵蓋率 + 補位

查證「資料是全部從 E-port 來嗎?」一問,跑對照表才發現只涵蓋 25%(21 / 82 所),不是先前估的 80%。坦承錯誤後找到「大學問 unews」補位,並啟用 Gemini Google Search 補抓 50 多所未涵蓋的學校。

跟「案 4 滿意度自動分類器」的對照

兩個案例都用到 Gemini API,但角色完全不同。對比一下,有助於選擇自己專案要走哪條路:

面向 案 4 · 滿意度自動分類器 案 5 · 升學營隊搜尋平台
運行環境Google Apps Script(綁在 Google 試算表上)本機 Flask + 可匯出單檔 standalone.html
Gemini 角色主力分類器,每筆資料都必跑一次補抓備胎,結構化 API 拿不到的資料才用,預設關閉
資料量級每天 N 筆滿意度回饋(漸進式累積)一次抓 158 筆營隊(批次刷新)
觸發方式事件驅動(表單送出自動觸發)使用者主動按「重新抓取」
技術門檻低:Apps Script 內建 Web IDE,不必裝 Python中:要會跑 Python 指令、裝套件、設環境變數
成本每筆 1-3 元 NTD(必跑)單純抓 E-port 免費;Gemini 補抓每輪約 3-15 元
適合場景事件流、訊息分類、自動警示多源資料聚合、定期刷新、結構化檢索
兩種類型怎麼選?

看你要做的事屬於「每來一筆就要處理」還是「定期批次刷新」。前者選案 4 的 Apps Script 路線,後者選案 5 的 Flask 路線。如果你的資料已經有公開 API,優先用 API,Gemini 留給「結構化 API 拿不到的盲點」,成本會差好幾倍。

3

關鍵指令範本:6 段可直接套用的對話

以下 6 段指令範本是可以直接複製改寫套到自己情境的版本。把「高中升學營隊」換成你的主題(例如「研究室招生資訊」「親子活動清單」「畢業旅行行程比價」),就能產出對應的搜尋平台。標原文的是當時的逐字稿。

指令 1 · 階段 1 搭骨架
一次性生成最小可動的雛形

不要分成「先做後端再做前端」這種多步驟,把整個專案的角色一次說清楚,讓 Claude Code 一次給出 16 個檔案的骨架,有什麼問題後面再改。

請幫我做一個「高中升學營隊搜尋平台」的最小可動版本。技術選型: 1. Flask 後端(Python),搭配 SSE 把「抓取進度」即時推到前端 2. 前端用單一 HTML 模板,顯示「篩選列 + 進度條 + 結果表格」 3. 爬蟲先用「假資料生成器」佔位,之後再換真資料 4. Gemini API 用 google-genai SDK + Google Search grounding 工具,作為「結構化 API 拿不到時的補抓備胎」 請建立完整目錄結構(scrapers / core / templates / static / data),寫好 requirements.txt,先確保「python app.py 後瀏覽器打得開、按鈕按得動、結果表格能渲染」。

關鍵心法:骨架不必對,但要「跑得動」。Claude 給的雛形通常不完美,但讓你看見整個專案的形狀,後面要修哪裡才有依據。

指令 2 · 階段 2 找資料源 · 原文
請 AI 先找穩定來源,而不是急著寫爬蟲

這句原話只有一行,但精準引導 AI 先做「資料探勘」,而不是直接動手寫爬蟲。AI 因此先去 WebSearch 確認哪些站點還活著、結構長什麼樣,而不是憑想像寫 selector。

穩定的聚合來源(例如教育部 ColleGo!、學校聯合活動站)能先幫我處理好嗎?

關鍵心法:對方是學習對象不是員工。先用一句話「請先處理 X」框定範圍,AI 會主動做探勘並把它能找到的選項報給你,而不是憑直覺寫程式碼。

指令 3 · 階段 3 發現 API · 重新整理
遇到 JavaScript 動態載入的網站,不要硬爬 DOM

原文是來回對話累積出來的,這裡整理成單一指令。重點是教 AI「看不到目標元素時不要慌,先找有沒有 JSON API」。

這個聚合站的列表頁我用 BeautifulSoup 抓不到 div.cours-bx,看來是 JavaScript 動態載入的。請改用以下策略: 1. 用 requests 拿原始 HTML(不執行 JS) 2. 用正規表達式或字串搜尋,在 inline <script> 區塊裡找有沒有像 /api/、/json/、event/search/ 這種端點 3. 找到後直接 curl 那個端點看回什麼,優先用結構化 JSON 4. 若該 API 的 CORS 設成允許跨域,純前端的 standalone.html 之後就能直接呼叫,不必經過後端 請列出你找到的所有候選 API 端點,我們再挑一個用。

關鍵心法:單頁式應用(SPA)的列表頁多半從 JSON API 載入。能用 API 就不要用 LLM 解析自由文字,API 拿到的欄位整齊、便宜、不會跟你「演繹」假資料。

指令 4 · 階段 4 介面要求 · 原文
用一句話同時要求「介面風格」與「部署方式」

老師原話三個重點壓縮在一句:HTML 介面、Apple HIG 風格、能分享到教學平台。這逼著 Claude 同時設計兩件事(本機 Flask 版 vs 可分享單檔版),並提早思考 CORS 問題。

若建一個 HTML 介面讓我使用,是否會很方便?而且設計感要 APPLE HIG。之後我也可以分享在我的教學平台裡。

關鍵心法:把「我之後要拿這個做什麼」一起講出來。「分享到教學平台」這 7 個字讓 Claude 立即想到「那需要單檔可移植版」,於是提早設計 standalone.html 的架構,而不是事後再拆出來。

指令 5 · 階段 6 查證來源 · 原文
看到結果好像不對,直接問「資料從哪來」

這是整個專案最有教學價值的提示詞。看似只是查證問題,但這一問讓 Claude 跑對照表發現自己之前估的「涵蓋率 80%」其實只有 25%,主動承認錯誤、更新 README、補位 unews。

這些搜尋出來的資料是全部從 E-port 賦能港上抓下來,還是也包括自己搜尋的?涵蓋率大概多少?可以列一張表給我看哪些學校有抓到、哪些沒有嗎?

關鍵心法:任何 AI 給你的百分比都該再用一段程式算給自己看。Claude 可能憑感覺說「涵蓋率約 80%」,實際跑對照表才知道只有 25%。三句話的查證能省下後面好幾個錯誤決策。

指令 6 · 階段 5+6 修 bug 與補位 · 重新整理
清楚描述症狀,別讓 AI 猜「你想要什麼」

修 bug 的指令要包含三件事:症狀、預期結果、目前的猜測。給太少資訊 AI 會亂改;給太多細節又會被誤導。中間值如下範例。

看 UI 上幾筆營隊的「費用」欄顯示成「NT$ 6500626006260092200」,顯然不對。我推測是因為這些營隊有多個價格(例如三堂合購 6,500、單堂 2,600、進階版 9,220),目前的解析把所有數字串接起來變成怪數字。 請: 1. 列出爬下來的原始 event_price 字串(取 3 個有問題的範例) 2. 若有 highest_price 這類純數字欄位,改優先取它 3. 若沒有,則只取字串中第一個出現的整數 4. 修完後跑一次完整 pipeline,確認所有費用都在合理範圍(0 到 50,000) 去重的部分:目前閾值 0.7 把「現代醫學課程 A」「現代醫學課程 B」「現代醫學課程 D」誤合成一筆。改為「強正規化(去數字 / 標點 / 空白)後完全相同才合併」,不要用相似度。

關鍵心法:修 bug 給三件事就夠:症狀 + 預期 + 猜測。猜測要說「我推測是因為...」而不是「請修這個 bug」,AI 有方向才修得準。

4

踩雷紀錄:Vibe Coding 不是一次到位

這個專案總共撞了 6 個雷,以下 4 個最有教學價值。看到別人怎麼踩雷、怎麼跳出來,比看到完美結果更有用。

1
WebFetch 看不到動態載入的內容
第一輪用 WebFetch 工具看聚合站,只拿到頁面摘要,看不到 div.cours-bx 這類關鍵元素的實際結構。原因是 WebFetch 會先用 AI 摘要再回傳,不是給原始 HTML。
修法:改用 requests.get(url).text 直接拿原始 HTML(不執行 JavaScript),然後用 grep 在 inline <script> 區塊裡找 API 端點。找到後直接打 API,跳過 DOM 解析。
2
費用解析串成 NT$ 6500626006260092200
E-port 回傳的 event_price 是人類可讀字串(像「(三堂合購)6,500 元 / (單堂)2,600 元 / (進階版)9,220 元」)。第一版解析直接把所有非數字字元剔除再串接,結果出現「6500260092200」這種把多個價格黏成一個天文數字的怪結果。
修法:優先用 E-port 提供的 highest_price 純數字欄位;沒有時改用正規表達式 re.search(r'\d+', text) 只抓字串中第一個出現的整數,不再做串接。
3
去重閾值 0.7 把不同梯次誤合
第一版去重用「Jaccard 相似度 > 0.7」(衡量兩段文字「字元有多少重疊」的演算法,值在 0 到 1 之間,1 = 完全相同,0.7 = 約 7 成字元相同)判斷是否為同一筆。看似合理,但中文短名容易出問題:「現代醫學課程 A / B / D」「7/2 行銷營 vs 8/22 行銷營」這些不同梯次的相似度都超過 0.7,結果三筆變一筆,丟掉真實資訊。
修法:改成「強正規化後完全相同才合併」。把數字、標點、空白全去掉,只剩中文骨架,完全一字不差才認定為同一筆。寧可漏合也不要誤合。
4
Gemini 補抓遇到 503 過載,整批白跑
Gemini 補抓有時會撞到 503 UNAVAILABLE(模型過載)。第一版的處理是「失敗就跳下批」,結果尖峰時段隨機掉 30-50% 的請求,9 批裡 8 批黑掉,Gemini 補抓幾乎沒抓到什麼東西。
Gemini 補抓 log:6 所學校批次連續成功 0 筆後,出現紅色 X 失敗訊息 503 UNAVAILABLE This model is currently experiencing high demand
同一輪內反覆出現 503 失敗,Gemini 補抓配額被白白消耗
修法:加「指數退避重試」(exponential backoff,失敗後等待時間每次加倍,給對方伺服器喘息空間):失敗等 1 秒再試,還失敗等 2 秒,再不行等 4 秒,最多 4 次。只對 503 / 429 / UNAVAILABLE 這類暫時性錯誤重試,認證 / 參數錯誤直接放棄(避免白白燒配額)。另外把 model 從 gemini-2.5-flash 換到 gemini-2.5-flash-lite,配額更寬鬆、不易撞過載。
加 retry 後的完整 pipeline log:正規化、去重、分類後共抓到 158 筆營隊
加 retry + 換 model 後,同一輪能穩穩抓到 158 筆營隊
為什麼把踩雷紀錄留下來?

學習 Vibe Coding 最容易誤解的就是「覺得別人都是一次寫對」。實情是這個專案 6 個雷裡有 4 個是 AI 直接犯的(WebFetch、費用串接、去重閾值、503 處理),2 個是設計判斷的盲點。把雷攤開來看,就會發現「踩雷 → 描述症狀 → AI 修正 → 驗證」這個循環本身就是 Vibe Coding 的核心技能。

5

成品 Demo:看完整成品長什麼樣

把六個階段跑完,得到的就是這份可以即時連 E-port API 刷新的「升學營隊搜尋平台」。點按鈕進去看完整版,瀏覽器右上「重新抓取」可以拿到最新報名狀態(連的是公開 API,不會用到老師的 Gemini 配額)。

開啟完整成品 · 高中升學營隊搜尋平台

成品的 6 大特色

三來源聚合

E-port API + unews 編輯精選 + Gemini Google Search,涵蓋 82 所大學、158 個營隊

多維篩選

地區(北中南東離島)、學群(10 學群)、費用(免費 / 收費)、學校名稱模糊搜尋

即時刷新

點「重新抓取」直接連 E-port 公開 API,拿到最新報名狀態,不必經過後端

自動過濾過期

報名截止日已過的營隊自動隱藏,不會看到去年的舊資料

深色模式

支援深色 / 淺色切換,跟隨系統設定;深夜查資料眼睛舒服

單檔可分享

整個應用打包成 101 KB 的 standalone.html,丟到任何靜態網站都能跑

實際使用建議

學生先用「地區」+「學群」兩個篩選縮小範圍,再依「截止日」排序看哪些還能報。若要看小型大學或科大的營隊,記得到頂部設定列把「全部來源」打開(位置在「深色模式」開關旁邊),免得 unews 編輯精選漏掉這些學校。如果想要最新一輪資料,每學期初會重新跑一次 pipeline 並上傳新版,日期看主頁卡片右上角的「最後修訂日」。

6

心得提醒:從這次製作學到什麼

把這次製作的關鍵心得整理成 5 條。即使你做的不是搜尋平台,以下原則套到任何「多來源資料聚合」的場景都通用:研究室招生資訊匯整、家族活動行事曆、社團迎新資源頁、產品比價工具。

動工前先把「資料源在哪」想清楚。爬蟲程式碼可以一晚寫好,但找到「對的、穩定的、結構化的」資料源往往要 2-3 天反覆探勘。先用 WebSearch 列出 5-7 個候選,挨個查活著沒、有沒有 API、CORS 開不開,再決定哪個當主力。
能用 API 就不要用 LLM 解析自由文字。Gemini 解析網頁字串 1 次 3-15 元,API 拿到結構化 JSON 是 0 元。找到 E-port API 後,Gemini 從「主力」降級為「補抓備胎」,整體成本下降 90%,正確率反而上升。
任何百分比都要再用一段程式算給自己看。AI 給的「涵蓋率約 80%」是憑感覺估的,跑對照表才發現只有 25%。三句話查證的價值勝過十段程式碼最佳化。
對暫時性錯誤要重試,對永久性錯誤不要重試。503 / 429 / UNAVAILABLE 重試會救活;認證錯誤、參數錯誤重試只會白燒配額。寫一個 helper 區分這兩類,套到所有 API 呼叫上,程式碼立刻穩很多。
動工前先想「最後要怎麼分享」。如果最終要部署到靜態網站(GitHub Pages、教學平台),從一開始就要考慮「能不能不依賴後端」「目標 API 的 CORS 設定如何」。事後拆出 standalone 版會痛苦,提早設計成「Flask 版 + 單檔版」雙軌就輕鬆。
這個案例在課程中的位置

本案例屬於第 7 章「Vibe Coding 入門」的工具型成果,跟案 3 醫療品質儀表板同類,但技術堆疊更深(後端 + 爬蟲 + AI 補抓 + 資料正規化 + 靜態打包)。建議讀完 ch07 §1-§6(心法 + Claude Code + 免費路徑 + Demo + 迭代除錯)後再回來看這個案例,會更能體會「為什麼要先搭骨架再迭代」「為什麼能用 API 就不用 LLM」這些原則的價值。