跳轉到

PokéAPI

PokéAPI 是全球知名的免費 REST API,提供完整的寶可夢世界資料庫,包含:

  • 寶可夢基本資料
  • 屬性(type)
  • 能力值(stats)
  • 技能(moves)
  • 進化鏈
  • 圖鑑編號
  • 圖片 sprites
  • 道具 items
  • 地區 region
  • 世代 generation

範例:終端機版寶可夢查詢器

import webbrowser

import requests


BASE_URL = "https://pokeapi.co/api/v2"


def get_json(url: str) -> dict:
    response = requests.get(url, timeout=10)

    if response.status_code == 404:
        raise ValueError("找不到這隻寶可夢,請確認英文名稱或圖鑑編號。")

    response.raise_for_status()
    return response.json()


def fetch_pokemon(keyword: str) -> dict:
    keyword = keyword.strip().lower()
    return get_json(f"{BASE_URL}/pokemon/{keyword}")


def fetch_species(species_url: str) -> dict:
    return get_json(species_url)


def fetch_evolution_chain(chain_url: str) -> dict:
    return get_json(chain_url)


def parse_evolution_chain(chain_data: dict) -> list[str]:
    result = []

    def walk(node: dict):
        result.append(node["species"]["name"])

        for next_node in node["evolves_to"]:
            walk(next_node)

    walk(chain_data["chain"])
    return result


def show_pokemon_info(pokemon: dict) -> None:
    species = fetch_species(pokemon["species"]["url"])
    chain_data = fetch_evolution_chain(species["evolution_chain"]["url"])
    evolution_names = parse_evolution_chain(chain_data)

    types = [item["type"]["name"] for item in pokemon["types"]]
    abilities = [item["ability"]["name"] for item in pokemon["abilities"]]
    stats = {
        item["stat"]["name"]: item["base_stat"]
        for item in pokemon["stats"]
    }
    moves = [item["move"]["name"] for item in pokemon["moves"][:20]]

    official_image = (
        pokemon["sprites"]
        ["other"]
        ["official-artwork"]
        ["front_default"]
    )

    print("\n" + "=" * 50)
    print("寶可夢完整資料")
    print("=" * 50)
    print(f"圖鑑編號:{pokemon['id']}")
    print(f"英文名稱:{pokemon['name']}")
    print(f"身高:{pokemon['height'] / 10} 公尺")
    print(f"體重:{pokemon['weight'] / 10} 公斤")
    print(f"基礎經驗值:{pokemon['base_experience']}")
    print(f"屬性:{', '.join(types)}")
    print(f"特性:{', '.join(abilities)}")
    print(f"世代:{species['generation']['name']}")
    print(f"顏色:{species['color']['name']}")
    print(f"棲息地:{species['habitat']['name'] if species['habitat'] else '無資料'}")
    print(f"進化鏈:{' -> '.join(evolution_names)}")

    print("\n能力值")
    for name, value in stats.items():
        print(f"- {name}: {value}")

    print("\n前 20 個技能")
    for move in moves:
        print(f"- {move}")

    print("\n圖片網址")
    print(official_image)

    if official_image:
        answer = input("\n是否要用瀏覽器開啟圖片?(y/n):").strip().lower()
        if answer == "y":
            webbrowser.open(official_image)
            print("已開啟瀏覽器。")


def main() -> None:
    print("=== PokéAPI 寶可夢查詢器 ===")
    print("可輸入英文名稱,例如 pikachu、charizard、bulbasaur")
    print("也可輸入圖鑑編號,例如 25、6、1")

    keyword = input("\n請輸入寶可夢名稱或圖鑑編號:")

    try:
        pokemon = fetch_pokemon(keyword)
        show_pokemon_info(pokemon)
    except ValueError as error:
        print("錯誤:", error)
    except requests.RequestException as error:
        print("API 連線失敗:", error)


if __name__ == "__main__":
    main()

範例:Streamlit 高完成度圖鑑儀表板

import requests
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import streamlit as st


### API 設定
BASE_URL = "https://pokeapi.co/api/v2" # PokéAPI 的基礎網址


