2026年1月30日 星期五

Gemini CLI 學習筆記 : 郵遞區號查詢小幫手專案 (三)

在前兩篇的查詢 6 碼郵遞區號的測試中, 我們只利用提示詞與 Gemini CLI 對話便能讓其生成程式碼, 透過 API 正確取得給定地址的郵遞區號. 本篇則要改用語境檔 GEMINI.md 來達成, 它是寫程式用的 PRD (專案需求文件檔), 關於語境檔 GEMINI.md 參考 :


以下測試使用 uv 工具來管理專案與 Python 版本, 關於 uv 參考 : 


本系列全部測試文章索引參考 :



首先開啟 PS 視窗, 用 uv init 建立一個名為 postal-helper-proj3 的 Python 專案資料夾 : 

PS D:\gemini> uv init postal-helper-proj3   
Initialized project `postal-helper-proj3` at `D:\gemini\postal-helper-proj3`

切換到專案資料夾檢視 uv 建立的檔案 : 

PS D:\gemini> cd postal-helper-proj3    
PS D:\gemini\postal-helper-proj3> dir   

    目錄: D:\gemini\postal-helper-proj3

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----       2026/1/30  上午 11:13            109 .gitignore
-a----       2026/1/30  上午 11:13              5 .python-version
-a----       2026/1/30  上午 11:13             97 main.py
-a----       2026/1/30  上午 11:13            165 pyproject.toml
-a----       2026/1/30  上午 11:13              0 README.md

然後開啟記事本編輯 GEMINI.md, 輸入如下內容 (可請 Gemini 產生) :

# 專案設定:台灣郵遞區號查詢工具 (Taiwan ZipCode CLI)

## 1. 角色與語氣
- **角色**: Python 腳本開發者 (Python Scripter)。
- **語氣**: 輕鬆、直觀、實用導向。
- **語言**: 繁體中文 (Traditional Chinese)。

## 2. 技術堆疊與工具 (Tech Stack)
- **專案管理**: **uv** (由 Astral 開發的超快 Python 工具)。
  - 安裝套件請用: `uv add <package>`
  - 執行程式請用: `uv run <script.py>`
  - **禁止使用**: 傳統的 `pip install` 或手動建立 `venv` 指令。
- **語言版本**: Python 3.12+
- **關鍵套件**: 
  - `requests` (若需爬蟲或呼叫 API)
  - `beautifulsoup4` (若需解析 HTML)
  - `typer` 或 `argparse` (用於處理 CLI 指令參數)

## 3. 專案目標 (Project Goal)
開發一個簡單的 Command Line Interface (CLI) 工具,功能如下:
1.  **輸入**: 使用者輸入「縣市」+「行政區」 (例如:`高雄市三民區`) 或 「完整地址」。
2.  **處理**: 程式透過網路查詢 (中華郵政/開放資料 API) 或 內建字典檔。
3.  **輸出**: 回傳對應的 **3+3 郵遞區號** (優先) 或 **3 碼地區號**。

## 4. 程式碼規範
- **結構**: 保持簡單,盡量將功能封裝在 `main.py` 模組中。
- **錯誤處理**: 如果使用者輸入的地址不存在或打錯字,請回傳友善的中文提示 (例如:「找不到此地區,請確認輸入格式」),而非 Python Traceback。
- **註解**: 程式碼中請加入簡單的中文註解,說明資料來源或邏輯。

