feat: introduce multi-source weather data collection module supporting OpenWeatherMap, Visual Crossing, and NOAA METAR.

This commit is contained in:
2569718930@qq.com
2026-02-08 02:35:55 +08:00
parent c7cd05da90
commit da76e98227
2 changed files with 65 additions and 10 deletions
+9 -8
View File
@@ -187,20 +187,21 @@ def start_bot():
city_today_str = city_now.strftime("%Y-%m-%d")
msg_lines.append(f"\n📊 <b>Open-Meteo 7天预测</b>")
model_split = daily.get("model_split")
nws = weather_data.get("nws", {})
nws_high = nws.get("today_high")
for i, (d, t) in enumerate(zip(dates[:7], max_temps[:7])):
day_label = "今天" if d == city_today_str else d[5:]
indicator = "👉 " if d == city_today_str else " "
# 如果是今天且存在模型分歧,则特别标注
if d == city_today_str and model_split:
ecmwf = model_split.get("ecmwf")
hrrr = model_split.get("hrrr")
if ecmwf and hrrr and abs(ecmwf - hrrr) > 0.5:
# 如果是今天且有 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" (模型分歧: ECMWF {ecmwf} | HRRR {hrrr})")
msg_lines.append(f" (NWS官方预报: {nws_high}{temp_symbol},差异 {diff:.1f}°)")
else:
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}")
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol} (NWS: {nws_high})")
else:
msg_lines.append(f"{indicator}{day_label}: 最高 {t}{temp_symbol}")
+56 -2
View File
@@ -314,6 +314,55 @@ class WeatherDataCollector:
logger.error(f"METAR 数据解析失败 ({icao}): {e}")
return None
def fetch_nws(self, lat: float, lon: float) -> Optional[Dict]:
"""
从 NWS (美国国家气象局) 获取高精度预报
仅适用于美国城市,全球 VPS 均可访问
"""
try:
# 1. 获取网格点
points_url = f"https://api.weather.gov/points/{lat},{lon}"
headers = {"User-Agent": "PolyWeather/1.0 (weather-bot)"}
points_resp = self.session.get(points_url, headers=headers, timeout=self.timeout)
points_resp.raise_for_status()
points_data = points_resp.json()
forecast_url = points_data.get("properties", {}).get("forecast")
if not forecast_url:
return None
# 2. 获取预报
forecast_resp = self.session.get(forecast_url, headers=headers, timeout=self.timeout)
forecast_resp.raise_for_status()
forecast_data = forecast_resp.json()
periods = forecast_data.get("properties", {}).get("periods", [])
if not periods:
return None
# 3. 提取今日最高温(找 isDaytime=True 的第一个)
today_high = None
for p in periods:
if p.get("isDaytime") and "High" in p.get("name", ""):
today_high = p.get("temperature")
break
# 如果没有明确的 High,取第一个 daytime 的温度
if today_high is None:
for p in periods:
if p.get("isDaytime"):
today_high = p.get("temperature")
break
return {
"source": "nws",
"today_high": today_high,
"unit": "fahrenheit",
}
except Exception as e:
logger.warning(f"NWS 请求失败: {e}")
return None
def fetch_from_open_meteo(
self,
lat: float,
@@ -343,10 +392,9 @@ class WeatherDataCollector:
"_t": int(time.time()), # 禁用缓存,强制刷新
}
# 对于美国市场,使用华氏度并请求更多的模型共识
# 对于美国市场,使用华氏度
if use_fahrenheit:
params["temperature_unit"] = "fahrenheit"
params["models"] = "ecmwf_ifs,hrrr_conus"
response = self.session.get(
url,
@@ -588,6 +636,12 @@ class WeatherDataCollector:
metar_data = self.fetch_metar(city, use_fahrenheit=use_fahrenheit, utc_offset=utc_offset)
if metar_data:
results["metar"] = metar_data
# 对美国城市,额外获取 NWS 高精预报
if use_fahrenheit:
nws_data = self.fetch_nws(lat, lon)
if nws_data:
results["nws"] = nws_data
else:
# 降级方案
metar_data = self.fetch_metar(city, use_fahrenheit=use_fahrenheit)