Files
pokemon-radar-api/SUMMARY.md
Timmy f7ef105436 init: Redis-backed Pokémon 雷達 API
- radar_fetcher.py:呼叫 Function Server 抓 twpkinfo.com 資料後寫入 Redis (TTL 300s)
- pokemon_radar_api.py:FastAPI /scan,從 Redis 讀取並依 request 即時二次篩選;Redis 不可用時回 200 空結果,不再 500
- pokemon_location_fetcher.py:舊版 CLI,仍由 fetch_and_upload.sh 使用
- 文件:README (參考手冊)、QUICKSTART (操作指南)、SUMMARY (故事線)、CLAUDE.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 09:13:59 +08:00

93 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SUMMARY
這份文件講**為什麼長這樣**:設計脈絡、演進、取捨、目前還沒處理乾淨的地方。要查指令看 `README.md`,要跑起來看 `QUICKSTART.md`
## 一句話
把一個只能「給座標、即時回寶可夢清單」的網站twpkinfo.com變成「前端 / n8n 能隨時打 API 拿最新資料」的服務。
## 為什麼需要這個專案
twpkinfo.com 的雷達頁(`/ipoke.aspx`)有完整的物種 / IV / CP / 技能 / 座標資訊,但:
1. 沒有 API資料只在網頁 DOM 裡
2. 有人機驗證與前端黑影遮罩,直接 `curl` 拿不到
3. 前端(地圖標點)和 n8n通知高 IV 寶可夢)都需要結構化資料
所以這個專案做兩件事:**把網頁變成 JSON**、**把 JSON 包成 API**。
## 架構演進
這條 pipeline 不是一次設計出來的,是三個階段堆疊出來的,中間每個階段的產物都還在 repo 裡。
### 第一階段CLI 一把梭
`pokemon_location_fetcher.py` + `twpk_radar.mjs` + `fetch_and_upload.sh`
- `twpk_radar.mjs` 送到 Function Server一台 `browserless` / 類似的 browser automation 服務)執行,繞 stealth、等黑影消退、解析資料回傳 JSON
- `pokemon_location_fetcher.py` 是 Python CLI讀 MJS、用 **regex 注入座標和 state.json**、POST 到 Function Server、對回來的資料做 IV%/remaining/species 篩選,最後輸出 `pokemon_data.json` + `pokemon_data.csv` + `screenshot.png`
- `fetch_and_upload.sh``flock` 避免重入,跑完後 `scp` 把 JSON 丟到另一台 caddy 主機,由前端靜態讀取
**為什麼 regex 注入**Function Server 的契約是「你丟一段 JS 過來,我幫你跑在無頭瀏覽器」。參數化只能在字串層做 — 所以 `twpk_radar.mjs` 裡的 `const TARGET = {...};``const DO_SCREENSHOT = ...;``globalThis.__POKE_STATE__ = ...;` 三個錨點變成 Python / JS 之間的介面。改 MJS 時若動到這些行的形狀Python regex 會失效但不會報錯,只會靜默用錯誤參數跑。
這階段到今天仍在生產跑,因為 caddy 前端是靜態檔,沒理由為了它多引入 Redis。
### 第二階段FastAPI v1已不存在
`pokemon_radar_api.py_BACKUP_20251208225502`(已清除)是第一版 API每個 `/scan` request 都呼叫一次 Function Server。
問題:
- Function Server 單次 browser automation 要 30-60 秒,前端打一次就等一次
- 多人同時打會超過 Function Server 的 concurrency
- 資料源本身是頁面抓取3 秒打一次跟 3 分鐘打一次拿到的結果幾乎一樣(寶可夢生成間隔長)
### 第三階段Redis backed API現行
把 fetch 和 serve 拆開:
- `radar_fetcher.py` 當 writercron 定期跑,結果 `SETEX` 到 RedisTTL 300s
- `pokemon_radar_api.py` 當 reader每個 request 從 Redis 讀快取資料,依 request 參數(`min_iv` / `perfect_only` / `species`)做二次篩選後回傳
- API 的 `ScanRequest` 保留了 `lat / lng / zoom / shot` 欄位**但忽略它們**,純粹相容舊前端不必改 client
Reader 端沒有 breaking change、Writer 端可以獨立調速Function Server 壓力從「每 request 一次」降到「每 TTL 一次」。
## 目前還沒處理乾淨的地方
### 兩條 pipeline 共存
`radar_fetcher.py`(新)和 `pokemon_location_fetcher.py`(舊)都能抓資料,**篩選邏輯各寫了一份**`_postprocess` vs `PokemonRadarClient.postprocess`)。修改篩選規則要兩邊同步改。
合併不是單純 refactor舊 CLI 還在 `fetch_and_upload.sh` 裡給前端生靜態檔,短期不會下掉。
### API 重用舊 client 的 postprocess
`pokemon_radar_api.py` 為了不重寫一次篩選,`import PokemonRadarClient` 然後 `_filter_helper = PokemonRadarClient(url="", token="")`,只用它的 `postprocess`。這個 workaround 讓新 API 與舊 CLI 有了非必要的耦合。長期方向應該把 `_postprocess` 抽成獨立模組,兩邊共用。
### `.env` 的環境耦合
`radar_fetcher.py` 預設 `REDIS_HOST=localhost``pokemon_radar_api.py``reader.py` 預設 `192.168.42.211` — 因為 fetcher 歷史上跑在 Redis 本機API 跑在遠端。實務上**一定要透過 `.env` 顯式設定**,不要依賴程式內預設。
### `state.json` 的脆弱性
整條 pipeline 最常壞的點是 state 過期。過期時 Function Server 會正常 200但回來的資料是空陣列或登入頁 DOM — pipeline 不會炸,只會靜默吐 0 筆。`radar_fetcher._postprocess` 有個診斷分支:當 `raw_items` 非空但篩完為 0 時會 log `remaining_min / remaining_max / perfect` 分佈,優先看這個判斷是過濾太嚴還是資料本身有問題。
## 最近處理過的問題
### `/scan` 在 Redis 不可用時會 500
`get_data_from_redis()` 原本只 `try/except json.JSONDecodeError`,沒處理 `redis.exceptions.RedisError`。結果:
- Redis key 不存在 → 照原本設計回空陣列OK
- Redis 連不上 → 例外沒接FastAPI 直接 500
修法:`get_data_from_redis()` 改回傳 `(data, ttl, err)` 三元組,整段 Redis 操作包 `try/except RedisError``scan()``err` 切換 `source``redis (unreachable)` 並把錯誤訊息放進 `debug.note`。三種情況連線成功、key 不存在、連不上)都回 200前端不會炸。
### 備份檔清理
repo 曾保留多份 `*_BACKUP_<timestamp>` 檔案當作版本保險,但有 git 就沒必要。已清除。
## 執行環境現況
- **本機開發**Docker 起 `redis:7-alpine``127.0.0.1:6379``.env` 指本機、無密碼。Function Server 走 VPN 或同網段連 `192.168.42.124`
- **生產**Redis 在 `192.168.42.211`有密碼、API 在某台 8008 port、`fetch_and_upload.sh` 由 cron 觸發並 scp 到 `192.168.42.104` 的 caddy。兩條 pipeline 並存。