### 寶可夢屬性顏色
TYPE_COLOR = {
    "normal": "#A8A77A",
    "fire": "#EE8130",
    "water": "#6390F0",
    "electric": "#F7D02C",
    "grass": "#7AC74C",
    "ice": "#96D9D6",
    "fighting": "#C22E28",
    "poison": "#A33EA1",
    "ground": "#E2BF65",
    "flying": "#A98FF3",
    "psychic": "#F95587",
    "bug": "#A6B91A",
    "rock": "#B6A136",
    "ghost": "#735797",
    "dragon": "#6F35FC",
    "dark": "#705746",
    "steel": "#B7B7CE",
    "fairy": "#D685AD",
}


### Streamlit 頁面設定
st.set_page_config(
    page_title="PokéAPI 寶可夢圖鑑", # 瀏覽器分頁標題
    page_icon="⚡", # 瀏覽器分頁圖示
    layout="wide" # 使用寬版頁面
)


### 自訂 CSS 樣式
st.markdown(
    """
    <style>
    .main-title {
        font-size: 46px;
        font-weight: 900;
        margin-bottom: 0px;
    }

    .subtitle {
        font-size: 18px;
        color: #666;
        margin-bottom: 24px;
    }

    .pokemon-card {
        padding: 24px;
        border-radius: 24px;
        background: linear-gradient(135deg, #fff7d6, #ffffff);
        box-shadow: 0 8px 30px rgba(0,0,0,0.08);
        border: 1px solid rgba(0,0,0,0.06);
    }

    .type-badge {
        display: inline-block;
        padding: 6px 14px;
        margin: 4px 6px 4px 0px;
        border-radius: 999px;
        color: white;
        font-weight: 800;
        font-size: 14px;
    }

    .small-note {
        color: #777;
        font-size: 14px;
    }
    </style>
    """,
    unsafe_allow_html=True # 允許 Streamlit 執行 HTML 與 CSS
)


### 建立 API GET Request 函式
def get_json(url: str) -> dict:
    """
    向指定 API 網址發送 GET Request,
    並將回傳的 JSON 資料轉成 Python dict。
    """

    response = requests.get(
        url,
        timeout=10 # API 最多等待 10 秒
    )

    if response.status_code == 404:
        raise ValueError("找不到資料") # 查無資料時產生自訂錯誤

    response.raise_for_status() # HTTP 狀態碼不是成功狀態時產生例外

    return response.json() # 將 JSON Response 轉成 Python dict


### 取得寶可夢基本資料
@st.cache_data(show_spinner=False)
def fetch_pokemon(keyword: str) -> dict:
    """
    根據寶可夢英文名稱或圖鑑編號,
    取得寶可夢的基本資料。
    """

    keyword = keyword.strip().lower() # 移除前後空白並統一轉成小寫

    return get_json(
        f"{BASE_URL}/pokemon/{keyword}"
    )


### 取得寶可夢物種資料
@st.cache_data(show_spinner=False)
def fetch_species(url: str) -> dict:
    """
    透過 Species API 網址,
    取得寶可夢的世代、顏色、棲息地等資料。
    """

    return get_json(url)


### 取得寶可夢進化鏈資料
@st.cache_data(show_spinner=False)
def fetch_evolution_chain(url: str) -> dict:
    """
    透過 Evolution Chain API 網址,
    取得寶可夢完整進化鏈資料。
    """

    return get_json(url)


### 整理寶可夢進化鏈
def parse_evolution_chain(
    chain_data: dict
) -> list[str]:

    result = [] # 儲存整理完成的寶可夢名稱

    def walk(node: dict):

        result.append(
            node["species"]["name"]
        ) # 將目前節點的寶可夢名稱加入結果

        for next_node in node["evolves_to"]:
            walk(next_node) # 使用遞迴繼續尋找下一階段進化

    walk(
        chain_data["chain"] # 從進化鏈第一個節點開始處理
    )

    return result


### 取得寶可夢官方圖片
def get_official_image(
    pokemon: dict
) -> str | None:

    return (
        pokemon["sprites"]
        ["other"]
        ["official-artwork"]
        ["front_default"]
    ) # 取得 PokéAPI 提供的官方 Artwork 圖片網址


