2025年7月27日 星期日

臺中市政府環境保護局即時空氣品質監測網 ---Python TKinter

臺中市政府環境保護局即時空氣品質監測網 ---Python TKinter


空氣品質指標(AQI)發布時間:2025年07月27日 21時





import tkinter as tk

from tkinter import ttk, messagebox

import requests

from bs4 import BeautifulSoup

import pandas as pd

import io # 確保導入 io 模組


class TaichungAQIApp:

    def __init__(self, root):

        self.root = root

        self.root.title("臺中市 AQI 資料一覽表")

        self.root.geometry("1400x750") # 調整視窗大小以容納更多欄位


        # 關鍵修正:目標 URL 改為實際包含 AQI 表格數據的頁面

        self.url = "https://taqm.epb.taichung.gov.tw/TQAMNEWAQITABLE.ASPX" 


        # 用於顯示發布時間的標籤

        self.publish_time_label = ttk.Label(root, text="資料發布時間: 載入中...", font=('Arial', 12, 'bold'))

        self.publish_time_label.pack(pady=(10, 0))


        self.create_widgets()

        self.fetch_and_display_data()


    def create_widgets(self):

        # 控制按鈕區塊

        control_frame = tk.Frame(self.root)

        control_frame.pack(pady=5)


        refresh_button = tk.Button(control_frame, text="重新整理資料", command=self.fetch_and_display_data, font=('Arial', 10))

        refresh_button.pack(side=tk.LEFT, padx=5)


        # Treeview 顯示資料

        self.tree = ttk.Treeview(self.root, show="headings")

        self.tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)


        # 設定捲軸

        vsb = ttk.Scrollbar(self.tree, orient="vertical", command=self.tree.yview)

        vsb.pack(side='right', fill='y')

        self.tree.configure(yscrollcommand=vsb.set)


        hsb = ttk.Scrollbar(self.tree, orient="horizontal", command=self.tree.xview)

        hsb.pack(side='bottom', fill='x')

        self.tree.configure(xscrollcommand=hsb.set)


    def fetch_and_display_data(self):

        self.clear_treeview() # 先清除舊資料


        try:

            # 獲取網頁內容

            response = requests.get(self.url)

            response.raise_for_status() # 檢查 HTTP 請求是否成功


            # 使用 BeautifulSoup 解析 HTML

            soup = BeautifulSoup(response.text, 'html.parser')


            # --- 擷取發布時間 ---

            # 根據提供的 HTML,發布時間在 id="Label2" 的 span 標籤內

            publish_time_tag = soup.find('span', {'id': 'Label2'}) 

            if publish_time_tag:

                time_text = publish_time_tag.get_text(strip=True)

                self.publish_time_label.config(text=time_text)

            else:

                self.publish_time_label.config(text="資料發布時間: 未找到")

            # --- 擷取發布時間結束 ---


            # 尋找包含 AQI 資料的表格,使用其明確的 ID

            table = soup.find('table', {'id': 'GridView1'}) 

            

            if not table:

                messagebox.showerror("錯誤", "未能找到 ID 為 'GridView1' 的 AQI 資料表格。請檢查網頁結構是否已更改。")

                return


            # 使用 pandas 來方便地讀取 HTML 表格

            # str(table) 將 BeautifulSoup 物件轉換為字串,然後 io.StringIO 包裹

            dfs = pd.read_html(io.StringIO(str(table)))

            

            if not dfs:

                messagebox.showerror("錯誤", "未能從網頁表格中解析出任何數據。")

                return


            # AQI 資料表格是第一個 (或唯一一個) 被找到的表格

            df = dfs[0]


            # 清洗欄位名稱,處理多層表頭和特殊字符

            # pandas.read_html 會自動處理多層表頭,生成 MultiIndex

            if isinstance(df.columns, pd.MultiIndex):

                new_columns = []

                for col_tuple in df.columns:

                    # 組合多層表頭,例如:('二氧化硫(SO2)', '小時濃度(ppb)')

                    # 考慮到子標題可能是空或重複的特殊情況

                    main_col = col_tuple[0].strip().replace('\n', '')

                    sub_col = col_tuple[1].strip().replace('\n', '')


                    if not sub_col or main_col == sub_col:

                        new_columns.append(main_col)

                    else:

                        # 對特定子標題進行簡化

                        if '小時濃度(ppb)' in sub_col:

                            sub_col = '(ppb)'

                        elif '8小時移動平均濃度(ppm)' in sub_col:

                            sub_col = '(ppm)'

                        elif '移動平均濃度(μg/m3)' in sub_col:

                            sub_col = '(μg/m3)'

                        elif '小時濃度(ppm)' in sub_col: # 確保處理所有可能的ppm

                            sub_col = '(ppm)'

                        elif '8小時移動平均濃度(ppb)' in sub_col: # 確保處理所有可能的ppb

                             sub_col = '(ppb)'


                        new_columns.append(f"{main_col}{sub_col}")

                df.columns = new_columns

            else:

                 # 如果不是多層表頭,也進行一般的清理

                df.columns = [col.strip().replace('\n', '') for col in df.columns]


            # 最終的欄位名稱映射,確保顯示名稱的一致性

            # 這裡定義了我們期望的最終顯示名稱和它們在DataFrame中可能出現的名稱

            # 這是為了處理 read_html 可能產生的各種組合名稱

            column_rename_map = {

                '測站名稱': '測站名稱',

                '對健康影響等級': '對健康影響等級',

                'AQI指標值': 'AQI指標值',

                '指標污染物': '指標污染物',

                '二氧化硫(SO2)(ppb)': '二氧化硫(SO2)',

                '一氧化碳(CO)(ppm)': '一氧化碳(CO)',

                '臭氧(O3)(ppb)': '臭氧(O3)',

                '臭氧(O3)8小時移動平均濃度(ppb)': '臭氧(O3)8小時移動平均',

                '二氧化氮(NO2)(ppb)': '二氧化氮(NO2)',

                '懸浮微粒(PM10)(μg/m3)': '懸浮微粒(PM10)',

                '細懸浮微粒(PM2.5)(μg/m3)': '細懸浮微粒(PM2.5)',

            }

            

            # 反轉映射,方便檢查 DataFrame 中是否存在這些最終名稱

            # 並處理原始 DataFrame 可能包含的額外空白或換行

            actual_cols_to_target_names = {key.replace(' ', '').replace('\n', '').lower(): value for key, value in column_rename_map.items()}

            

            # 對 DataFrame 的欄位進行標準化和重新命名

            current_df_cols = {}

            for col in df.columns:

                cleaned_col = col.replace(' ', '').replace('\n', '').lower()

                if cleaned_col in actual_cols_to_target_names:

                    current_df_cols[col] = actual_cols_to_target_names[cleaned_col]


            df.rename(columns=current_df_cols, inplace=True)

            

            # 確保欄位名稱與您提供的列表完全一致,並保持順序

            # 這裡列出您期望看到的欄位名稱及其順序

            final_display_order = [

                '測站名稱', '對健康影響等級', 'AQI指標值', '指標污染物',

                '二氧化硫(SO2)', '一氧化碳(CO)', '臭氧(O3)', '臭氧(O3)8小時移動平均',

                '二氧化氮(NO2)', '懸浮微粒(PM10)', '細懸浮微粒(PM2.5)'

            ]


            # 篩選並重新排序 DataFrame 的欄位

            # 只保留 final_display_order 中存在的欄位

            df_display = df[[col for col in final_display_order if col in df.columns]]

            

            # 設定 Treeview 的欄位

            self.tree["columns"] = list(df_display.columns)

            for col in df_display.columns:

                self.tree.heading(col, text=col, command=lambda c=col: self.sort_treeview(self.tree, c, False))

                # 調整部分欄位寬度以更好顯示內容

                if col == '測站名稱':

                    self.tree.column(col, width=150, anchor='center')

                elif col == '對健康影響等級':

                    self.tree.column(col, width=100, anchor='center')

                elif col == '指標污染物':

                    self.tree.column(col, width=100, anchor='center')

                else:

                    self.tree.column(col, width=80, anchor='center') # 預設欄寬


            # 插入資料

            for index, row in df_display.iterrows():

                # 將 NaN, '--', ' ', '無監測' 轉換為空字串或更友好的顯示

                row_values = []

                for val in row:

                    s_val = str(val).strip()

                    # 檢查各種空值或無效數據的表示

                    if pd.isna(val) or s_val == '--' or s_val == ' ' or s_val == '無監測' or s_val == '*':

                        row_values.append("") # 顯示為空字串

                    else:

                        # 移除 <br> 標籤和 <sup> 標籤

                        s_val = s_val.replace('<br>', '').replace('<sup>', '').replace('</sup>', '')

                        row_values.append(s_val)

                self.tree.insert("", "end", values=row_values)


        except requests.exceptions.RequestException as e:

            messagebox.showerror("網路錯誤", f"無法連接到網站或獲取資料: {e}")

            self.publish_time_label.config(text="資料發布時間: 載入失敗")

        except Exception as e:

            messagebox.showerror("資料解析錯誤", f"處理網頁資料時發生錯誤: {e}\n\n請檢查網頁結構或更新程式。")

            self.publish_time_label.config(text="資料發布時間: 載入失敗")


    def clear_treeview(self):

        for item in self.tree.get_children():

            self.tree.delete(item)

        self.tree["columns"] = () # 清除所有欄位

        self.tree.heading("#0", text="") # 清除預設標題


    def sort_treeview(self, tree, col, reverse):

        try:

            col_index = list(tree["columns"]).index(col)

        except ValueError:

            return 

        

        l = [(tree.set(k, col), k) for k in tree.get_children('')]


        def try_numeric_sort(value):

            clean_value = str(value).strip()

            # 移除所有非數字和小數點的字符

            numeric_chars = ''.join(filter(lambda x: x.isdigit() or x == '.' or x == '-' , clean_value)) # 考慮負號

            if numeric_chars and numeric_chars.count('.') <= 1:

                try:

                    return float(numeric_chars)

                except ValueError:

                    pass

            return value


        l.sort(key=lambda t: try_numeric_sort(t[0]), reverse=reverse)


        for index, (val, k) in enumerate(l):

            tree.move(k, '', index)

        

        tree.heading(col, command=lambda: self.sort_treeview(tree, col, not reverse))



