Python 網路爬蟲 空氣品質指標 ( AQI ) --python TKinter
從政府資料開放平臺裡,搜尋「空氣品質指標」,就能開啟空氣品質指標 ( AQI ) 的資料頁面,資料有提供 JSON 格式與 CSV 格式,這個範例中會使用 JSON 格式,點擊對應的按鈕,就可以開啟對應的 JSON API 內容。
- 空氣品質指標 ( AQI ) :https://data.gov.tw/dataset/40448
- JSON API:檔案連結

{
"fields": [
{
"id": "sitename",
"type": "text",
"info": {
"label": "測站名稱"
}
},
{
"id": "county",
"type": "text",
"info": {
"label": "縣市"
}
},
{
"id": "aqi",
"type": "text",
"info": {
"label": "空氣品質指標"
}
},
{
"id": "pollutant",
"type": "text",
"info": {
"label": "空氣污染指標物"
}
},
{
"id": "status",
"type": "text",
"info": {
"label": "狀態"
}
},
{
"id": "so2",
"type": "text",
"info": {
"label": "二氧化硫(ppb)"
}
},
{
"id": "co",
"type": "text",
"info": {
"label": "一氧化碳(ppm)"
}
},
{
"id": "o3",
"type": "text",
"info": {
"label": "臭氧(ppb)"
}
},
{
"id": "o3_8hr",
"type": "text",
"info": {
"label": "臭氧8小時移動平均(ppb)"
}
},
{
"id": "pm10",
"type": "text",
"info": {
"label": "懸浮微粒(μg\/m3)"
}
},
{
"id": "pm2.5",
"type": "text",
"info": {
"label": "細懸浮微粒(μg\/m3)"
}
},
{
"id": "no2",
"type": "text",
"info": {
"label": "二氧化氮(ppb)"
}
},
{
"id": "nox",
"type": "text",
"info": {
"label": "氮氧化物(ppb)"
}
},
{
"id": "no",
"type": "text",
"info": {
"label": "一氧化氮(ppb)"
}
},
{
"id": "wind_speed",
"type": "text",
"info": {
"label": "風速(m\/sec)"
}
},
{
"id": "wind_direc",
"type": "text",
"info": {
"label": "風向(degrees)"
}
},
{
"id": "publishtime",
"type": "text",
"info": {
"label": "資料發布時間"
}
},
{
"id": "co_8hr",
"type": "text",
"info": {
"label": "一氧化碳8小時移動平均(ppm)"
}
},
{
"id": "pm2.5_avg",
"type": "text",
"info": {
"label": "細懸浮微粒移動平均值(μg\/m3)"
}
},
{
"id": "pm10_avg",
"type": "text",
"info": {
"label": "懸浮微粒移動平均值(μg\/m3)"
}
},
{
"id": "so2_avg",
"type": "text",
"info": {
"label": "二氧化硫移動平均值(ppb)"
}
},
{
"id": "longitude",
"type": "text",
"info": {
"label": "經度"
}
},
{
"id": "latitude",
"type": "text",
"info": {
"label": "緯度"
}
},
{
"id": "siteid",
"type": "text",
"info": {
"label": "測站編號"
}
}
],
"resource_id": "8d2f907f-bbb4-4fdf-8f08-8eabae15da45",
"__extras": {
"api_key": "9b651a1b-0732-418e-b4e9-e784417cadef"
},
"include_total": true,
"total": "86",
"resource_format": "object",
"limit": "1000",
"offset": "0",
"_links": {
"start": "\/api\/v2\/aqx_p_432?api_key=9b651a1b-0732-418e-b4e9-e784417cadef&limit=1000&format=JSON&sort=ImportDate desc",
"next": "\/api\/v2\/aqx_p_432?api_key=9b651a1b-0732-418e-b4e9-e784417cadef&limit=1000&format=JSON&sort=ImportDate desc&offset=1000"
},
"records": [
{
"sitename": "基隆",
"county": "基隆市",
"aqi": "22",
"pollutant": "",
"status": "良好",
"so2": "0.7",
"co": "0.1",
"o3": "23",
"o3_8hr": "20",
"pm10": "8",
"pm2.5": "0",
"no2": "1",
"nox": "2.5",
"no": "1.1",
"wind_speed": "1.5",
"wind_direc": "78",
"publishtime": "2025\/08\/11 13:00:00",
"co_8hr": "0.2",
"pm2.5_avg": "3.2",
"pm10_avg": "13",
"so2_avg": "0",
"longitude": "121.760056",
"latitude": "25.129167",
"siteid": "1"
},
{
"sitename": "汐止",
"county": "新北市",
"aqi": "33",
"pollutant": "",
"status": "良好",
"so2": "0.9",
"co": "0.17",
"o3": "45",
"o3_8hr": "24",
"pm10": "16",
"pm2.5": "9",
"no2": "8",
"nox": "9",
"no": "0.7",
"wind_speed": "1.9",
"wind_direc": "43",
"publishtime": "2025\/08\/11 13:00:00",
"co_8hr": "0.2",
"pm2.5_avg": "8.2",
"pm10_avg": "17",
"so2_avg": "1",
"longitude": "121.64081",
"latitude": "25.06624",
"siteid": "2"
},
{
"sitename": "新店",
"county": "新北市",
"aqi": "28",
"pollutant": "",
"status": "良好",
"so2": "0.8",
"co": "0.24",
"o3": "69",
"o3_8hr": "30",
"pm10": "22",
"pm2.5": "11",
"no2": "9",
"nox": "9.8",
"no": "0.7",
"wind_speed": "1.1",
"wind_direc": "250",
"publishtime": "2025\/08\/11 13:00:00",
"co_8hr": "0.2",
"pm2.5_avg": "6",
"pm10_avg": "17",
"so2_avg": "1",
"longitude": "121.537778",
"latitude": "24.977222",
"siteid": "4"
},<<Python TKinter>>
import tkinter as tk # 導入 tkinter 庫,用於建立 GUI 介面
from tkinter import ttk, messagebox, filedialog # 從 tkinter 導入 ttk(主題套件)、messagebox(訊息視窗)和 filedialog(檔案對話框)
import requests # 導入 requests 庫,用於發送 HTTP 請求
import csv # 導入 csv 庫,用於處理 CSV 檔案
import pandas as pd # 導入 pandas 庫,此程式未使用,但可用於數據處理
# --- 函數定義 ---
def fetch_data():
"""從環保署 API 擷取空氣品質資料"""
# 定義環保署空氣品質 API 的 URL
url = "https://data.moenv.gov.tw/api/v2/aqx_p_432?api_key=9b651a1b-0732-418e-b4e9-e784417cadef&limit=1000&sort=ImportDate desc&format=JSON"
try:
response = requests.get(url) # 向 API 網址發送 GET 請求
response.raise_for_status() # 如果請求失敗(例如 404 或 500 錯誤),則引發 HTTPError
data = response.json().get('records', []) # 將 JSON 格式的回應轉換成 Python 字典,並取出 'records' 列表;如果 'records' 不存在,則回傳空列表
# 過濾出需要的欄位
filtered_data = [
# 遍歷 data 列表中的每個字典(record),並取出 'county', 'sitename', 'aqi', 'status' 的值
# 如果某個鍵不存在,則使用 'N/A' 作為預設值
(record.get('county', 'N/A'), record.get('sitename', 'N/A'), record.get('aqi', 'N/A'), record.get('status', 'N/A'))
for record in data
]
return filtered_data # 返回過濾後的資料列表
except requests.exceptions.RequestException as e:
# 如果請求過程中發生任何錯誤(如網路連線問題),則顯示錯誤訊息視窗
messagebox.showerror("錯誤", f"無法連線到 API,請檢查網路連線或 API 狀態。\n錯誤訊息:{e}")
return [] # 回傳空列表
def create_treeview():
"""建立並設定 Treeview 元件"""
tree_frame = ttk.Frame(root) # 建立一個 ttk.Frame 作為 Treeview 的容器
tree_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) # 將容器打包到主視窗中,設定邊距,並使其填滿可用空間
columns = ('county', 'sitename', 'AQI', 'status') # 定義 Treeview 的欄位名稱
tree = ttk.Treeview(tree_frame, columns=columns, show='headings') # 建立 Treeview 元件,並指定欄位和只顯示標題
# 為每個欄位設定標題文字
tree.heading('county', text='縣市')
tree.heading('sitename', text='測站名稱')
tree.heading('AQI', text='AQI')
tree.heading('status', text='空氣品質狀態')
# 設定每個欄位的寬度
tree.column('county', width=100)
tree.column('sitename', width=150)
tree.column('AQI', width=80)
tree.column('status', width=150)
# 添加滾動條
scrollbar = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=tree.yview) # 建立垂直滾動條
tree.configure(yscrollcommand=scrollbar.set) # 將 Treeview 的 yscollcommand 與滾動條綁定
scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 將滾動條打包到右側,並填滿垂直空間
tree.pack(fill=tk.BOTH, expand=True) # 將 Treeview 打包,使其填滿並擴展
return tree # 回傳建立好的 Treeview 元件
def update_treeview():
"""獲取資料並更新 Treeview"""
for item in tree.get_children(): # 遍歷 Treeview 中所有現有的項目
tree.delete(item) # 刪除每個項目,清空舊資料
data = fetch_data() # 呼叫 fetch_data 函數獲取最新資料
if data: # 檢查是否成功獲取到資料
for row in data: # 遍歷資料列表中的每一行
tree.insert('', 'end', values=row) # 將每一行資料插入到 Treeview 的末尾
def export_to_csv():
"""將 Treeview 中的資料匯出為 CSV 檔案"""
# 獲取 Treeview 中所有可見的資料,以列表形式儲存
data = [tree.item(item)['values'] for item in tree.get_children()]
if not data: # 如果沒有資料,則顯示警告視窗
messagebox.showwarning("警告", "沒有資料可供匯出。")
return
# 打開檔案儲存對話框,讓使用者選擇儲存路徑和檔名
file_path = filedialog.asksaveasfilename(
defaultextension=".csv", # 設定預設副檔名為 .csv
filetypes=[("CSV files", "*.csv")], # 設定檔案類型篩選
title="儲存 CSV 檔案" # 設定對話框標題
)
if file_path: # 如果使用者選擇了路徑並按下儲存
try:
# 以寫入模式 'w'、newline=''(防止寫入多餘空行)、並以 'utf-8-sig' 編碼(支援中文並防止亂碼)開啟檔案
with open(file_path, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f) # 建立 csv 寫入器
writer.writerow(['縣市', '測站名稱', 'AQI', '空氣品質狀態']) # 寫入 CSV 檔案的標題列
writer.writerows(data) # 將所有資料行寫入檔案
messagebox.showinfo("成功", f"資料已成功儲存至 {file_path}") # 顯示成功訊息
except Exception as e:
messagebox.showerror("錯誤", f"匯出檔案時發生錯誤:{e}") # 如果寫入檔案時發生錯誤,顯示錯誤訊息
# --- 程式主體 ---
# 初始化主視窗
root = tk.Tk() # 建立主視窗物件
root.title("台灣 AQI 空氣品質查詢") # 設定視窗標題
root.geometry("600x500") # 設定視窗的初始大小
# 建立按鈕框架
button_frame = ttk.Frame(root) # 建立一個框架來放置按鈕
button_frame.pack(pady=5) # 將框架打包,並在上方留有 5 像素的垂直邊距
# 建立更新按鈕
update_button = ttk.Button(button_frame, text="獲取資料並更新", command=update_treeview) # 建立一個按鈕,點擊時執行 update_treeview 函數
update_button.pack(side=tk.LEFT, padx=5) # 將按鈕打包到框架左側,並設定水平邊距
# 建立匯出按鈕
export_button = ttk.Button(button_frame, text="匯出為 CSV", command=export_to_csv) # 建立一個按鈕,點擊時執行 export_to_csv 函數
export_button.pack(side=tk.LEFT, padx=5) # 將按鈕打包到框架左側,並設定水平邊距
# 建立 Treeview 來顯示資料
tree = create_treeview() # 呼叫 create_treeview 函數,並將返回的 Treeview 元件賦值給 tree
# 程式啟動時自動載入資料
update_treeview() # 在程式啟動時自動呼叫一次,以便第一次打開時就顯示資料
# 啟動主迴圈
root.mainloop() # 進入 Tkinter 的事件主迴圈,保持視窗運行並響應使用者操作


沒有留言:
張貼留言