### 建立寶可夢屬性標籤
def get_type_badges(
    types: list[str]
) -> str:

    html = "" # 儲存所有屬性標籤的 HTML

    for pokemon_type in types:

        color = TYPE_COLOR.get(
            pokemon_type,
            "#666" # 找不到對應屬性時使用灰色
        )

        html += (
            f"<span class='type-badge' "
            f"style='background:{color}'>"
            f"{pokemon_type.upper()}"
            f"</span>"
        )

    return html


### 建立能力值 DataFrame
def build_stats_df(
    pokemon: dict
) -> pd.DataFrame:

    rows = [] # 儲存每一項能力值資料

    for item in pokemon["stats"]:

        rows.append(
            {
                "能力": item["stat"]["name"],
                "數值": item["base_stat"]
            }
        )

    return pd.DataFrame(rows) # 將 list 轉成 Pandas DataFrame


### 建立技能 DataFrame
def build_moves_df(
    pokemon: dict
) -> pd.DataFrame:

    rows = [] # 儲存每一個技能的資料

    for item in pokemon["moves"]:

        move = item["move"]["name"] # 取得技能名稱

        version_details = (
            item["version_group_details"]
        ) # 取得不同版本中的技能學習資料

        learn_methods = sorted(
            {
                detail["move_learn_method"]["name"]
                for detail in version_details
            }
        ) # 使用 set 移除重複的技能學習方式,再進行排序

        rows.append(
            {
                "技能": move,
                "學習方式": ", ".join(
                    learn_methods[:3]
                ) # 最多顯示前三種學習方式
            }
        )

    return pd.DataFrame(rows)


### 建立能力值雷達圖
def render_radar_chart(
    stats_df: pd.DataFrame,
    pokemon_name: str
) -> None:

    fig = go.Figure() # 建立 Plotly Figure

    fig.add_trace(
        go.Scatterpolar(
            r=stats_df["數值"], # 雷達圖半徑使用能力值
            theta=stats_df["能力"], # 雷達圖各軸使用能力名稱
            fill="toself", # 填滿雷達圖內部區域
            name=pokemon_name
        )
    )

    max_value = max(
        160,
        int(
            stats_df["數值"].max()
        ) + 20
    ) # 最大刻度至少 160,若能力值更高則自動增加 20

    fig.update_layout(
        polar=dict(
            radialaxis=dict(
                visible=True,
                range=[
                    0,
                    max_value
                ]
            )
        ),
        showlegend=False, # 隱藏圖例
        height=430,
        margin=dict(
            l=40,
            r=40,
            t=40,
            b=40
        )
    )

    st.plotly_chart(
        fig,
        width="stretch" # 圖表寬度自動填滿容器
    )


### 建立能力值長條圖
def render_bar_chart(
    stats_df: pd.DataFrame
) -> None:

    fig = px.bar(
        stats_df,
        x="能力",
        y="數值",
        text="數值", # 在長條上顯示能力值
        title="能力值長條圖"
    )

    fig.update_traces(
        textposition="outside" # 將數值顯示在長條上方
    )

    max_value = max(
        160,
        int(
            stats_df["數值"].max()
        ) + 20
    ) # 設定 Y 軸最大值並保留額外顯示空間

    fig.update_layout(
        height=430,
        yaxis_range=[
            0,
            max_value
        ]
    )

    st.plotly_chart(
        fig,
        width="stretch"
    )


### 顯示網站標題
st.markdown(
    "<div class='main-title'>⚡ PokéAPI 寶可夢圖鑑儀表板</div>",
    unsafe_allow_html=True
)

st.markdown(
    """
    <div class='subtitle'>
    輸入英文名稱或圖鑑編號,即時查詢屬性、能力值、技能、進化鏈與官方圖片。
    </div>
    """,
    unsafe_allow_html=True
)


### 建立 Sidebar 查詢設定
with st.sidebar:

    st.header(
        "查詢設定"
    )

    keyword = st.text_input(
        "寶可夢英文名稱或圖鑑編號",
        value="pikachu", # 預設查詢皮卡丘
        placeholder="例如 pikachu、charizard、25"
    )

    show_all_moves = st.checkbox(
        "顯示全部技能",
        value=False # 預設只顯示部分技能
    )

    search_button = st.button(
        "開始查詢",
        type="primary",
        width="stretch"
    )

    st.divider()

    st.caption(
        "建議學生先查:"
        "pikachu、eevee、charizard、mewtwo、lucario"
    )