if __name__ == "__main__":

    root = tk.Tk()

    app = TaichungAQIApp(root)

    root.mainloop()




view-source:https://taqm.epb.taichung.gov.tw/TQAMNEWAQITABLE.ASPX
<script async src='https://www.googletagmanager.com/gtag/js?id=UA-137158817-2'></script><script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-137158817-2');</script>

<!DOCTYPE html>
<html lang="zh-Hant" xmlns="http://www.w3.org/1999/xhtml">
<head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>
AQI TABLE LIST
</title>
<style type="text/css">
.auto-style1 {
width: 100%;
}
a, a:link, a:visited {
color: #904E0E;
text-decoration: none;
}
a:hover {
color: #F4AB25;
background-color: #FFECD9;
text-decoration: none;
}
.breadcrumb {
list-style: none;
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 0;
background-color: #f8f9fa;
}

.breadcrumb-item + .breadcrumb-item::before {
content: ">";
padding: 0 8px;
color: #6c757d;
}

.breadcrumb-item a {
text-decoration: none;
color: #007bff;
}

.breadcrumb-item.active {
color: #6c757d;
}

.breadcrumb-item a:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
}
h1 {
font-size: 1.5em; /* 調整字體大小為1倍的基準字體大小 */
}
.inline-container {
display: flex; /* 使用 flexbox 讓元素在一行顯示 */
align-items: center; /* 垂直對齊中心 */
}

