尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
追蹤 Polymarket 上台灣各縣市首長選舉賭盤的即時賠率與每一筆成交明細, 包含下單錢包地址、顯示名稱、買賣方向、金額與時間。頂端頁簽切換縣市。 兩者都接同一個 GitHub repo 自動部署,Actions 每次 commit 快照後兩邊都會更新。 網址帶錨點可直接開到指定縣市,例如。 Polymarket 封鎖台灣 IP。 這是整套架構的前提。 直接用瀏覽器打 polymarket.com 的 API,在台灣會連不上;掛 VPN 才通。 所以資料來源做成兩條腿,網頁會自己選能走的那條。 而且要用到的兩個 API 穩定度差很多,所以它們是分開判斷的: Gamma API 的請求送得到伺服器,但 Cloudflare 有時回傳的內容不帶 access-control-allow-origin 標頭,瀏覽器就擋下來了。 (用 curl 測反而會成功,因為拿到的是快取版本——所以不要用 curl 判斷瀏覽器能不能連。) 關鍵是成交明細抓得到就算即時——賠率抓不到只是換個來源,不該讓整頁降級。 中間那種混合狀態一定會在畫面上講清楚哪個是舊的、哪個是新的, 不會讓人誤把舊資料當成當下行情。檢視完整 README ↗
追蹤 Polymarket 上台灣各縣市首長選舉賭盤的即時賠率與每一筆成交明細, 包含下單錢包地址、顯示名稱、買賣方向、金額與時間。頂端頁簽切換縣市。
目前涵蓋 19 個縣市,全部 2026-11-28 到期:
| 縣市 | Event ID | 網址錨點 |
|---|---|---|
| 基隆市 | 848410 | #keelung |
| 臺北市 | 848341 | #taipei |
| 新北市 | 848347 | #new-taipei |
| 桃園市 | 848370 | #taoyuan |
| 高雄市 | 848409 | #kaohsiung |
| 新竹市 | 848435 | #hsinchu-city |
| 新竹縣 | 848418 | #hsinchu-county |
| 臺中市 | 848407 | #taichung |
| 臺南市 | 848408 | #tainan |
| 彰化縣 | 848438 | #changhua |
| 南投縣 | 848447 | #nantou |
| 雲林縣 | 848448 | #yunlin |
| 嘉義縣 | 848449 | #chiayi-county |
| 屏東縣 | 848479 | #pingtung |
| 花蓮縣 | 848480 | #hualien |
| 臺東縣 | 848481 | #taitung |
| 宜蘭縣 | 848417 | #yilan |
| 嘉義市 | 848453 | #chiayi-city |
| 苗栗縣 | 848436 | #miaoli |
| 平台 | 網址 | 備註 |
|---|---|---|
| Vercel | https://keelung-polymarket-tracker.vercel.app/ | 主要,Ches 的內網擋 github.io |
| GitHub Pages | https://kcpb-ches.github.io/keelung-polymarket-tracker/ | 備用 |
兩者都接同一個 GitHub repo 自動部署,Actions 每次 commit 快照後兩邊都會更新。
網址帶錨點可直接開到指定縣市,例如 https://keelung-polymarket-tracker.vercel.app/#kaohsiung。
Polymarket 封鎖台灣 IP。 這是整套架構的前提。
直接用瀏覽器打 polymarket.com 的 API,在台灣會連不上;掛 VPN 才通。
所以資料來源做成兩條腿,網頁會自己選能走的那條。
而且要用到的兩個 API 穩定度差很多,所以它們是分開判斷的:
| API | 用途 | 從瀏覽器直連 |
|---|---|---|
| Data API | 每一筆成交明細(核心資料) | 穩定 |
| Gamma API | 盤口賠率、成交量、流動性 | 時好時壞 |
Gamma API 的請求送得到伺服器,但 Cloudflare 有時回傳的內容不帶
access-control-allow-origin 標頭,瀏覽器就擋下來了。
(用 curl 測反而會成功,因為拿到的是快取版本——所以不要用 curl 判斷瀏覽器能不能連。)
因此實際行為有三種:
開啟網頁 → 同時試「成交明細」和「盤口賠率」(各自重試 3 次)
│
├─ 兩個都成功 ─────────→ 🟢 即時直連,無提示
│
├─ 成交成功、盤口失敗 ─→ 🟢 即時直連 + 一行說明:
│ 「賠率是 X 時間的快照值,成交紀錄是當下最新」
│
└─ 成交也失敗 ─────────→ 🟡 快照資料,提示連 VPN
關鍵是成交明細抓得到就算即時——賠率抓不到只是換個來源,不該讓整頁降級。 中間那種混合狀態一定會在畫面上講清楚哪個是舊的、哪個是新的, 不會讓人誤把舊資料當成當下行情。
快照由 scripts/snapshot.py 產生。它可以在你自己電腦跑(要開 VPN),
也可以掛在 GitHub Actions 上跑——GitHub 的機器在美國,不受台灣封鎖影響,
等於免費幫你顧一台永遠連得到 Polymarket 的機器。
keelung_polymarket/
├── index.html 前端入口
├── app.js 全部前端邏輯(抓取、篩選、三種視圖、CSV)
├── style.css 樣式(深色為主,可切淺色)
├── data-<eventId>.json ← 各縣市最新快照,前端連不到 API 時讀這個
│
├── scripts/
│ ├── snapshot.py 抓取腳本(純標準庫,不用 pip install)
│ ├── should_commit.py 判斷這一輪值不值得留一個 commit
│ └── backfill_new_wallets.py 一次性:回填 new-wallets.json 的歷史資料
│
├── cron-worker/ Cloudflare Worker:每 5 分鐘觸發 Actions(見該資料夾 README)
│
├── seen_wallets.json ← 已知錢包名冊,新錢包偵測的依據
├── new-wallets.json ← 新進錢包完整明細,「新進錢包通知」頁籤讀這個
│
├── snapshots/
│ └── YYYY-MM-DD.json 每天一份賠率與統計存檔(不含逐筆明細)
│
└── .github/workflows/snapshot.yml GitHub Actions 排程(每小時)+ 寄信
cd keelung_polymarket
python3 -m http.server 8765
然後開 http://localhost:8765/index.html。
不能直接用滑鼠雙擊
index.html開啟。用file://開的話瀏覽器會擋掉讀取data.json的請求,一定要透過上面的小伺服器。
python3 scripts/snapshot.py
會同時更新 data.json 和 snapshots/當天日期.json。
.github/workflows/snapshot.yml 每小時抓一次快照並 commit| 模式 | 用途 |
|---|---|
| 📋 表格 | 每筆成交一列,可依任一欄位排序、分頁,適合逐筆核對 |
| 📰 動態 | 卡片式時間軸,適合快速掃過最近有誰在下注 |
| 👛 錢包 | 依錢包彙總:累計筆數、買進/賣出金額、淨投入、押注標的、首次進場時間 |
錢包視圖會把開盤 24 小時內就進場的錢包標記出來——早期交易者可能有資訊優勢,
是值得優先看的對象。這個時間窗可以在 app.js 的 EARLY_WINDOW_H 調整。
所有錢包地址都可以點擊,三個入口:Polygonscan(鏈上紀錄)、 Polymarket(個人頁)、Relay(跨鏈轉帳,回溯 USDC.e 的來源鏈)。 滑鼠移到地址上會出現複製按鈕。表格視圖最右欄也有 Relay 連結。
頁籤列上跟各縣市同一層,有一個 🔔 新進錢包通知。它是所有縣市的彙整, 列出每個錢包第一次在某個縣市下注的那一筆交易——跟寄出去的通知信同一份資料, 差別只在信會過期、這一頁可以隨時回頭查。
資料來自 new-wallets.json,由 Actions 每輪產生,所以沒有 VPN 也看得到。
可以依縣市、日期、交易者名稱或錢包地址篩選,也能只看跨縣市錢包,並匯出 CSV。
頂端三個數字分別是:符合目前篩選條件的筆數、最近 24 小時的新進錢包、 以及其中跨縣市的錢包數。
同一個錢包在不同縣市各算一筆(它在每個縣市都有自己的「首航」), 所以列數會比錢包數多。
new-wallets.json 採合併寫入:只補新的,不動既有紀錄。
首筆交易明細是從 data-*.json 撈的,而那些檔有 MAX_OFFSET 上限。
等成交筆數成長到截斷,早期錢包的首筆交易會從 data-*.json 消失;
若每輪重建,這份名單會跟著愈縮愈短。合併寫入則是記過就不會掉。
歷史資料由 scripts/backfill_new_wallets.py 一次性補齊(2026-09-21 執行,回填 1,447 筆),
之後日常維護交給 snapshot.py,不需要再跑。
每次抓完資料後,比對 seen_wallets.json 名冊,出現從未見過的錢包就寄信。
十個縣市合併成一封信,依縣市分段,不會一次收到八封。
信裡列出每個新錢包的首筆動作(押哪位候選人、買賣、Yes/No、股數、成交價、金額), 以及 Polygonscan、Polymarket、Relay 三個直達連結。
主旨會標註筆數與涉及幾個縣市,有跨縣市錢包時加註「N 個跨縣市」, 否則標註金額逾 $50 的筆數,讓人從手機通知欄就能判斷輕重。
同一個錢包在多個縣市都下注時,信裡會用黃色標籤標出「也出現在 ○○、○○」。 這是單一縣市看不出來的模式,也是最值得優先檢視的訊號。
目前約 380 個錢包裡有 136 個跨縣市,其中有錢包同時押了 9 個縣市。
| 名稱 | 內容 | 必填 |
|---|---|---|
MAIL_USERNAME | 寄件的 Gmail 地址 | 是 |
MAIL_PASSWORD | Google 應用程式密碼(16 碼,不是登入密碼) | 是 |
MAIL_TO | 額外收件地址,多個用逗號分隔 | 否,不填就只寄給自己 |
MAIL_TO 是「加上去」而不是「取代掉」——MAIL_USERNAME 那個信箱一定收得到,
填了 MAIL_TO 就是再多寄給那些人。收件地址放 secret 而不是寫死在 workflow 裡,
是因為這個 repo 是公開的,寫死會被爬蟲撈去發垃圾信。
應用程式密碼在 https://myaccount.google.com/apppasswords 產生(需先開兩步驟驗證), 只能用來寄信,隨時可撤銷。設定方式:
gh secret set MAIL_USERNAME --body "你的信箱@gmail.com"
gh secret set MAIL_PASSWORD # 執行後貼上 16 碼
到 Actions → snapshot → Run workflow,勾選「寄一封測試信」。
它會拿最近進場的錢包組一封標示為測試的信寄出,內容格式與真實通知完全相同—— 測試信長什麼樣,真信就長什麼樣。不會動到名冊,可以放心重複測試。 換信箱、或懷疑通知壞掉時都用這個驗證。
seen_wallets.json 依 event id 分組,每個縣市各有自己的名冊:
{ "updated_at": "...", "count": 533,
"events": { "848410": { "0xabc…": {"first_seen": "...", "name": "..."} }, ... } }
不從 data-*.json 反推「看過哪些錢包」,是因為那些檔有 MAX_OFFSET(10000 筆)上限。
將來筆數成長到會截斷時,早期的錢包會從資料裡消失,被誤判成新錢包而爆寄一整批信。
三種情況會靜默建檔、不寄信,避免一次收到上百封:
要把某個縣市重設為「全部已知」,把它在 events 底下那一段刪掉再跑一次即可。
所有縣市與候選人中文名都集中在兩個地方,要新增縣市就各加一筆,其餘程式碼完全不用動:
app.js 最上面的 EVENTS 陣列(前端顯示用,含政黨配色)scripts/snapshot.py 最上面的 EVENTS 清單(抓取與通知信用){
id: 848410, slug: 'keelung', city: '基隆市', office: '市長',
candidates: {
'Hsieh Kuo-liang': { zh: '謝國樑', party: 'kmt', partyZh: '國民黨' },
'Tung Tzu-wei': { zh: '童子瑋', party: 'dpp', partyZh: '民進黨' },
},
},
Polymarket 的 zh-hant 介面是機器翻譯,而且有錯:
Chang San-cheng(張善政),
中文介面卻顯示「鄭文燦」——兩者是不同人。
目前依 Ches 判斷採用「鄭文燦」,程式碼裡有註記,若日後確認有誤改一行即可。所以中文名一律以設定表為準,人工對照 API 回傳的英文拼音(groupItemTitle)。
party 只填確定的,沒把握的一律留空(顯示為中性灰)。
寧可不標也不要標錯——標錯會影響判讀。
同理,中譯沒把握的候選人乾脆不放進對照表,前端會顯示英文拼音原文, 不會把人漏掉。以下幾位待確認,確認後補進兩張設定表即可:
| 縣市 | 英文拼音 | 備註 |
|---|---|---|
| 彰化縣 | Wei Ping-cheng、Hung Jung-chang | |
| 南投縣 | Wen Shih-cheng | |
| 嘉義縣 | Wu Pin-jui、Tsai Sung-yi | |
| 花蓮縣 | Yu Shu-chen、Yeh Yao-hui、Lo Pei-chin | Yu Shu-chen 目前領先 |
| 臺東縣 | Wu Hsiu-hua、Li Wu Ying-chih | Wu Hsiu-hua 目前領先 |
每個 event 底下有 29~33 個 market,但真候選人只有 2~6 位,
其餘是 Candidate A ~ Candidate Z 的佔位盤(成交量 0、賠率固定 0.5)。
前端預設隱藏沒人下注的佔位盤,只在下方標註還有幾個。
之後若有新人參選、佔位盤被改成真名,把名字加進設定表就會自動顯示;
沒加的話會顯示英文原文,不會被吃掉。
| API | 用途 | 認證 |
|---|---|---|
gamma-api.polymarket.com/events?id=<eventId> | 盤口賠率、成交量、流動性 | 不需要 |
data-api.polymarket.com/trades?eventId=<eventId>&takerOnly=true | 每一筆成交明細 | 不需要 |
takerOnly=true 很重要:鏈上每筆撮合都有 maker、taker 兩方,
不加這個參數會把同一筆交易算成兩筆,成交數直接膨脹一倍。
資料判讀要注意的兩件事:
name / pseudonym 是使用者在 Polymarket 上的公開暱稱,不是實名。
部分帳號的 name 是系統自動填的錢包地址字串(例如 0xfBd8...-1779347618060),
程式會辨識出來並改用 pseudonym 或標示為「未具名」。offset 上限是 10000。目前最大的高雄盤約 482 筆,離上限還很遠;
真的成長到那個量級時,snapshot.py 要改用 start/end 時間窗分段抓。:30 以避開整點前後的排隊高峰。
要真正即時就用直連模式(開 VPN),網頁會自己去抓最新的。app.js 或 style.css 要記得把 index.html 裡的 ?v= 數字加一,
否則瀏覽器會一直用快取的舊版本。
網頁載入時 console 會印出 [選舉賭盤監控] app.js v8,
版號跟你剛改的對不上就是中了快取,按 Cmd+Shift+R 強制重新載入。| 什麼 | 多久一次 | 改哪裡 |
|---|---|---|
| 網頁自動重抓 | 5 分鐘 | app.js 的 REFRESH_INTERVAL |
| Actions 抓快照+檢查新錢包 | 5 分鐘(由 Cloudflare Worker 觸發) | cron-worker/wrangler.toml 的 crons |
2026-09-22 實測:把 cron 從每 3 小時改成每小時,平均間隔幾乎沒變。
| 預期間隔 | 實際平均間隔 | |
|---|---|---|
每 3 小時(30 */3 * * *) | 3 小時 | 4.6 小時 |
每 1 小時(30 * * * *) | 1 小時 | 4.3 小時 |
改成每小時後的 23 小時內只跑了 4 班,執行率 17%。
結論是 GitHub 給這個 repo 的排程執行大約就是一天 5~6 次,跟 cron 寫多密無關。 這是 GitHub 對免費/公開 repo 排程的節流,官方文件只寫「schedule 事件可能因負載而延遲」, 沒說會直接砍掉這麼多班。
還是維持每小時而不調回 3 小時,是因為反正配額一樣, 提出的請求多一點、被分配到的機會也多一點,沒有壞處。
節流只限制排程,不限制手動觸發——按 Run workflow 都是秒級啟動。
所以真正的解法是讓 GitHub 以外的服務定時呼叫 workflow_dispatch API。
cron-worker/ 就是這件事:一支 Cloudflare Worker,每 5 分鐘打一次那支 API。
設定方式見 cron-worker/README.md。
snapshot.yml 裡的排程保留不動,當作 Worker 掛掉時的備援。
一天跑 288 次,如果每次都 commit,兩個月就是一萬七千個 commit,git pull 會愈來愈慢。
而這些 commit 絕大多數沒有意義——這個盤一天約 50 筆成交,288 個快照裡大概有 250 個
除了時間戳之外內容一模一樣。
所以 scripts/should_commit.py 會先判斷「這個盤真的有新動作嗎」:
| 會觸發 commit | |
|---|---|
| 任何縣市有新成交 | ✅ |
| 錢包名冊出現新錢包 | ✅ |
| 新進錢包名單有新項目 | ✅ |
時間戳(fetched_at/updated_at) | ❌ |
| 盤口賠率、流動性、總成交量 | ❌ |
錢包的全站預測次數(traded) | ❌ |
下面三類為什麼不算:它們會自己一直跳動,跟「有沒有人在這個盤下注」無關——
賠率會因做市商調單而變,traded 會因為那個錢包在 Polymarket 其他市場
(跟台灣選舉無關)交易而變。讓它們觸發 commit 等於回到每輪都 commit。
代價:沒有新成交的那段時間,快照裡的賠率會停在最後一次 commit 的數字。 但沒有成交時賠率本來就不太會動,而且網頁連得到 API 時是直接讀即時賠率, 快照只是備援。
想看當下最新不必等排程:到 Actions → snapshot → Run workflow 手動觸發一次,
約一分半後網頁就是新的(按右上角 ↻ 重載)。終端機也可以直接下
gh workflow run snapshot.yml。
概念參考自 tw-election-2026-tracker (全國地方選舉政黨盤),沿用了它的深色介面風格與雙視圖設計, 但抓取邏輯、分組維度(政黨 → 候選人)、資料來源策略均為重寫。
主要差異:原專案靠一台 VPS 常駐 Python 程式抓資料再 git push, 單點故障且需要自行維運(其線上資料已停更);這裡改為前端直連優先、 GitHub Actions 快照備援,不需要任何伺服器。