## 5. 互動範例 (Expected Behavior)
使用者在終端機執行:
```bash
uv run main.py "台北市信義區市府路1號"

以 utf-8 編碼存檔後, 在此專案目錄下輸入 gemini 啟動 Gemini CLI : 




可見 Gemini CLI 一啟動就會先讀取 GEMINI.md 的內容作為對話中持續性的系統提示, 我先問它是否經由此語境檔已完全了解專案目標, 並要求它先提供執行計畫再做 :

> 請問你了解此專案了嗎? 請不要開始寫程式,先告訴我你的計劃

它回答如下, 看來並無問題, 輸入 ok, 好的, 或 go ahead 等授權它撰寫程式 : 





程式撰寫完畢要求寫入 main.py :





要求授權用 uv 執行測試 :




測試 "台北市信義區市府路1號" 結果正確, 要求再授權測試 "高雄市三民區" : 




測試成功取回 3 碼郵遞區號, 要求授權刪除虛擬環境中沒用到的套件 : 




完成專案 : 




最後開啟 main.py 將程式碼抄錄如下 : 

import typer
import requests
import json
import sys
from typing import Optional

app = typer.Typer(help="台灣郵遞區號查詢工具 (Taiwan ZipCode CLI)")

# 3碼郵遞區號備援字典 (部分範例,實務上可擴充或改用完整資料檔)
THREE_DIGIT_ZIPCODES = {
    "台北市": {
        "中正區": "100", "大同區": "103", "中山區": "104", "松山區": "105", 
        "大安區": "106", "萬華區": "108", "信義區": "110", "士林區": "111", 
        "北投區": "112", "內湖區": "114", "南港區": "115", "文山區": "116"
    },
    "高雄市": {
        "新興區": "800", "前金區": "801", "苓雅區": "802", "鹽埕區": "803", 
        "鼓山區": "804", "旗津區": "805", "前鎮區": "806", "三民區": "807", 
        "楠梓區": "811", "小港區": "812", "左營區": "813", "仁武區": "814",
        "大社區": "815", "岡山區": "820", "路竹區": "821"
    },
    "台中市": {
        "中區": "400", "東區": "401", "南區": "402", "西區": "403", "北區": "404",
        "北屯區": "406", "西屯區": "407", "南屯區": "408"
    }
}

def query_zip5_api(address: str) -> Optional[str]:
    """
    使用 zip5.5432.tw API 查詢 3+3 郵遞區號
    """
    try:
        # 使用知名的 zip5 服務 (非官方但穩定)
        url = "https://zip5.5432.tw/zip5json.py"
        params = {"adrs": address}
        response = requests.get(url, params=params, timeout=5)
        response.raise_for_status()
        
        # 該 API 回傳格式為 JSON
        data = response.json()
        
        # 檢查是否有回傳 zipcode
        if data and "zipcode" in data and data["zipcode"]:
            return data["zipcode"]
            
    except requests.RequestException:
        # 網路連線錯誤,靜默失敗,交給備援機制
        return None
    except json.JSONDecodeError:
        return None
    
    return None

def lookup_local_3digit(address: str) -> Optional[str]:
    """
    從內建字典查詢 3 碼郵遞區號
    """
    for city, districts in THREE_DIGIT_ZIPCODES.items():
        if city in address:
            for district, code in districts.items():
                if district in address:
                    return code
    return None

@app.command()
def main(
    address: str = typer.Argument(..., help="請輸入完整地址或縣市行政區,例如:'台北市信義區市府路1號' 或 '高雄市三民區'"),
    show_json: bool = typer.Option(False, "--json", "-j", help="以 JSON 格式輸出")
):
    """
    查詢台灣郵遞區號 (優先嘗試 3+3 碼,失敗則回傳 3 碼)
    """
    result_code = None
    source = "未知"

    # 1. 嘗試網路查詢 (3+3)
    print(f"正在查詢 '{address}' 的郵遞區號...", file=sys.stderr)
    result_code = query_zip5_api(address)
    
    if result_code:
        source = "網路查詢 (3+3碼)"
    else:
        # 2. 失敗則嘗試內建字典 (3碼)
        result_code = lookup_local_3digit(address)
        if result_code:
            source = "內建字典 (3碼地區號)"

    # 3. 輸出結果
    if result_code:
        if show_json:
            print(json.dumps({"address": address, "zipcode": result_code, "source": source}, ensure_ascii=False))
        else:
            print(f"--------------------------------")
            print(f"查詢結果: {result_code}")
            print(f"資料來源: {source}")
            print(f"--------------------------------")
    else:
        print(f"❌ 找不到此地區,請確認輸入格式 (例如:縣市 + 行政區 + 路名)。")
        sys.exit(1)

if __name__ == "__main__":
    app()

可見 Gemini CLI 這次採取自己的方式查詢郵遞區號, 如果在 GEMINI.md 中有限制它使用 zip5.5432.tw 的 API 查詢的話, 實作方式應該會跟前面第一篇測試文章的實作結果差不多. 

沒有留言 :