cwadayi commited on
Commit
8b5bf5f
·
verified ·
1 Parent(s): d38dacf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +69 -59
app.py CHANGED
@@ -1,15 +1,25 @@
1
  # app.py
2
 
3
- import gradio as gr
4
- import html
5
  import requests
6
  import re
7
  import xml.etree.ElementTree as ET
8
  from datetime import datetime, timedelta, timezone
 
 
 
 
 
9
 
10
- # ===============================================================
11
- # Geocode 與縣市名稱的對照字典
12
- # ===============================================================
 
 
 
 
 
13
  GEOCODE_MAP = {
14
  "10002": "宜蘭縣", "10004": "新竹縣", "10005": "苗栗縣",
15
  "10007": "彰化縣", "10008": "南投縣", "10009": "雲林縣",
@@ -21,42 +31,29 @@ GEOCODE_MAP = {
21
  "09020": "金門縣"
22
  }
23
 
24
- # ===============================================================
25
- # 主要功能:讀取並過濾地震速報
26
- # ===============================================================
27
- def get_disaster_warnings() -> str:
28
  """
29
- Fetches and formats Earthquake Early Warnings from the CWA PWS feed.
30
-
31
- This function retrieves the latest disaster warnings, filters them to show
32
- only 'Earthquake Early Warning' (地震速報) alerts issued by the CWA, and
33
- formats the output in Markdown, including affected counties.
34
  """
35
- # 使用時間戳避免快取
36
  timestamp = int(datetime.now().timestamp())
37
  feed_url = f"https://cbs.tw/files/rssatomfeed.xml?_={timestamp}"
38
  keywords = ["中央氣象署", "氣象署"]
39
-
40
  headers = {
41
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
42
  'Cache-Control': 'no-cache',
43
  'Pragma': 'no-cache'
44
  }
45
-
46
  try:
47
  response = requests.get(feed_url, headers=headers, timeout=15)
48
  response.raise_for_status()
49
-
50
  xml_content = response.content.decode('utf-8')
51
  xml_content = re.sub(' xmlns="[^"]+"', '', xml_content, count=1)
52
  root = ET.fromstring(xml_content)
53
-
54
- # 取得台灣時區
55
- tw_tz = timezone(timedelta(hours=8))
56
- current_time_str = datetime.now(tw_tz).strftime('%Y-%m-%d %H:%M:%S')
57
 
58
- markdown_output = f"## 最新地震速報 (僅顯示來自:中央氣象署)\n---\n*最後更新時間:{current_time_str} (GMT+8)*\n\n"
59
- messages_found = 0
60
 
61
  for entry in root.findall('entry'):
62
  summary_text = ''
@@ -73,58 +70,71 @@ def get_disaster_warnings() -> str:
73
  is_from_cwa = any(keyword in summary_text for keyword in keywords)
74
 
75
  if is_earthquake_alert and is_from_cwa:
76
- messages_found += 1
77
-
78
  sent_tag = entry.find('sent')
79
- published_time = sent_tag.text if sent_tag is not None else "無發布時間"
80
-
81
- markdown_output += f"### {html.escape(title)}\n"
82
- markdown_output += f"**發布時間:** {html.escape(published_time)}\n\n"
83
 
 
 
84
  area_tag = entry.find('area')
85
  if area_tag is not None:
86
  area_desc_tag = area_tag.find('areadesc')
87
  area_desc = area_desc_tag.text if area_desc_tag is not None else ''
88
 
89
- affected_locations = []
90
  for geocode_tag in area_tag.findall('geocode'):
91
  value_tag = geocode_tag.find('value')
92
  if value_tag is not None:
93
  code = value_tag.text
94
  location_name = GEOCODE_MAP.get(code, f"未知代碼({code})")
95
- affected_locations.append(location_name)
96
-
97
- area_details = ""
98
- if affected_locations:
99
- area_details = f" ({'、'.join(affected_locations)})"
100
-
101
- full_area_info = f"{html.escape(area_desc)}{area_details}"
102
- markdown_output += f"**影響範圍:** {full_area_info}\n\n"
103
 
104
- markdown_output += f"**內容:**\n{html.escape(summary_text)}\n\n"
105
- markdown_output += "---\n"
 
 
 
 
 
 
 
 
106
 
107
- if messages_found == 0:
108
- return markdown_output + "目前沒有最新的地震速報。"
109
-
110
- return markdown_output
111
  except requests.exceptions.RequestException as e:
112
- return f"網路請求失敗,無法取得資料: {e}"
 
113
  except ET.ParseError as e:
114
- return f"XML 資料解析失敗,請稍後再試: {e}"
 
115
  except Exception as e:
116
- return f"讀取訊息時發生未預期的錯誤: {e}"
 
117
 
118
- # ===============================================================
119
- # 建立 Gradio 介面
120
- # ===============================================================
121
- with gr.Blocks(title="台灣地震速報") as app:
122
- gr.Markdown("## 台灣地震速報")
123
- gr.Markdown("自動讀取最新的地震速報,並過濾只顯示由「中央氣象署」發布的內容。")
124
- output_markdown = gr.Markdown()
125
- app.load(get_disaster_warnings, outputs=output_markdown, every=60) # 每60秒自動更新一次
 
 
 
126
 
 
 
 
 
127
 
128
- # 啟動 Gradio 網頁介面
129
- if __name__ == "__main__":
130
- app.launch()
 
 
 
 
 
 
 
 
 
 
 
1
  # app.py
2
 
3
+ from fastapi import FastAPI, HTTPException
4
+ from starlette.responses import JSONResponse
5
  import requests
6
  import re
7
  import xml.etree.ElementTree as ET
8
  from datetime import datetime, timedelta, timezone
9
+ import logging
10
+ import html
11
+
12
+ # --- 配置日誌 ---
13
+ logging.basicConfig(level=logging.INFO)
14
 
15
+ # --- 初始化 FastAPI 應用 ---
16
+ app = FastAPI(
17
+ title="台灣地震速報 API",
18
+ description="一個從台灣中央氣象署(CWA)公開預警XML Feed中,擷取最新地震速報的API。",
19
+ version="1.0.0",
20
+ )
21
+
22
+ # --- Geocode 與縣市名稱的對照字典 ---
23
  GEOCODE_MAP = {
24
  "10002": "宜蘭縣", "10004": "新竹縣", "10005": "苗栗縣",
25
  "10007": "彰化縣", "10008": "南投縣", "10009": "雲林縣",
 
31
  "09020": "金門縣"
32
  }
33
 
34
+ # --- 核心功能函式 ---
35
+ def get_cwa_earthquakes():
 
 
36
  """
37
+ 抓取並解析 CWA 地震速報 Feed,回傳結構化的 Python 列表。
 
 
 
 
38
  """
 
39
  timestamp = int(datetime.now().timestamp())
40
  feed_url = f"https://cbs.tw/files/rssatomfeed.xml?_={timestamp}"
41
  keywords = ["中央氣象署", "氣象署"]
 
42
  headers = {
43
  'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
44
  'Cache-Control': 'no-cache',
45
  'Pragma': 'no-cache'
46
  }
47
+
48
  try:
49
  response = requests.get(feed_url, headers=headers, timeout=15)
50
  response.raise_for_status()
51
+
52
  xml_content = response.content.decode('utf-8')
53
  xml_content = re.sub(' xmlns="[^"]+"', '', xml_content, count=1)
54
  root = ET.fromstring(xml_content)
 
 
 
 
55
 
56
+ earthquake_alerts = []
 
57
 
58
  for entry in root.findall('entry'):
59
  summary_text = ''
 
70
  is_from_cwa = any(keyword in summary_text for keyword in keywords)
71
 
72
  if is_earthquake_alert and is_from_cwa:
 
 
73
  sent_tag = entry.find('sent')
74
+ published_time = sent_tag.text if sent_tag is not None else "N/A"
 
 
 
75
 
76
+ area_desc = ''
77
+ affected_counties = []
78
  area_tag = entry.find('area')
79
  if area_tag is not None:
80
  area_desc_tag = area_tag.find('areadesc')
81
  area_desc = area_desc_tag.text if area_desc_tag is not None else ''
82
 
 
83
  for geocode_tag in area_tag.findall('geocode'):
84
  value_tag = geocode_tag.find('value')
85
  if value_tag is not None:
86
  code = value_tag.text
87
  location_name = GEOCODE_MAP.get(code, f"未知代碼({code})")
88
+ affected_counties.append(location_name)
 
 
 
 
 
 
 
89
 
90
+ alert = {
91
+ "title": html.unescape(title),
92
+ "publish_time": published_time,
93
+ "summary": html.unescape(summary_text),
94
+ "affected_area_desc": html.unescape(area_desc),
95
+ "affected_counties": affected_counties
96
+ }
97
+ earthquake_alerts.append(alert)
98
+
99
+ return earthquake_alerts
100
 
 
 
 
 
101
  except requests.exceptions.RequestException as e:
102
+ logging.error(f"無法從 CWA 取得資料: {e}")
103
+ raise HTTPException(status_code=503, detail="無法連接到 CWA 資料來源")
104
  except ET.ParseError as e:
105
+ logging.error(f"XML 資料解析失敗: {e}")
106
+ raise HTTPException(status_code=500, detail="解析 CWA 資料時發生錯誤")
107
  except Exception as e:
108
+ logging.error(f"發生未預期的錯誤: {e}")
109
+ raise HTTPException(status_code=500, detail=f"發生內部錯誤: {e}")
110
 
111
+ # --- API 端點 ---
112
+ @app.get("/")
113
+ def root():
114
+ """
115
+ 根目錄端點,提供 API 簡介和範例連結。
116
+ """
117
+ return {
118
+ "message": "歡迎使用台灣地震速報 API",
119
+ "documentation": "/docs",
120
+ "earthquake_feed_endpoint": "/cwa-earthquakes"
121
+ }
122
 
123
+ @app.get("/cwa-earthquakes")
124
+ async def get_earthquakes_endpoint():
125
+ """
126
+ 提供最新的地震速報。
127
 
128
+ 此端點會從 CWA 的公開預警 Feed 中擷取資料,
129
+ 篩選出來自「中央氣象署」的「地震速報」,
130
+ 並以 JSON 格式回傳。
131
+ """
132
+ alerts = get_cwa_earthquakes()
133
+ tw_tz = timezone(timedelta(hours=8))
134
+ last_updated = datetime.now(tw_tz).isoformat()
135
+
136
+ return JSONResponse(content={
137
+ "last_updated": last_updated,
138
+ "source": "台灣中央氣象署 (CWA)",
139
+ "alerts": alerts
140
+ })