### 建立 Session State 儲存查詢條件
# Streamlit 操作元件時會重新執行整份程式
# 使用 session_state 可以保存使用者目前查詢的寶可夢
if "search_keyword" not in st.session_state:
    st.session_state.search_keyword = "pikachu" # 第一次開啟網站預設查詢 pikachu


### 按下查詢按鈕後更新查詢條件
if search_button:

    if keyword.strip():

        st.session_state.search_keyword = (
            keyword.strip()
        ) # 移除輸入內容前後的空白


search_keyword = (
    st.session_state.search_keyword
) # 取得目前要查詢的寶可夢


### 呼叫 API 取得寶可夢資料
try:

    with st.spinner(
        f"正在查詢 {search_keyword}..."
    ):

        pokemon = fetch_pokemon(
            search_keyword
        ) # 取得寶可夢基本資料

        species = fetch_species(
            pokemon["species"]["url"]
        ) # 使用基本資料中的網址取得 Species 資料

        evolution_chain = (
            fetch_evolution_chain(
                species["evolution_chain"]["url"]
            )
        ) # 使用 Species 資料中的網址取得進化鏈


    ### 整理寶可夢基本資料
    pokemon_name = (
        pokemon["name"]
    )

    pokemon_id = (
        pokemon["id"]
    )

    image_url = (
        get_official_image(
            pokemon
        )
    )


    ### 整理寶可夢屬性
    types = [
        item["type"]["name"]
        for item in pokemon["types"]
    ]


    ### 整理寶可夢特性
    abilities = [
        item["ability"]["name"]
        for item in pokemon["abilities"]
    ]


    ### 整理寶可夢能力值
    stats_df = (
        build_stats_df(
            pokemon
        )
    )


    ### 整理寶可夢技能
    moves_df = (
        build_moves_df(
            pokemon
        )
    )


    ### 整理寶可夢進化鏈
    evolution_names = (
        parse_evolution_chain(
            evolution_chain
        )
    )


    ### 建立基本資訊左右兩欄
    left_col, right_col = (
        st.columns(
            [1, 2] # 左欄比例 1、右欄比例 2
        )
    )


    ### 左側顯示寶可夢圖片與基本資料
    with left_col:

        st.markdown(
            "<div class='pokemon-card'>",
            unsafe_allow_html=True
        )

        if image_url:

            st.image(
                image_url,
                width="stretch"
            )

        st.markdown(
            f"## #{pokemon_id} "
            f"{pokemon_name.title()}"
        )

        st.markdown(
            get_type_badges(
                types
            ),
            unsafe_allow_html=True
        )

        st.markdown(
            f"""
            <p class='small-note'>
            世代:{species['generation']['name']}
            顏色:{species['color']['name']}
            </p>
            """,
            unsafe_allow_html=True
        )

        st.markdown(
            "</div>",
            unsafe_allow_html=True
        )


    ### 右側顯示寶可夢基本數據
    with right_col:

        metric_cols = (
            st.columns(4)
        )

        metric_cols[0].metric(
            "身高",
            f"{pokemon['height'] / 10} m" # API 的身高單位為 0.1 公尺,因此除以 10
        )

        metric_cols[1].metric(
            "體重",
            f"{pokemon['weight'] / 10} kg" # API 的體重單位為 0.1 公斤,因此除以 10
        )

        metric_cols[2].metric(
            "基礎經驗",
            pokemon["base_experience"]
        )

        metric_cols[3].metric(
            "技能數",
            len(
                pokemon["moves"]
            )
        )

        st.markdown(
            "### 特性"
        )

        st.write(
            "、".join(
                abilities
            )
        ) # 使用頓號串接所有特性名稱

        st.markdown(
            "### 進化鏈"
        )

        st.success(
            " → ".join(
                evolution_names
            )
        ) # 使用箭頭串接完整進化鏈


    ### 建立功能分頁
    tab1, tab2, tab3, tab4 = (
        st.tabs(
            [
                "能力分析",
                "技能資料庫",
                "原始資料摘要",
                "教學引導"
            ]
        )
    )


    ### Tab 1:能力分析
    with tab1:

        chart_col1, chart_col2 = (
            st.columns(2)
        )

        with chart_col1:

            render_radar_chart(
                stats_df,
                pokemon_name
            )

        with chart_col2:

            render_bar_chart(
                stats_df
            )


        ### 計算能力值摘要
        total_score = int(
            stats_df[
                "數值"
            ].sum()
        ) # 加總所有基礎能力值


        strongest = (
            stats_df
            .sort_values(
                "數值",
                ascending=False
            )
            .iloc[0]
        ) # 依照能力值由高到低排序,取得最高能力


        weakest = (
            stats_df
            .sort_values(
                "數值",
                ascending=True
            )
            .iloc[0]
        ) # 依照能力值由低到高排序,取得最低能力


        ### 顯示能力值摘要
        insight_col1, insight_col2, insight_col3 = (
            st.columns(3)
        )

        insight_col1.metric(
            "總能力值",
            total_score
        )

        insight_col2.metric(
            "最高能力",
            f"{strongest['能力']} "
            f"({strongest['數值']})"
        )

        insight_col3.metric(
            "最低能力",
            f"{weakest['能力']} "
            f"({weakest['數值']})"
        )


    ### Tab 2:技能資料庫
    with tab2:

        st.markdown(
            "### 技能列表"
        )

        if show_all_moves:

            st.dataframe(
                moves_df,
                width="stretch",
                height=500,
                hide_index=True # 不顯示 DataFrame index
            )

        else:

            st.info(
                "目前只顯示前 30 筆。"
                "若要看完整技能,"
                "請到左側勾選「顯示全部技能」。"
            )

            st.dataframe(
                moves_df.head(30), # 只取前 30 筆技能
                width="stretch",
                height=500,
                hide_index=True
            )


    ### Tab 3:原始資料摘要
    with tab3:

        habitat = (
            species["habitat"]["name"]
            if species["habitat"]
            else "無資料"
        ) # 如果 API 沒有棲息地資料則顯示「無資料」


        ### 建立寶可夢資料摘要
        summary = {
            "圖鑑編號":
                pokemon_id,

            "名稱":
                pokemon_name,

            "屬性":
                ", ".join(
                    types
                ),

            "特性":
                ", ".join(
                    abilities
                ),

            "身高(m)":
                pokemon["height"] / 10,

            "體重(kg)":
                pokemon["weight"] / 10,

            "世代":
                species[
                    "generation"
                ][
                    "name"
                ],

            "棲息地":
                habitat,

            "進化鏈":
                " -> ".join(
                    evolution_names
                ),

            "圖片網址":
                image_url
        }


        ### 使用 JSON 格式顯示資料摘要
        st.json(
            summary
        )


    ### Tab 4:教學引導
    with tab4:

        st.markdown(
            """
            ### 可以引導學生觀察的問題

            這個範例很適合拿來教 REST API,因為學生可以直接看到:
            一隻寶可夢的完整資料,其實不是從單一 API 一次取得。

            - 基本資料來自 `/pokemon/{name}`
            - 世代、顏色、棲息地來自 Species API
            - 進化鏈來自 Evolution Chain API
            - 圖片來自 API 回傳的圖片網址
            - 技能資料可以再透過 Pandas 整理
            - 能力值可以再利用 Plotly 視覺化

            因此整個流程會變成:

            `輸入資料 → 發送 Request → JSON → 整理資料 → DataFrame → 圖表 → Streamlit`

            這樣比單純把 JSON 印出來,更容易理解 API 在實際專案裡怎麼使用。
            """
        )


### 處理找不到寶可夢的錯誤
except ValueError:

    st.error(
        "找不到這隻寶可夢。"
        "請輸入英文名稱,例如 pikachu,"
        "或輸入圖鑑編號,例如 25。"
    )


### 處理 API 連線錯誤
except requests.RequestException as error:

    st.error(
        f"API 連線失敗:{error}"
    )


### 處理其他程式錯誤
except Exception as error:

    st.error(
        f"程式發生錯誤:{error}"
    )