.brick, .breadcrumb {
margin-right: 10px; /* 可以調整兩者之間的距離 */
}
</style>
</head>
<body>
<noscript>
<div style="color: red; font-weight: bold;">
您的瀏覽器不支援JavaScript功能,若網頁功能無法正常使用時,請開啟瀏覽器的JavaScript狀態。
</div>
</noscript>
<div class="inline-container">
<a href="#Accesskey_B" class="brick" id="Accesskey_B" accesskey="B" title="主功能區塊,由選單點選後於此呈現相關資訊">:::</a>
<nav aria-label="麵包屑導航">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/TQAMNEWAQI.ASPX">首頁</a></li>
<li class="breadcrumb-item"><a href="/TQAMNEWAQItable.ASPX">AQI資料一覽表</a></li>
</ol>
</nav>
</div>
<form method="post" action="./TQAMNEWAQITABLE.ASPX" id="form1">
<div class="aspNetHidden">
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTg4MTIxODI3OA9kFgICAw9kFgYCAQ8PFgoeB1Zpc2libGVoHgRUZXh0BRXnm67liY3nhKHorabloLHpgJrnn6UeBF8hU0ICiAEeCUJhY2tDb2xvcgpeHgZIZWlnaHQbAAAAAAAAN0ABAAAAZGQCAw8PFgIfAQU956m65rCj5ZOB6LOq5oyH5qiZKEFRSSnnmbzluIPmmYLplpPvvJoyMDI15bm0MDfmnIgyN+aXpSAyMeaZgmRkAicPPCsAEQIADxYEHgtfIURhdGFCb3VuZGceC18hSXRlbUNvdW50AhBkDBQrABAWCB4ETmFtZQUM5ris56uZ5ZCN56ixHgpJc1JlYWRPbmx5aB4EVHlwZRkrAh4JRGF0YUZpZWxkBQzmuKznq5nlkI3nqLEWCB8HBQbnrYnntJofCGgfCRkrAh8KBQbnrYnntJoWCB8HBQnmjIfmqJnlgLwfCGgfCRkrAh8KBQnmjIfmqJnlgLwWCB8HBQnmjIfmqJnniakfCGgfCRkrAh8KBQnmjIfmqJnniakWCB8HBQ9TTzLjgIDlia/mjIfmqJkfCGgfCRkrAh8KBQ9TTzLjgIDlia/mjIfmqJkWCB8HBQxTTzLjgIDmv4PluqYfCGgfCRkrAh8KBQxTTzLjgIDmv4PluqYWCB8HBQ5DT+OAgOWJr+aMh+aomR8IaB8JGSsCHwoFDkNP44CA5Ymv5oyH5qiZFggfBwULQ0/jgIDmv4PluqYfCGgfCRkrAh8KBQtDT+OAgOa/g+W6phYIHwcFEU8z44CA44CA5Ymv5oyH5qiZHwhoHwkZKwIfCgURTzPjgIDjgIDlia/mjIfmqJkWCB8HBQ5PM+OAgOOAgOa/g+W6ph8IaB8JGSsCHwoFDk8z44CA44CA5r+D5bqmFggfBwUPTk8y44CA5Ymv5oyH5qiZHwhoHwkZKwIfCgUPTk8y44CA5Ymv5oyH5qiZFggfBwUMTk8y44CA5r+D5bqmHwhoHwkZKwIfCgUMTk8y44CA5r+D5bqmFggfBwUNUE0xMOWJr+aMh+aomR8IaB8JGSsCHwoFDVBNMTDlia/mjIfmqJkWCB8HBQpQTTEw5r+D5bqmHwhoHwkZKwIfCgUKUE0xMOa/g+W6phYIHwcFDlBNMi415Ymv5oyH5qiZHwhoHwkZKwIfCgUOUE0yLjXlia/mjIfmqJkWCB8HBQtQTTIuNea/g+W6ph8IaB8JGSsCHwoFC1BNMi415r+D5bqmFgJmD2QWIgIBDw8WBB4JRm9yZUNvbG9yCTOxT/8fAgIEZBYgZg8PFgIfAQUX6KW/5bGv5ris56uZKOeSsOWig+mDqClkZAIBDw8WAh8BBQboia/lpb1kZAICDw8WAh8BBQI0MmRkAgMPDxYCHwEFDOaHuOa1ruW+rueykmRkAgQPDxYCHwEFBiZuYnNwO2RkAgUPDxYCHwEFAzAuN2RkAgYPDxYCHwEFBiZuYnNwO2RkAgcPDxYCHwEFBDAuMTBkZAIIDw8WAh8BBQQyOC4wZGQCCQ8PFgIfAQUCMzNkZAIKDw8WAh8BBQYmbmJzcDtkZAILDw8WAh8BBQMzLjBkZAIMDw8WAh8BBQYmbmJzcDtkZAINDw8WAh8BBQIyNWRkAg4PDxYCHwEFBiZuYnNwO2RkAg8PDxYCHwEFAjEwZGQCAg8PFgQfCwkzsU//HwICBGQWIGYPDxYCHwEFF+W/oOaYjua4rOermSjnkrDlooPpg6gpZGQCAQ8PFgIfAQUG6Imv5aW9ZGQCAg8PFgIfAQUCNDJkZAIDDw8WAh8BBQ/ntLDmh7jmta7lvq7nspJkZAIEDw8WAh8BBQYmbmJzcDtkZAIFDw8WAh8BBQMwLjlkZAIGDw8WAh8BBQYmbmJzcDtkZAIHDw8WAh8BBQQwLjEwZGQCCA8PFgIfAQUEMjYuMGRkAgkPDxYCHwEFAjMyZGQCCg8PFgIfAQUGJm5ic3A7ZGQCCw8PFgIfAQUDNC4wZGQCDA8PFgIfAQUGJm5ic3A7ZGQCDQ8PFgIfAQUCMjBkZAIODw8WAh8BBQYmbmJzcDtkZAIPDw8WAh8BBQIxMGRkAgMPDxYEHwsJAJ3m/x8CAgRkFiBmDw8WAh8BBRflpKfph4zmuKznq5ko55Kw5aKD6YOoKWRkAgEPDxYCHwEFBuaZrumAmmRkAgIPDxYCHwEFAjUyZGQCAw8PFgIfAQUP57Sw5oe45rWu5b6u57KSZGQCBA8PFgIfAQUGJm5ic3A7ZGQCBQ8PFgIfAQUDMC43ZGQCBg8PFgIfAQUGJm5ic3A7ZGQCBw8PFgIfAQUEMC4xMGRkAggPDxYCHwEFBDI3LjBkZAIJDw8WAh8BBQIzMmRkAgoPDxYCHwEFBiZuYnNwO2RkAgsPDxYCHwEFAzUuMGRkAgwPDxYCHwEFBiZuYnNwO2RkAg0PDxYCHwEFAjIxZGQCDg8PFgIfAQUGJm5ic3A7ZGQCDw8PFgIfAQUCMTNkZAIEDw8WBB8LCTOxT/8fAgIEZBYgZg8PFgIfAQUX5rKZ6bm/5ris56uZKOeSsOWig+mDqClkZAIBDw8WAh8BBQboia/lpb1kZAICDw8WAh8BBQI0MmRkAgMPDxYCHwEFDOaHuOa1ruW+rueykmRkAgQPDxYCHwEFBiZuYnNwO2RkAgUPDxYCHwEFAzAuMmRkAgYPDxYCHwEFBiZuYnNwO2RkAgcPDxYCHwEFBDAuMTBkZAIIDw8WAh8BBQQzMC4wZGQCCQ8PFgIfAQUCMzNkZAIKDw8WAh8BBQYmbmJzcDtkZAILDw8WAh8BBQMzLjBkZAIMDw8WAh8BBQYmbmJzcDtkZAINDw8WAh8BBQIyNWRkAg4PDxYCHwEFBiZuYnNwO2RkAg8PDxYCHwEFATlkZAIFDw8WBB8LCTOxT/8fAgIEZBYgZg8PFgIfAQUX6LGQ5Y6f5ris56uZKOeSsOWig+mDqClkZAIBDw8WAh8BBQboia/lpb1kZAICDw8WAh8BBQIzNWRkAgMPDxYCHwEFD+e0sOaHuOa1ruW+rueykmRkAgQPDxYCHwEFBiZuYnNwO2RkAgUPDxYCHwEFAzEuMmRkAgYPDxYCHwEFBiZuYnNwO2RkAgcPDxYCHwEFBDAuMTBkZAIIDw8WAh8BBQQzMS4wZGQCCQ8PFgIfAQUCMzFkZAIKDw8WAh8BBQYmbmJzcDtkZAILDw8WAh8BBQMzLjBkZAIMDw8WAh8BBQYmbmJzcDtkZAINDw8WAh8BBQIxOWRkAg4PDxYCHwEFBiZuYnNwO2RkAg8PDxYCHwEFATlkZAIGDw8WBB8LCTOxT/8fAgIEZBYgZg8PFgIfAQUM5paH5bGx5ris56uZZGQCAQ8PFgIfAQUG6Imv5aW9ZGQCAg8PFgIfAQUCNDdkZAIDDw8WAh8BBQzmh7jmta7lvq7nspJkZAIEDw8WAh8BBQYmbmJzcDtkZAIFDw8WAh8BBQMwLjdkZAIGDw8WAh8BBQYmbmJzcDtkZAIHDw8WAh8BBQQwLjQ3ZGQCCA8PFgIfAQUEMjguM2RkAgkPDxYCHwEFAjMyZGQCCg8PFgIfAQUGJm5ic3A7ZGQCCw8PFgIfAQUDMi42ZGQCDA8PFgIfAQUGJm5ic3A7ZGQCDQ8PFgIfAQUCMjhkZAIODw8WAh8BBQYmbmJzcDtkZAIPDw8WAh8BBQE4ZGQCBw8PFgQfCwkzsU//HwICBGQWIGYPDxYCHwEFDOWkp+eUsua4rOermWRkAgEPDxYCHwEFBuiJr+WlvWRkAgIPDxYCHwEFAjQwZGQCAw8PFgIfAQUP57Sw5oe45rWu5b6u57KSZGQCBA8PFgIfAQUGJm5ic3A7ZGQCBQ8PFgIfAQUDMS4yZGQCBg8PFgIfAQUGJm5ic3A7ZGQCBw8PFgIfAQUEMC40OWRkAggPDxYCHwEFBDMyLjVkZAIJDw8WAh8BBQIzM2RkAgoPDxYCHwEFBiZuYnNwO2RkAgsPDxYCHwEFAzYuNGRkAgwPDxYCHwEFBiZuYnNwO2RkAg0PDxYCHwEFAjI0ZGQCDg8PFgIfAQUGJm5ic3A7ZGQCDw8PFgIfAQUCMTBkZAIIDw8WBB8LCTOxT/8fAgIEZBYgZg8PFgIfAQUM5aSq5bmz5ris56uZZGQCAQ8PFgIfAQUG6Imv5aW9ZGQCAg8PFgIfAQUCNDdkZAIDDw8WAh8BBQ/ntLDmh7jmta7lvq7nspJkZAIEDw8WAh8BBQYmbmJzcDtkZAIFDw8WAh8BBQMxLjFkZAIGDw8WAh8BBQYmbmJzcDtkZAIHDw8WAh8BBQQwLjUzZGQCCA8PFgIfAQUEMjkuOWRkAgkPDxYCHwEFAjI5ZGQCCg8PFgIfAQUGJm5ic3A7ZGQCCw8PFgIfAQUDNS44ZGQCDA8PFgIfAQUGJm5ic3A7ZGQCDQ8PFgIfAQUCMThkZAIODw8WAh8BBQYmbmJzcDtkZAIPDw8WAh8BBQIxMmRkAgkPDxYEHwsJM7FP/x8CAgRkFiBmDw8WAh8BBQzpnKfls7DmuKznq5lkZAIBDw8WAh8BBQboia/lpb1kZAICDw8WAh8BBQI0MmRkAgMPDxYCHwEFDOaHuOa1ruW+rueykmRkAgQPDxYCHwEFBiZuYnNwO2RkAgUPDxYCHwEFAzEuNmRkAgYPDxYCHwEFBiZuYnNwO2RkAgcPDxYCHwEFBDAuNDFkZAIIDw8WAh8BBQQyOS42ZGQCCQ8PFgIfAQUCMzNkZAIKDw8WAh8BBQYmbmJzcDtkZAILDw8WAh8BBQMyLjlkZAIMDw8WAh8BBQYmbmJzcDtkZAINDw8WAh8BBQIyNWRkAg4PDxYCHwEFBiZuYnNwO2RkAg8PDxYCHwEFATlkZAIKDw8WBB8LCTOxT/8fAgIEZBYgZg8PFgIfAQUM54OP5pel5ris56uZZGQCAQ8PFgIfAQUG6Imv5aW9ZGQCAg8PFgIfAQUCMzFkZAIDDw8WAh8BBQboh63msKdkZAIEDw8WAh8BBQYmbmJzcDtkZAIFDw8WAh8BBQMwLjhkZAIGDw8WAh8BBQYmbmJzcDtkZAIHDw8WAh8BBQQwLjM0ZGQCCA8PFgIfAQUEMjkuMGRkAgkPDxYCHwEFAjM0ZGQCCg8PFgIfAQUGJm5ic3A7ZGQCCw8PFgIfAQUDNC4yZGQCDA8PFgIfAQUGJm5ic3A7ZGQCDQ8PFgIfAQUCMTdkZAIODw8WAh8BBQYmbmJzcDtkZAIPDw8WAh8BBQE3ZGQCCw8PFgQfCwkzsU//HwICBGQWIGYPDxYCHwEFDOWQjumHjOa4rOermWRkAgEPDxYCHwEFBuiJr+WlvWRkAgIPDxYCHwEFAjQxZGQCAw8PFgIfAQUP57Sw5oe45rWu5b6u57KSZGQCBA8PFgIfAQUGJm5ic3A7ZGQCBQ8PFgIfAQUDMS41ZGQCBg8PFgIfAQUGJm5ic3A7ZGQCBw8PFgIfAQUEMC40OWRkAggPDxYCHwEFBDI4LjFkZAIJDw8WAh8BBQIyOWRkAgoPDxYCHwEFBiZuYnNwO2RkAgsPDxYCHwEFAzEuNGRkAgwPDxYCHwEFBiZuYnNwO2RkAg0PDxYCHwEFAjE5ZGQCDg8PFgIfAQUGJm5ic3A7ZGQCDw8PFgIfAQUCMTBkZAIMDw8WBB8LCQCd5v8fAgIEZBYgZg8PFgIfAQUR5qKn5qOy56uZKOWPsOmbuylkZAIBDw8WAh8BBQbmma7pgJpkZAICDw8WAh8BBQI1MWRkAgMPDxYCHwEFDOaHuOa1ruW+rueykmRkAgQPDxYCHwEFBiZuYnNwO2RkAgUPDxYCHwEFAzEuM2RkAgYPDxYCHwEFBiZuYnNwO2RkAgcPDxYCHwEFCeeEoeebo+a4rGRkAggPDxYCHwEFBDMwLjVkZAIJDw8WAh8BBQIzNGRkAgoPDxYCHwEFBiZuYnNwO2RkAgsPDxYCHwEFAzIuMmRkAgwPDxYCHwEFBiZuYnNwO2RkAg0PDxYCHwEFAjMxZGQCDg8PFgIfAQUGJm5ic3A7ZGQCDw8PFgIfAQUBN2RkAg0PDxYEHwsJM7FP/x8CAgRkFiBmDw8WAh8BBRHlpKfogprnq5ko5Y+w6Zu7KWRkAgEPDxYCHwEFBuiJr+WlvWRkAgIPDxYCHwEFAjQ3ZGQCAw8PFgIfAQUM5oe45rWu5b6u57KSZGQCBA8PFgIfAQUGJm5ic3A7ZGQCBQ8PFgIfAQUDMS41ZGQCBg8PFgIfAQUGJm5ic3A7ZGQCBw8PFgIfAQUJ54Sh55uj5risZGQCCA8PFgIfAQUEMjkuMWRkAgkPDxYCHwEFAjM0ZGQCCg8PFgIfAQUGJm5ic3A7ZGQCCw8PFgIfAQUDMy42ZGQCDA8PFgIfAQUGJm5ic3A7ZGQCDQ8PFgIfAQUCMjhkZAIODw8WAh8BBQYmbmJzcDtkZAIPDw8WAh8BBQE4ZGQCDg8PFgQfCwkzsU//HwICBGQWIGYPDxYCHwEFEeadseWkp+ermSjlj7Dpm7spZGQCAQ8PFgIfAQUG6Imv5aW9ZGQCAg8PFgIfAQUCMzhkZAIDDw8WAh8BBQ/ntLDmh7jmta7lvq7nspJkZAIEDw8WAh8BBQYmbmJzcDtkZAIFDw8WAh8BBQMxLjhkZAIGDw8WAh8BBQYmbmJzcDtkZAIHDw8WAh8BBQnnhKHnm6PmuKxkZAIIDw8WAh8BBQQyNS42ZGQCCQ8PFgIfAQUCMzFkZAIKDw8WAh8BBQYmbmJzcDtkZAILDw8WAh8BBQMxLjZkZAIMDw8WAh8BBQYmbmJzcDtkZAINDw8WAh8BBQIxOWRkAg4PDxYCHwEFBiZuYnNwO2RkAg8PDxYCHwEFAjEwZGQCDw8PFgQfCwkzsU//HwICBGQWIGYPDxYCHwEFEea4heawtOermSjlj7Dpm7spZGQCAQ8PFgIfAQUG6Imv5aW9ZGQCAg8PFgIfAQUCNDNkZAIDDw8WAh8BBQzmh7jmta7lvq7nspJkZAIEDw8WAh8BBQYmbmJzcDtkZAIFDw8WAh8BBQMxLjVkZAIGDw8WAh8BBQYmbmJzcDtkZAIHDw8WAh8BBQnnhKHnm6PmuKxkZAIIDw8WAh8BBQQzMi40ZGQCCQ8PFgIfAQUCMzNkZAIKDw8WAh8BBQYmbmJzcDtkZAILDw8WAh8BBQM0LjBkZAIMDw8WAh8BBQYmbmJzcDtkZAINDw8WAh8BBQIyNmRkAg4PDxYCHwEFBiZuYnNwO2RkAg8PDxYCHwEFATlkZAIQDw8WBB8LCTOxT/8fAgIEZBYgZg8PFgIfAQUR6b6N5LqV56uZKOWPsOmbuylkZAIBDw8WAh8BBQboia/lpb1kZAICDw8WAh8BBQI0N2RkAgMPDxYCHwEFDOaHuOa1ruW+rueykmRkAgQPDxYCHwEFBiZuYnNwO2RkAgUPDxYCHwEFAzEuM2RkAgYPDxYCHwEFBiZuYnNwO2RkAgcPDxYCHwEFBDAuMTZkZAIIDw8WAh8BBQQzMi40ZGQCCQ8PFgIfAQUCMzdkZAIKDw8WAh8BBQYmbmJzcDtkZAILDw8WAh8BBQMyLjJkZAIMDw8WAh8BBQYmbmJzcDtkZAINDw8WAh8BBQIyOGRkAg4PDxYCHwEFBiZuYnNwO2RkAg8PDxYCHwEFAjEwZGQCEQ8PFgIfAGhkZBgBBQlHcmlkVmlldzEPPCsADAEIAgFkK7YX09jvCftWOCtmCrEgDd/Luds=" />
</div>

