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)