From b0e967781527c13012296fe2e5055b876e7a3407 Mon Sep 17 00:00:00 2001 From: "2569718930@qq.com" <2569718930@qq.com> Date: Sun, 8 Feb 2026 19:44:41 +0800 Subject: [PATCH] feat: Add a multi-source weather data collection module with support for OpenWeatherMap, Visual Crossing, and NOAA METAR. --- bot_listener.py | 13 ++++-- src/data_collection/weather_sources.py | 61 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/bot_listener.py b/bot_listener.py index de2ae2d6..60b68308 100644 --- a/bot_listener.py +++ b/bot_listener.py @@ -269,14 +269,19 @@ def start_bot(): nws_high = weather_data.get("nws", {}).get("today_high") mgm_high = mgm.get("today_high") + mb_high = weather_data.get("meteoblue", {}).get("today_high") # 今天对比 today_t = max_temps[0] if max_temps else "N/A" - comp_str = "" + comp_parts = [] if nws_high is not None: - comp_str = f" (NWS: {nws_high})" - elif mgm_high is not None: - comp_str = f" (MGM: {mgm_high})" + comp_parts.append(f"NWS: {nws_high}") + if mgm_high is not None: + comp_parts.append(f"MGM: {mgm_high}") + if mb_high is not None: + comp_parts.append(f"MB: {mb_high}") + + comp_str = f" ({' | '.join(comp_parts)})" if comp_parts else "" msg_lines.append(f"\n📊 预报 (Open-Meteo)") msg_lines.append(f"👉 今天: {today_t}{temp_symbol}{comp_str}") diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 80a0e27f..7fd5840f 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -524,6 +524,62 @@ class WeatherDataCollector: logger.error(f"Open-Meteo forecast failed: {e}") return None + def fetch_from_meteoblue( + self, + lat: float, + lon: float, + timezone_name: str = "UTC", + ) -> Optional[Dict]: + """ + 从 Meteoblue 网页抓取多模型预测数据的“共识”最高温 + 这是交易者最认可的高精度预测源之一 + """ + try: + # 构造 Meteoblue 规范化的 URL + # 格式: https://www.meteoblue.com/en/weather/week/{lat}N{lon}E8_{tz_slug} + tz_slug = timezone_name.replace("/", "%2F") + # 这里的 E8 后缀是 Meteoblue 的规范 + url = f"https://www.meteoblue.com/en/weather/week/{lat}N{lon}E8_{tz_slug}" + + headers = { + "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", + "Accept-Language": "en-US,en;q=0.9" + } + + # 使用现有 session (已配置代理) + response = self.session.get( + url, + headers=headers, + timeout=self.timeout + ) + response.raise_for_status() + + content = response.text + # 提取今天(Today)区块的最高温 + # 逻辑:找到 "Today" 后,查找第一个 "tab-temp-max" 中的温度 + match = re.search(r'Today.*?tab-temp-max.*?(\d+) °C', content, re.DOTALL | re.IGNORECASE) + + result = { + "source": "meteoblue", + "url": url, + "today_high": None, + "daily_highs": [] + } + + if match: + result["today_high"] = float(match.group(1)) + + # 同时提取接下来几天的最高温,增强对比性 + all_highs = re.findall(r'tab-temp-max.*?(\d+) °C', content, re.DOTALL) + if all_highs: + result["daily_highs"] = [float(h) for h in all_highs] + + logger.info(f"✅ Meteoblue 抓取成功: 今天 {result['today_high']}°C") + return result + except Exception as e: + logger.error(f"Meteoblue fetch failed: {e}") + return None + def extract_date_from_title(self, title: str) -> Optional[str]: """ 从标题中提取日期并标准化为 YYYY-MM-DD @@ -712,6 +768,11 @@ class WeatherDataCollector: if mgm_data: results["mgm"] = mgm_data + # 获取 Meteoblue 预测 (公认最准) + mb_data = self.fetch_from_meteoblue(lat, lon, timezone_name=open_meteo.get("timezone", "UTC")) + if mb_data: + results["meteoblue"] = mb_data + # 对美国城市,额外获取 NWS 高精预报 if use_fahrenheit: nws_data = self.fetch_nws(lat, lon)