From accdb5104b1987a20620c44718470f16b47462d0 Mon Sep 17 00:00:00 2001
From: "2569718930@qq.com" <2569718930@qq.com>
Date: Sun, 8 Feb 2026 18:09:37 +0800
Subject: [PATCH] feat: implement multi-source weather data collection
functionality with OpenWeatherMap, Visual Crossing, and METAR APIs.
---
bot_listener.py | 38 +++++++++++++++-----
src/data_collection/weather_sources.py | 50 ++++++++++++++++++++++++++
2 files changed, 80 insertions(+), 8 deletions(-)
diff --git a/bot_listener.py b/bot_listener.py
index 8ed0201d..64d04181 100644
--- a/bot_listener.py
+++ b/bot_listener.py
@@ -276,6 +276,8 @@ def start_bot():
msg_lines.append(f"\n📊 Open-Meteo 7天预测")
nws = weather_data.get("nws", {})
nws_high = nws.get("today_high")
+ mgm = weather_data.get("mgm", {})
+ mgm_high = mgm.get("today_high")
for i, (d, t) in enumerate(zip(dates[:7], max_temps[:7])):
# 跳过无效数据
@@ -285,17 +287,37 @@ def start_bot():
day_label = "今天" if d == city_today_str else d[5:]
indicator = "👉 " if d == city_today_str else " "
- # 如果是今天且有 NWS 数据,显示模型对比
- if d == city_today_str and nws_high is not None:
- diff = abs(t - nws_high)
- if diff > 1:
- msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol} ⚠️")
- msg_lines.append(f" (NWS官方预报: {nws_high}{temp_symbol},差异 {diff:.1f}°)")
- else:
- msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol} (NWS: {nws_high}{temp_symbol})")
+ # 如果是今天且有 NWS 或 MGM 数据,显示模型对比
+ comp_lines = []
+ if d == city_today_str:
+ if nws_high is not None:
+ diff_nws = abs(t - nws_high)
+ warning = " ⚠️" if diff_nws > 1 else ""
+ comp_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}{warning}")
+ comp_lines.append(f" (NWS官方预报: {nws_high}{temp_symbol},差异 {diff_nws:.1f}°)")
+ elif mgm_high is not None:
+ # 安卡拉 MGM 对比
+ diff_mgm = abs(t - mgm_high)
+ warning = " ⚠️" if diff_mgm > 1 else ""
+ comp_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}{warning}")
+ comp_lines.append(f" (MGM官方预报: {mgm_high}{temp_symbol},差异 {diff_mgm:.1f}°)")
+
+ if comp_lines:
+ msg_lines.extend(comp_lines)
else:
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}")
+ # MGM 官方实测显示
+ if mgm:
+ mgm_curr = mgm.get("current", {})
+ mgm_temp = mgm_curr.get("temp")
+ if mgm_temp is not None:
+ msg_lines.append(f"\n🏛️ MGM 官方实测 ({mgm_curr.get('station_name', 'Ankara')})")
+ msg_lines.append(f" 🌡️ {mgm_temp}°C (湿度: {mgm_curr.get('nem', mgm_curr.get('humidity'))}%)")
+ msg_lines.append(f" 💨 风速: {mgm_curr.get('wind_speed_kt')}kt")
+ if mgm_curr.get("rain_24h"):
+ msg_lines.append(f" 🌧️ 24h降水: {mgm_curr.get('rain_24h')}mm")
+
if metar:
icao = metar.get("icao", "")
metar_temp = metar.get("current", {}).get("temp")
diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py
index 3358a7dd..188a6a34 100644
--- a/src/data_collection/weather_sources.py
+++ b/src/data_collection/weather_sources.py
@@ -319,6 +319,50 @@ class WeatherDataCollector:
logger.error(f"METAR 数据解析失败 ({icao}): {e}")
return None
+ def fetch_from_mgm(self, istno: str) -> Optional[Dict]:
+ """
+ 从土耳其气象局 (MGM) 获取实时数据和预测 (由用户提供其内部 API)
+ """
+ base_url = "https://servis.mgm.gov.tr/web"
+ # 必须带 Origin,否则会被反爬拦截
+ headers = {
+ "Origin": "https://www.mgm.gov.tr",
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
+ }
+ results = {}
+
+ try:
+ # 1. 实时数据
+ obs_resp = self.session.get(f"{base_url}/sondurumlar?istno={istno}", headers=headers, timeout=self.timeout)
+ if obs_resp.status_code == 200:
+ data = obs_resp.json()
+ if data:
+ latest = data[0] if isinstance(data, list) else data
+ # MGM 数据字段映射
+ results["current"] = {
+ "temp": latest.get("sicaklik"),
+ "humidity": latest.get("nem"),
+ "wind_speed_kt": round(latest.get("ruzgarHiz", 0) * 1.94, 1) if latest.get("ruzgarHiz") is not None else None,
+ "wind_dir": latest.get("ruzgarYon"),
+ "rain_24h": latest.get("toplamYagis"),
+ "time": latest.get("veriZamani"),
+ "station_name": latest.get("istasyonAd")
+ }
+
+ # 2. 每日预报
+ daily_resp = self.session.get(f"{base_url}/tahminler/gunluk?istno={istno}", headers=headers, timeout=self.timeout)
+ if daily_resp.status_code == 200:
+ forecasts = daily_resp.json()
+ if forecasts and isinstance(forecasts, list):
+ today = forecasts[0]
+ results["today_high"] = today.get("enYuksekGun1")
+ results["today_low"] = today.get("enDusukGun1")
+
+ return results if "current" in results else None
+ except Exception as e:
+ logger.error(f"MGM API 请求失败 ({istno}): {e}")
+ return None
+
def fetch_nws(self, lat: float, lon: float) -> Optional[Dict]:
"""
从 NWS (美国国家气象局) 获取高精度预报
@@ -653,6 +697,12 @@ class WeatherDataCollector:
if metar_data:
results["metar"] = metar_data
+ # 对安卡拉,额外获取 MGM 官方数据
+ if city_lower == "ankara":
+ mgm_data = self.fetch_from_mgm("17128")
+ if mgm_data:
+ results["mgm"] = mgm_data
+
# 对美国城市,额外获取 NWS 高精预报
if use_fahrenheit:
nws_data = self.fetch_nws(lat, lon)