<div class="aspNetHidden">

<input type="hidden" name="__VIEWSTATEGENERATOR" id="__VIEWSTATEGENERATOR" value="FB0C4454" />
</div>
<div>
<h1>AQI測值一覽表</h1>
</div>
<table class="auto-style1">
<tr>
<td>
<br />
<span id="Label2" style="font-weight:bold;">空氣品質指標(AQI)發布時間:2025年07月27日 21時</span>
</td>
</tr>
<tr>
<td>
</td>
</tr>
<tr>
<td>
<div>
<table cellspacing="0" cellpadding="4" id="GridView1" style="border-collapse:collapse;">
<tr style="color:White;background-color:#1C5E55;font-weight:bold;">
<th ROWspan="2" style="width:130px;">測站名稱</th><th ROWspan="2" style="width:70px;">對健康影響等級</th><th ROWspan="2" style="width:49px;">AQI指標值</th><th ROWspan="2" style="width:100px;">指標污染物</th><th COLspan="2" style="width:1px;">二氧化硫(SO<sub>2</sub>)</th><th COLspan="2" style="width:74px;">一氧化碳(CO)</th><th COLspan="2" style="width:1px;">臭氧(O<sub>3</sub>)</th><th COLspan="2" style="width:74px;">二氧化氮(NO<sub>2</sub>)</th><th COLspan="2" style="width:1px;">懸浮微粒(PM<sub>10</sub>)</th><th COLspan="2" style="width:74px;">細懸浮微粒(PM<sub>2.5</sub>)</th></tr><tr></th><th style="width:1px;"></th><th style="width:74px;">小時濃度(ppb)</th><th style="width:1px;"></th><th style="width:84px;">8小時移動平均濃度(ppm)</th><th style="width:1px;">小時濃度(ppb)</th><th style="width:1px;">8小時移動平均濃度(ppb)</th><th></th><th>小時濃度(ppb)</th><th></th><th>移動平均濃度(μg/m<sup>3</sup>)</th><th></th><th>移動平均濃度(μg/m<sup>3</sup>)</th>
</tr><tr align="center" style="color:#4FB133;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=1">西屯測站(環境部)</a></td><td style="width:70px;">良好</td><td style="width:49px;">42</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.7</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.10</td><td style="width:1px;">28.0</td><td style="width:74px;">33</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">3.0</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">25</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">10</td>
</tr><tr align="center" style="color:#4FB133;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=2">忠明測站(環境部)</a></td><td style="width:70px;">良好</td><td style="width:49px;">42</td><td style="width:100px;">細懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.9</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.10</td><td style="width:1px;">26.0</td><td style="width:74px;">32</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">4.0</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">20</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">10</td>
</tr><tr align="center" style="color:#E69D00;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=3">大里測站(環境部)</a></td><td style="width:70px;">普通</td><td style="width:49px;">52</td><td style="width:100px;">細懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.7</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.10</td><td style="width:1px;">27.0</td><td style="width:74px;">32</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">5.0</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">21</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">13</td>
</tr><tr align="center" style="color:#4FB133;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=4">沙鹿測站(環境部)</a></td><td style="width:70px;">良好</td><td style="width:49px;">42</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.2</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.10</td><td style="width:1px;">30.0</td><td style="width:74px;">33</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">3.0</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">25</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">9</td>
</tr><tr align="center" style="color:#4FB133;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=5">豐原測站(環境部)</a></td><td style="width:70px;">良好</td><td style="width:49px;">35</td><td style="width:100px;">細懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.2</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.10</td><td style="width:1px;">31.0</td><td style="width:74px;">31</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">3.0</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">19</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">9</td>
</tr><tr align="center" style="color:#4FB133;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=6">文山測站</a></td><td style="width:70px;">良好</td><td style="width:49px;">47</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.7</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.47</td><td style="width:1px;">28.3</td><td style="width:74px;">32</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">2.6</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">28</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">8</td>
</tr><tr align="center" style="color:#4FB133;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=7">大甲測站</a></td><td style="width:70px;">良好</td><td style="width:49px;">40</td><td style="width:100px;">細懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.2</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.49</td><td style="width:1px;">32.5</td><td style="width:74px;">33</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">6.4</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">24</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">10</td>
</tr><tr align="center" style="color:#4FB133;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=8">太平測站</a></td><td style="width:70px;">良好</td><td style="width:49px;">47</td><td style="width:100px;">細懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.1</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.53</td><td style="width:1px;">29.9</td><td style="width:74px;">29</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">5.8</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">18</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">12</td>
</tr><tr align="center" style="color:#4FB133;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=9">霧峰測站</a></td><td style="width:70px;">良好</td><td style="width:49px;">42</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.6</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.41</td><td style="width:1px;">29.6</td><td style="width:74px;">33</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">2.9</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">25</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">9</td>
</tr><tr align="center" style="color:#4FB133;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=10">烏日測站</a></td><td style="width:70px;">良好</td><td style="width:49px;">31</td><td style="width:100px;">臭氧</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.8</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.34</td><td style="width:1px;">29.0</td><td style="width:74px;">34</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">4.2</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">17</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">7</td>
</tr><tr align="center" style="color:#4FB133;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=11">后里測站</a></td><td style="width:70px;">良好</td><td style="width:49px;">41</td><td style="width:100px;">細懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.5</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.49</td><td style="width:1px;">28.1</td><td style="width:74px;">29</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.4</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">19</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">10</td>
</tr><tr align="center" style="color:#E69D00;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=12">梧棲站(台電)</a></td><td style="width:70px;">普通</td><td style="width:49px;">51</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.3</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">無監測</td><td style="width:1px;">30.5</td><td style="width:74px;">34</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">2.2</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">31</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">7</td>
</tr><tr align="center" style="color:#4FB133;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=13">大肚站(台電)</a></td><td style="width:70px;">良好</td><td style="width:49px;">47</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.5</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">無監測</td><td style="width:1px;">29.1</td><td style="width:74px;">34</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">3.6</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">28</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">8</td>
</tr><tr align="center" style="color:#4FB133;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=14">東大站(台電)</a></td><td style="width:70px;">良好</td><td style="width:49px;">38</td><td style="width:100px;">細懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.8</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">無監測</td><td style="width:1px;">25.6</td><td style="width:74px;">31</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.6</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">19</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">10</td>
</tr><tr align="center" style="color:#4FB133;background-color:#EFF3FB;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=15">清水站(台電)</a></td><td style="width:70px;">良好</td><td style="width:49px;">43</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.5</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">無監測</td><td style="width:1px;">32.4</td><td style="width:74px;">33</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">4.0</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">26</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">9</td>
</tr><tr align="center" style="color:#4FB133;background-color:White;">
<td style="width:130px;"><a href="aqi/aqiNEW.ASPX?name=16">龍井站(台電)</a></td><td style="width:70px;">良好</td><td style="width:49px;">47</td><td style="width:100px;">懸浮微粒</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">1.3</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">0.16</td><td style="width:1px;">32.4</td><td style="width:74px;">37</td><td style="width:1px;">&nbsp;</td><td style="width:74px;">2.2</td><td style="width:1px;">&nbsp;</td><td style="width:84px;">28</td><td style="width:1px;">&nbsp;</td><td style="width:1px;">10</td>
</tr>
</table>
</div>
</td>
</tr>
</table>
</form>
<h2>資料說明</h2>
<table>
<tr>
<td>
<p>1. 各污染物以環境部公布即時空氣品質指標(AQI)值計算方法換算,<a href="https://airtw.epa.gov.tw/CHT/Information/Standard/AirQualityIndicator.aspx#tg4" target="_blank">參考網址</a></p>
<p>2. 測值說明:</p>
<p> O<sub>3</sub>:取最近連續8小時移動平均值</p>
<p> PM<sub>2.5</sub>:0.5×前12小時平均+0.5×前4小時平均(前4小時2筆有效,前12小時6筆有效)</p>
<p> PM<sub>10</sub>:0.5×前12小時平均+0.5×前4小時平均(前4小時2筆有效,前12小時6筆有效)</p>
<p> CO:取最近連續8小時移動平均值</p>
<p> SO<sub>2</sub>:取即時濃度值</p>
<p> NO<sub>2</sub>:取即時濃度值</p>
<p>"—"為無效數據(監測資料不足),副指標部分"*"表該濃度無副指標對應值</p>
</td>
</tr>
</table>
</body>
</html>
  • 最終確認目標 URL:

    • 我將 self.url 精確地設定為 https://taqm.epb.taichung.gov.tw/TQAMNEWAQITABLE.ASPX。這是最關鍵的修正,因為這個頁面確實包含了完整的 AQI 數據表格。

  • 精確定位發布時間:

    • 根據 HTML,發布時間現在是位於 <span id="Label2"> 標籤中,程式碼已更新為 soup.find('span', {'id': 'Label2'})

  • 使用表格 ID 定位數據:

    • 最可靠地找到數據表格的方式是使用其唯一的 ID:id="GridView1"。程式碼現在使用 soup.find('table', {'id': 'GridView1'}) 來確保找到正確的表格。

  • 更全面的欄位名稱清洗與標準化:

    • pandas.read_html 處理複雜的 HTML 表頭時,可能會生成一些包含換行符、空格或奇怪組合的 MultiIndex。我對欄位清洗邏輯進行了加強,使其能夠:

      • 將多層表頭組合為更簡潔、易讀的單層名稱(例如 二氧化硫(SO2)小時濃度(ppb) 簡化為 二氧化硫(SO2)(ppb))。

      • 提供一個 column_rename_map,用於將這些讀取到的實際欄位名稱進一步標準化為您期望的簡潔顯示名稱(例如 二氧化硫(SO2)(ppb) 最終顯示為 二氧化硫(SO2))。

      • 確保最終顯示的欄位只包含 final_display_order 中定義的欄位,並且按照該順序排列

  • 增強數據值處理:

    • 在插入數據到 Treeview 時,除了 NaN--無監測,現在也處理 &nbsp; (HTML 中的不換行空格) 和 * (副指標無對應值) 這些可能代表空值的符號,將它們統一顯示為空字串,使表格更整潔。

    • 對於包含 <br><sup> 等 HTML 標籤的數據,在顯示前將其移除,只保留純文本。

  • 優化欄位寬度:

    • 對某些欄位(如「測站名稱」、「對健康影響等級」、「指標污染物」)設置了更合適的初始寬度,以改善視覺呈現。

  • 沒有留言:

    張貼留言

    ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

     ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...