File size: 5,440 Bytes
c0f48f1
e9e401c
8b5bf5f
 
c0f48f1
 
 
 
8b5bf5f
 
 
 
 
7b108b2
8b5bf5f
 
 
2cce2ac
8b5bf5f
 
 
 
c0f48f1
 
 
 
 
 
 
a1fc907
c0f48f1
 
7b108b2
8b5bf5f
 
7b108b2
8b5bf5f
7b108b2
c0f48f1
 
 
 
 
 
 
 
8b5bf5f
7b108b2
c0f48f1
 
8b5bf5f
c0f48f1
 
 
 
8b5bf5f
7b108b2
c0f48f1
 
 
 
 
 
 
 
 
 
 
 
 
7b108b2
c0f48f1
 
8b5bf5f
7b108b2
8b5bf5f
 
c0f48f1
 
 
 
 
 
 
 
 
 
8b5bf5f
c0f48f1
8b5bf5f
 
 
 
 
 
 
 
 
 
e9e401c
c0f48f1
8b5bf5f
 
c0f48f1
8b5bf5f
 
7b108b2
8b5bf5f
 
c0f48f1
8b5bf5f
 
 
 
 
 
 
a1a9f32
8b5bf5f
a1a9f32
8b5bf5f
c0f48f1
8b5bf5f
 
 
 
2cce2ac
8b5bf5f
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# app.py

from fastapi import FastAPI, HTTPException
from starlette.responses import JSONResponse
import requests
import re
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta, timezone
import logging
import html

# --- 配置日誌 ---
logging.basicConfig(level=logging.INFO)

# --- 初始化 FastAPI 應用 ---
app = FastAPI(
    title="台灣地震速報 API",
    description="一個從台灣中央氣象署(CWA)公開預警XML Feed中,擷取最新地震速報的API。https://cwadayi-app-show-pws.hf.space/cwa-earthquakes",
    version="1.0.0",
)

# --- Geocode 與縣市名稱的對照字典 ---
GEOCODE_MAP = {
    "10002": "宜蘭縣", "10004": "新竹縣", "10005": "苗栗縣",
    "10007": "彰化縣", "10008": "南投縣", "10009": "雲林縣",
    "10010": "嘉義縣", "10013": "屏東縣", "10014": "臺東縣",
    "10015": "花蓮縣", "10016": "澎湖縣", "10017": "基隆市",
    "10018": "新竹市", "10020": "嘉義市", "63": "臺北市",
    "64": "高雄市", "65": "新北市", "66": "臺中市",
    "67": "臺南市", "68": "桃園市", "09007": "連江縣",
    "09020": "金門縣"
}

# --- 核心功能函式 ---
def get_cwa_earthquakes():
    """
    抓取並解析 CWA 地震速報 Feed,回傳結構化的 Python 列表。
    """
    timestamp = int(datetime.now().timestamp())
    feed_url = f"https://cbs.tw/files/rssatomfeed.xml?_={timestamp}"
    keywords = ["中央氣象署", "氣象署"]
    headers = {
        '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',
        'Cache-Control': 'no-cache',
        'Pragma': 'no-cache'
    }

    try:
        response = requests.get(feed_url, headers=headers, timeout=15)
        response.raise_for_status()

        xml_content = response.content.decode('utf-8')
        xml_content = re.sub(' xmlns="[^"]+"', '', xml_content, count=1)
        root = ET.fromstring(xml_content)
        
        earthquake_alerts = []

        for entry in root.findall('entry'):
            summary_text = ''
            for tag_name in ['summary', 'description']:
                content_tag = entry.find(tag_name)
                if content_tag is not None and content_tag.text:
                    summary_text = content_tag.text
                    break
            
            title_tag = entry.find('title')
            title = title_tag.text if title_tag is not None else ""
            
            is_earthquake_alert = "地震速報" in title
            is_from_cwa = any(keyword in summary_text for keyword in keywords)

            if is_earthquake_alert and is_from_cwa:
                sent_tag = entry.find('sent')
                published_time = sent_tag.text if sent_tag is not None else "N/A"

                area_desc = ''
                affected_counties = []
                area_tag = entry.find('area')
                if area_tag is not None:
                    area_desc_tag = area_tag.find('areadesc')
                    area_desc = area_desc_tag.text if area_desc_tag is not None else ''

                    for geocode_tag in area_tag.findall('geocode'):
                        value_tag = geocode_tag.find('value')
                        if value_tag is not None:
                            code = value_tag.text
                            location_name = GEOCODE_MAP.get(code, f"未知代碼({code})")
                            affected_counties.append(location_name)
                
                alert = {
                    "title": html.unescape(title),
                    "publish_time": published_time,
                    "summary": html.unescape(summary_text),
                    "affected_area_desc": html.unescape(area_desc),
                    "affected_counties": affected_counties
                }
                earthquake_alerts.append(alert)
        
        return earthquake_alerts

    except requests.exceptions.RequestException as e:
        logging.error(f"無法從 CWA 取得資料: {e}")
        raise HTTPException(status_code=503, detail="無法連接到 CWA 資料來源")
    except ET.ParseError as e:
        logging.error(f"XML 資料解析失敗: {e}")
        raise HTTPException(status_code=500, detail="解析 CWA 資料時發生錯誤")
    except Exception as e:
        logging.error(f"發生未預期的錯誤: {e}")
        raise HTTPException(status_code=500, detail=f"發生內部錯誤: {e}")

# --- API 端點 ---
@app.get("/")
def root():
    """
    根目錄端點,提供 API 簡介和範例連結。
    """
    return {
        "message": "歡迎使用台灣地震速報 API - https://cwadayi-app-show-pws.hf.space/cwa-earthquakes",
        "documentation": "/docs",
        "earthquake_feed_endpoint": "/cwa-earthquakes"
    }

@app.get("/cwa-earthquakes")
async def get_earthquakes_endpoint():
    """
    提供最新的地震速報。
    https://cwadayi-app-show-pws.hf.space/cwa-earthquakes
    此端點會從 CWA 的公開預警 Feed 中擷取資料,
    篩選出來自「中央氣象署」的「地震速報」,
    並以 JSON 格式回傳。
    """
    alerts = get_cwa_earthquakes()
    tw_tz = timezone(timedelta(hours=8))
    last_updated = datetime.now(tw_tz).isoformat()
    
    return JSONResponse(content={
        "last_updated": last_updated,
        "source": "台灣中央氣象署 (CWA)",
        "alerts": alerts
    })