feat: Implement multi-source weather data collection from OpenWeatherMap, Visual Crossing, and NOAA METAR, and add NYC market-specific data handling.
This commit is contained in:
+16
-8
@@ -218,13 +218,13 @@ def start_bot():
|
|||||||
"la": "los angeles", "洛杉矶": "los angeles",
|
"la": "los angeles", "洛杉矶": "los angeles",
|
||||||
}
|
}
|
||||||
|
|
||||||
# 1. 尝试直接从映射表获取
|
# 1. 第一优先级:严格全字匹配
|
||||||
city_name = STANDARD_MAPPING.get(city_input)
|
city_name = STANDARD_MAPPING.get(city_input)
|
||||||
|
|
||||||
# 2. 如果没匹配到,尝试前缀匹配 (如输入 "seou")
|
# 2. 第二优先级:如果长度 >= 3,尝试前缀匹配
|
||||||
if not city_name:
|
if not city_name and len(city_input) >= 3:
|
||||||
for k, v in STANDARD_MAPPING.items():
|
for k, v in STANDARD_MAPPING.items():
|
||||||
if len(city_input) >= 3 and k.startswith(city_input[:3]):
|
if k.startswith(city_input):
|
||||||
city_name = v
|
city_name = v
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -278,19 +278,27 @@ def start_bot():
|
|||||||
|
|
||||||
if mb_high is not None:
|
if mb_high is not None:
|
||||||
sources.append("MB")
|
sources.append("MB")
|
||||||
comp_parts.append(f"MB: {mb_high}")
|
comp_parts.append(f"MB: {mb_high:.1f}{temp_symbol}" if isinstance(mb_high, (int, float)) else f"MB: {mb_high}")
|
||||||
if nws_high is not None:
|
if nws_high is not None:
|
||||||
sources.append("NWS")
|
sources.append("NWS")
|
||||||
comp_parts.append(f"NWS: {nws_high}")
|
comp_parts.append(f"NWS: {nws_high:.1f}{temp_symbol}" if isinstance(nws_high, (int, float)) else f"NWS: {nws_high}")
|
||||||
if mgm_high is not None:
|
if mgm_high is not None:
|
||||||
sources.append("MGM")
|
sources.append("MGM")
|
||||||
comp_parts.append(f"MGM: {mgm_high}")
|
comp_parts.append(f"MGM: {mgm_high:.1f}{temp_symbol}" if isinstance(mgm_high, (int, float)) else f"MGM: {mgm_high}")
|
||||||
|
|
||||||
|
# 检查是否有显著分歧 (超过 5°F 或 2.5°C)
|
||||||
|
divergence_warning = ""
|
||||||
|
if mb_high is not None and max_temps:
|
||||||
|
diff = abs(mb_high - max_temps[0])
|
||||||
|
threshold = 5.0 if temp_unit == "fahrenheit" else 2.5
|
||||||
|
if diff > threshold:
|
||||||
|
divergence_warning = f" ⚠️ <b>模型显著分歧 ({diff:.1f}{temp_symbol})</b>"
|
||||||
|
|
||||||
comp_str = f" ({' | '.join(comp_parts)})" if comp_parts else ""
|
comp_str = f" ({' | '.join(comp_parts)})" if comp_parts else ""
|
||||||
sources_str = " | ".join(sources)
|
sources_str = " | ".join(sources)
|
||||||
|
|
||||||
msg_lines.append(f"\n📊 <b>预报 ({sources_str})</b>")
|
msg_lines.append(f"\n📊 <b>预报 ({sources_str})</b>")
|
||||||
msg_lines.append(f"👉 <b>今天: {today_t}{temp_symbol}{comp_str}</b>")
|
msg_lines.append(f"👉 <b>今天: {today_t}{temp_symbol}{comp_str}</b>{divergence_warning}")
|
||||||
|
|
||||||
# 明后天
|
# 明后天
|
||||||
if len(dates) > 1:
|
if len(dates) > 1:
|
||||||
|
|||||||
+4415
File diff suppressed because one or more lines are too long
@@ -0,0 +1,17 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
def save_nyc_mb():
|
||||||
|
url = "https://www.meteoblue.com/en/weather/week/40.713N74.006W"
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
proxies = {"http": "http://127.0.0.1:7890", "https": "http://127.0.0.1:7890"}
|
||||||
|
try:
|
||||||
|
resp = requests.get(url, headers=headers, proxies=proxies, timeout=10)
|
||||||
|
with open("nyc_mb.html", "w", encoding="utf-8") as f:
|
||||||
|
f.write(resp.text)
|
||||||
|
print("Saved nyc_mb.html")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
save_nyc_mb()
|
||||||
@@ -535,27 +535,47 @@ class WeatherDataCollector:
|
|||||||
从 Meteoblue 网页抓取多模型预测数据的“共识”最高温
|
从 Meteoblue 网页抓取多模型预测数据的“共识”最高温
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
# 1. 构造精确的方向后缀 URL
|
||||||
|
lat_dir = "N" if lat >= 0 else "S"
|
||||||
|
lon_dir = "E" if lon >= 0 else "W"
|
||||||
|
# Meteoblue 坐标 URL 习惯:保留 3 位小数,使用大写方向后缀
|
||||||
|
coord_str = f"{abs(lat):.3f}{lat_dir}{abs(lon):.3f}{lon_dir}"
|
||||||
tz_slug = timezone_name.replace("/", "%2F")
|
tz_slug = timezone_name.replace("/", "%2F")
|
||||||
url = f"https://www.meteoblue.com/en/weather/week/{lat}N{lon}E8_{tz_slug}"
|
|
||||||
|
# 使用 forecast/week 路径通常比直接 week 更稳定
|
||||||
|
url = f"https://www.meteoblue.com/en/weather/week/{coord_str}"
|
||||||
|
if tz_slug:
|
||||||
|
url += f"_{tz_slug}"
|
||||||
|
|
||||||
headers = {
|
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",
|
"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"
|
"Accept-Language": "en-US,en;q=0.9",
|
||||||
|
"Referer": "https://www.meteoblue.com/"
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.session.get(
|
response = self.session.get(
|
||||||
url,
|
url,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
timeout=self.timeout
|
timeout=self.timeout,
|
||||||
|
allow_redirects=True # 允许重定向,但我们要检查终点
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
content = response.text
|
content = response.text
|
||||||
|
|
||||||
|
# 2. 检查是否重定向到了错误的地方 (例如大阪 Osaka)
|
||||||
|
# 如果 URL 里有 W 但页面内容里全是 E 或者城市名完全对不上
|
||||||
|
if lon < 0 and "W" in coord_str:
|
||||||
|
if "osaka" in response.url.lower():
|
||||||
|
logger.warning(f"⚠️ Meteoblue 重定向异常: 目标 {coord_str} 却返回了 {response.url}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 3. 提取最高温
|
||||||
match = re.search(r'Today.*?tab-temp-max.*?(\d+) °C', content, re.DOTALL | re.IGNORECASE)
|
match = re.search(r'Today.*?tab-temp-max.*?(\d+) °C', content, re.DOTALL | re.IGNORECASE)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"source": "meteoblue",
|
"source": "meteoblue",
|
||||||
"url": url,
|
"url": response.url,
|
||||||
"today_high": None,
|
"today_high": None,
|
||||||
"daily_highs": [],
|
"daily_highs": [],
|
||||||
"unit": "celsius"
|
"unit": "celsius"
|
||||||
@@ -568,7 +588,7 @@ class WeatherDataCollector:
|
|||||||
val = float(match.group(1))
|
val = float(match.group(1))
|
||||||
result["today_high"] = c_to_f(val) if use_fahrenheit else val
|
result["today_high"] = c_to_f(val) if use_fahrenheit else val
|
||||||
|
|
||||||
# 同时提取接下来几天的最高温
|
# 同时提取接下来几天
|
||||||
all_highs = re.findall(r'tab-temp-max.*?(\d+) °C', content, re.DOTALL)
|
all_highs = re.findall(r'tab-temp-max.*?(\d+) °C', content, re.DOTALL)
|
||||||
if all_highs:
|
if all_highs:
|
||||||
if use_fahrenheit:
|
if use_fahrenheit:
|
||||||
@@ -577,7 +597,7 @@ class WeatherDataCollector:
|
|||||||
result["daily_highs"] = [float(h) for h in all_highs]
|
result["daily_highs"] = [float(h) for h in all_highs]
|
||||||
|
|
||||||
result["unit"] = "fahrenheit" if use_fahrenheit else "celsius"
|
result["unit"] = "fahrenheit" if use_fahrenheit else "celsius"
|
||||||
logger.info(f"✅ Meteoblue 抓取成功: 今天 {result['today_high']}{result['unit']}")
|
logger.info(f"✅ Meteoblue 抓取成功 ({coord_str}): 今天 {result['today_high']}{result['unit']}")
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Meteoblue fetch failed: {e}")
|
logger.error(f"Meteoblue fetch failed: {e}")
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import requests
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def test_mb_nyc():
|
||||||
|
lat = 40.7128
|
||||||
|
lon = -74.0060
|
||||||
|
timezone_name = "America/New_York"
|
||||||
|
|
||||||
|
lat_dir = "N" if lat >= 0 else "S"
|
||||||
|
lon_dir = "E" if lon >= 0 else "W"
|
||||||
|
tz_slug = timezone_name.replace("/", "%2F")
|
||||||
|
|
||||||
|
# Try both ways
|
||||||
|
url1 = f"https://www.meteoblue.com/en/weather/week/{lat}N{lon}E8_{tz_slug}"
|
||||||
|
url2 = f"https://www.meteoblue.com/en/weather/week/{abs(lat)}{lat_dir}{abs(lon)}{lon_dir}8_{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"
|
||||||
|
}
|
||||||
|
proxies = {"http": "http://127.0.0.1:7890", "https": "http://127.0.0.1:7890"}
|
||||||
|
|
||||||
|
for url in [url1, url2]:
|
||||||
|
print(f"\nTesting URL: {url}")
|
||||||
|
try:
|
||||||
|
resp = requests.get(url, headers=headers, proxies=proxies, timeout=10)
|
||||||
|
print(f"Status: {resp.status_code}")
|
||||||
|
content = resp.text
|
||||||
|
match = re.search(r'Today.*?tab-temp-max.*?(\d+) °C', content, re.DOTALL | re.IGNORECASE)
|
||||||
|
if match:
|
||||||
|
print(f"Captured High: {match.group(1)}°C")
|
||||||
|
else:
|
||||||
|
all_highs = re.findall(r'tab-temp-max.*?(\d+) °C', content, re.DOTALL)
|
||||||
|
print(f"All Highs Found: {all_highs}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
|
||||||
|
test_mb_nyc()
|
||||||
Reference in New Issue
Block a user