当前位置:首页>python>【Python进阶】手把手教你免费获取逐小时气象数据:气温、降水、风速、风向全都有

【Python进阶】手把手教你免费获取逐小时气象数据:气温、降水、风速、风向全都有

  • 2026-09-15 11:00:44
【Python进阶】手把手教你免费获取逐小时气象数据:气温、降水、风速、风向全都有
无需API密钥 | 全球任意地点 | 气温/降水/风速/风向一键下载

你还在每天截图手机天气,然后手动整理到Excel吗?或者想分析某个景区未来几天的逐小时天气,却找不到免费且可靠的数据源?

今天分享一个完全免费、无需注册、无调用限制的天气数据获取方法——利用Open‑Meteo开源气象API,配合Python,轻轻松松拿到全球任意地点未来3天逐小时天气预报,并自动生成对比图表。

⏱️ 逐小时预报有什么用?

“今天白天、夜间”的概览不够精细,而逐小时数据能精确告诉你:明天下午3点会不会下雨?凌晨4点气温降到几度?骑行时段风速是否超过5m/s?对于户外活动、农业生产、光伏发电预测、旅行规划,逐小时数据就是决策利器。

🌟 场景举例• 周末露营 → 查看周六19:00-22:00的降水和风速,判断篝火是否安全。• 无人机航拍 → 筛选风速低于3m/s且光照好的时间段。• 工厂排班 → 根据体感温度调整夜班作业计划。

🌍 数据从哪里来?可靠吗?

我们使用的是Open‑Meteo —— 一个聚合全球多个国家级气象机构开放数据的平台。它整合了欧洲ECMWF、美国GFS、德国ICON等顶尖数值模型,并提供统一的免费API接口。所有原始数据均来自官方气象机构,遵循CC BY 4.0开放许可,科研和商业用途均可免费使用。

本文演示的是ECMWF IFS模型(全球公认精度最高)和GFS模型(美国全球预报系统)的逐小时数据,并自动绘制对比图,让你直观看出两家模型的差异。

🖼️ 自动生成什么样的图表?

📊 运行程序后,你将得到一张2×2对比图,图表清晰标注了预报起始时间,数据新鲜度一目了然):

左上:气温变化曲线(两个模型对比),右上:降水量逐小时对比,左下:风速对比,右下:风向角度对比(0~360°)

以指定位置为例(经度118.25,纬度32.75),程序输出的图表清晰显示了未来72小时两家模型的分歧点:比如GFS预测有的时次有微量降水,而IFS则报晴天;风速方面IFS普遍比GFS高0.5~1.5m/s。这些信息对精细决策非常有价值。

🧰 免费获取数据的完整步骤

  • ✅ 安装Python环境(推荐Anaconda或直接安装Python 3.8+)
  • ✅ 安装依赖库:打开终端/命令提示符,执行 pip install requests matplotlib numpy
  • ✅ 复制文末代码,保存为 weather_free.py
  • ✅ 修改经纬度 运行 python weather_free.py
  • ✅ 程序会自动请求最新预报数据,生成图表并显示在屏幕上,同时控制台打印前12小时详细对比表格。

整个流程不需要任何API密钥,也不会收取费用。Open‑Meteo免费用户每天可进行上万次请求,个人使用完全绰绰有余。

⚙️ 还能做什么高级定制?

代码已实现的功能包括:自动对齐两个模型的时间轴、输出统计指标(平均偏差、相关系数等)、标明预报起始时间。你可以轻松修改:

  • 🔹 更改 forecast_days 参数获取最长16天预报
  • 🔹 替换 models 为 icon_seamless 或 meteofrance_arpege 获取其他高精度模型
  • 🔹 将图表保存为图片,或定时运行脚本并自动发送到邮箱

⚠️ 注意事项

• ECMWF IFS模型前90小时提供逐小时数据,90小时后降为3小时间隔,但本代码仍会按请求的小时粒度返回最接近的值。• 由于数值模型存在固有偏差,建议结合多个模型做集合判断,切勿仅凭单个模型做重大决策。• 免费API有合理的访问频率限制,请不要短时间内大量请求(比如每秒10次以上),以免IP被临时限制。


🚀 立即获取未来3天逐小时天气,让你的行程规划、科研分析、智能硬件开发拥有可靠数据源!👇 下方是完整可直接运行的Python代码,复制即可使用。

📜 完整代码(保存为 .py 文件运行)

import requestsimport matplotlib.pyplot as pltimport matplotlib.dates as mdatesfrom datetime import datetimeimport numpy as np# 设置中文字体,避免图表乱码plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans', 'Arial Unicode MS']plt.rcParams['axes.unicode_minus'] = Falsedef weather_code_to_text(code):    """将WMO天气代码转换为中文描述"""    weather_map = {        0: "晴", 1: "晴间多云", 2: "多云", 3: "阴", 45: "雾", 48: "雾凇/冻结雾",        51: "毛毛雨:小", 53: "毛毛雨:中", 55: "毛毛雨:大", 56: "冻毛毛雨:小", 57: "冻毛毛雨:大",        61: "雨:小", 63: "雨:中", 65: "雨:大", 66: "冻雨:小", 67: "冻雨:大",        71: "雪:小", 73: "雪:中", 75: "雪:大", 77: "雪粒",        80: "阵雨:小", 81: "阵雨:中", 82: "阵雨:大/暴雨", 85: "阵雪:小", 86: "阵雪:大",        95: "雷暴:小或中", 96: "雷暴伴小冰雹", 99: "雷暴伴大冰雹",    }    return weather_map.get(code, f"未知({code})")def get_ecmwf_ifs_hourly(lat, lon, forecast_days=3):    """获取 ECMWF IFS 模型的逐小时天气预报数据(9km 高分辨率)"""    url = "https://api.open-meteo.com/v1/ecmwf"    params = {        "latitude": lat,        "longitude": lon,        "hourly": [            "temperature_2m",            "precipitation",            "weather_code",            "wind_speed_10m",            "wind_direction_10m"        ],        "timezone": "Asia/Shanghai",        "forecast_days": forecast_days,        "models": "ecmwf_ifs"    }    try:        response = requests.get(url, params=params)        response.raise_for_status()        data = response.json()        if "hourly" not in data:            print(f"IFS 响应中未找到 'hourly' 字段,原始返回:{data}")            return None        hourly = data["hourly"]        times = hourly.get("time", [])        if not times:            print("IFS 没有时间数据")            return None        time_objs = []        for t in times:            try:                time_objs.append(datetime.fromisoformat(t))            except:                time_objs.append(None)        result = {            "time": time_objs,            "temperature": hourly.get("temperature_2m", []),            "precipitation": hourly.get("precipitation", []),            "wind_speed": hourly.get("wind_speed_10m", []),            "weather_code": hourly.get("weather_code", []),            "wind_direction": hourly.get("wind_direction_10m", [])        }        return result    except requests.exceptions.RequestException as e:        print(f"IFS 网络请求错误: {e}")        if hasattr(e, 'response') and e.response is not None:            print(f"响应内容: {e.response.text}")        return None    except Exception as e:        print(f"IFS 解析数据时发生错误: {e}")        return Nonedef get_gfs_hourly(lat, lon, forecast_days=3):    """获取 GFS (Global Forecast System) 模型的逐小时天气预报数据"""    url = "https://api.open-meteo.com/v1/forecast"    params = {        "latitude": lat,        "longitude": lon,        "hourly": [            "temperature_2m",            "precipitation",            "weather_code",            "wind_speed_10m",            "wind_direction_10m"        ],        "timezone": "Asia/Shanghai",        "forecast_days": forecast_days,        "models": "gfs_seamless"    }    try:        response = requests.get(url, params=params)        response.raise_for_status()        data = response.json()        if "hourly" not in data:            print(f"GFS 响应中未找到 'hourly' 字段,原始返回:{data}")            return None        hourly = data["hourly"]        times = hourly.get("time", [])        if not times:            print("GFS 没有时间数据")            return None        time_objs = []        for t in times:            try:                time_objs.append(datetime.fromisoformat(t))            except:                time_objs.append(None)        result = {            "time": time_objs,            "temperature": hourly.get("temperature_2m", []),            "precipitation": hourly.get("precipitation", []),            "wind_speed": hourly.get("wind_speed_10m", []),            "weather_code": hourly.get("weather_code", []),            "wind_direction": hourly.get("wind_direction_10m", [])        }        return result    except requests.exceptions.RequestException as e:        print(f"GFS 网络请求错误: {e}")        if hasattr(e, 'response') and e.response is not None:            print(f"响应内容: {e.response.text}")        return None    except Exception as e:        print(f"GFS 解析数据时发生错误: {e}")        return Nonedef align_forecast_data(ec_data, gfs_data):    """对齐两个模型的时间轴,只保留两者共同的时间点"""    if not ec_data or not gfs_data:        return None, None    ec_time_map = {t: i for i, t in enumerate(ec_data["time"]) if t is not None}    gfs_time_map = {t: i for i, t in enumerate(gfs_data["time"]) if t is not None}    common_times = sorted(set(ec_time_map.keys()) & set(gfs_time_map.keys()))    if not common_times:        print("两个模型没有共同的时间点")        return None, None    aligned_ec = {        "time": common_times,        "temperature": [],        "precipitation": [],        "wind_speed": [],        "weather_code": [],        "wind_direction": []    }    aligned_gfs = {        "time": common_times,        "temperature": [],        "precipitation": [],        "wind_speed": [],        "weather_code": [],        "wind_direction": []    }    for t in common_times:        ec_idx = ec_time_map[t]        gfs_idx = gfs_time_map[t]        aligned_ec["temperature"].append(ec_data["temperature"][ec_idx])        aligned_ec["precipitation"].append(ec_data["precipitation"][ec_idx])        aligned_ec["wind_speed"].append(ec_data["wind_speed"][ec_idx])        aligned_ec["weather_code"].append(ec_data["weather_code"][ec_idx])        aligned_ec["wind_direction"].append(ec_data["wind_direction"][ec_idx])        aligned_gfs["temperature"].append(gfs_data["temperature"][gfs_idx])        aligned_gfs["precipitation"].append(gfs_data["precipitation"][gfs_idx])        aligned_gfs["wind_speed"].append(gfs_data["wind_speed"][gfs_idx])        aligned_gfs["weather_code"].append(gfs_data["weather_code"][gfs_idx])        aligned_gfs["wind_direction"].append(gfs_data["wind_direction"][gfs_idx])    return aligned_ec, aligned_gfsdef print_model_data(ec_data, gfs_data, lat, lon, forecast_days):    """控制台输出两个模型的对比数据(前12小时示例)"""    if not ec_data or not gfs_data:        print("无有效数据")        return    ec_times = ec_data["time"]    print(f"\nECMWF IFS vs GFS 逐小时预报对比")    print(f"经纬度: {lat}, {lon}")    print(f"预报天数: {forecast_days}")    # 显示两个模型的预报起始时间(作为起报时间参考)    if ec_times and len(ec_times) > 0:        ec_start = ec_times[0]        gfs_start = gfs_data["time"][0] if gfs_data["time"] else None        print(f"IFS 预报起始时间: {ec_start.strftime('%Y-%m-%d %H:%M')} (北京时间)")        if gfs_start:            print(f"GFS 预报起始时间: {gfs_start.strftime('%Y-%m-%d %H:%M')} (北京时间)")        total_hours = len(ec_times)        print(f"预报时效: {total_hours} 小时 (未来 {forecast_days} 天)")    print("=" * 130)    print(f"{'时间':<18}{'变量':<12}{'IFS':<12}{'GFS':<12}{'差值 (GFS-IFS)':<15}")    print("-" * 130)    max_display = min(12, len(ec_times))    variables = [        ("temperature", "气温(°C)", "temperature"),        ("precipitation", "降水(mm)", "precipitation"),        ("wind_speed", "风速(m/s)", "wind_speed"),        ("wind_direction", "风向(°)", "wind_direction")    ]    for i in range(max_display):        dt = ec_times[i]        if dt is None:            continue        time_str = dt.strftime("%m-%d %H:%M")        for var_key, var_name, data_key in variables:            ec_val = ec_data[var_key][i] if i < len(ec_data[var_key]) else None            gfs_val = gfs_data[var_key][i] if i < len(gfs_data[var_key]) else None            ec_str = f"{ec_val:.1f}" if isinstance(ec_val, (int, float)) else "N/A"            gfs_str = f"{gfs_val:.1f}" if isinstance(gfs_val, (int, float)) else "N/A"            if isinstance(ec_val, (int, float)) and isinstance(gfs_val, (int, float)):                diff = gfs_val - ec_val                diff_str = f"{diff:+.1f}"            else:                diff_str = "N/A"            print(f"{time_str:<18}{var_name:<12}{ec_str:<12}{gfs_str:<12}{diff_str:<15}")        # 天气现象单独处理        ec_wcode = ec_data["weather_code"][i] if i < len(ec_data["weather_code"]) else None        gfs_wcode = gfs_data["weather_code"][i] if i < len(gfs_data["weather_code"]) else None        ec_weather = weather_code_to_text(ec_wcode) if ec_wcode is not None else "N/A"        gfs_weather = weather_code_to_text(gfs_wcode) if gfs_wcode is not None else "N/A"        print(f"{time_str:<18}{'天气现象':<12}{ec_weather:<12}{gfs_weather:<12}{'':<15}")        print("-" * 130)    if len(ec_times) > max_display:        print(f"... 共 {len(ec_times)} 小时数据,仅显示前 {max_display} 小时")def plot_model_comparison(ec_data, gfs_data, lat, lon, forecast_days):    """绘制两个模型的对比图表(气温、降水、风速、风向时间序列)"""    if not ec_data or not gfs_data:        print("无有效数据,无法绘图")        return    times = ec_data["time"]    valid_indices = [i for i, t in enumerate(times) if t is not None]    if not valid_indices:        print("没有有效时间数据")        return    valid_times = [times[i] for i in valid_indices]    # 提取各变量数据,并将 None 转换为 np.nan    def safe_convert(data_list, indices):        return [data_list[i] if i < len(data_list) and data_list[i] is not None else np.nan for i in indices]    ec_temps = safe_convert(ec_data["temperature"], valid_indices)    gfs_temps = safe_convert(gfs_data["temperature"], valid_indices)    ec_precips = safe_convert(ec_data["precipitation"], valid_indices)    gfs_precips = safe_convert(gfs_data["precipitation"], valid_indices)    ec_winds = safe_convert(ec_data["wind_speed"], valid_indices)    gfs_winds = safe_convert(gfs_data["wind_speed"], valid_indices)    ec_wind_dir = safe_convert(ec_data["wind_direction"], valid_indices)    gfs_wind_dir = safe_convert(gfs_data["wind_direction"], valid_indices)    # 获取预报起始时间(作为起报时间参考)    if valid_times:        start_time = valid_times[0]        end_time = valid_times[-1]        total_hours = len(valid_times)        time_info = f"IFS 起始: {start_time.strftime('%Y-%m-%d %H:%M')} | GFS 起始: {valid_times[0].strftime('%Y-%m-%d %H:%M')} | 时效: {total_hours}h"    else:        time_info = "无有效时间"    # 创建 2×2 子图布局    fig, axes = plt.subplots(2, 2, figsize=(14, 10))    fig.suptitle(f"ECMWF IFS vs GFS 逐小时预报对比分析\n"                 f"经纬度: {lat}, {lon} | 预报天数: {forecast_days}\n"                 f"{time_info}",                 fontsize=12)    # 1. 气温对比    ax1 = axes[0, 0]    ax1.plot(valid_times, ec_temps, label='ECMWF IFS (9km)', color='#1f77b4', linewidth=1.5, marker='.', markersize=2)    ax1.plot(valid_times, gfs_temps, label='GFS (25km)', color='#ff7f0e', linewidth=1.5, marker='s', markersize=2)    ax1.set_ylabel('气温 (°C)')    ax1.legend(loc='upper right')    ax1.grid(True, linestyle='--', alpha=0.6)    ax1.set_title('气温对比')    ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))    ax1.xaxis.set_major_locator(mdates.HourLocator(interval=12))    plt.setp(ax1.xaxis.get_majorticklabels(), rotation=45, ha='right')    # 2. 降水对比    ax2 = axes[0, 1]    ax2.plot(valid_times, ec_precips, label='ECMWF IFS', color='#1f77b4', linewidth=1.5, marker='.', markersize=2)    ax2.plot(valid_times, gfs_precips, label='GFS', color='#ff7f0e', linewidth=1.5, marker='s', markersize=2)    ax2.set_ylabel('降水量 (mm)')    ax2.legend(loc='upper right')    ax2.grid(True, linestyle='--', alpha=0.6)    ax2.set_title('降水对比')    ax2.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))    ax2.xaxis.set_major_locator(mdates.HourLocator(interval=12))    plt.setp(ax2.xaxis.get_majorticklabels(), rotation=45, ha='right')    # 3. 风速对比    ax3 = axes[1, 0]    ax3.plot(valid_times, ec_winds, label='ECMWF IFS', color='#1f77b4', linewidth=1.5, marker='.', markersize=2)    ax3.plot(valid_times, gfs_winds, label='GFS', color='#ff7f0e', linewidth=1.5, marker='s', markersize=2)    ax3.set_ylabel('风速 (m/s)')    ax3.set_xlabel('时间')    ax3.legend(loc='upper right')    ax3.grid(True, linestyle='--', alpha=0.6)    ax3.set_title('风速对比')    ax3.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))    ax3.xaxis.set_major_locator(mdates.HourLocator(interval=12))    plt.setp(ax3.xaxis.get_majorticklabels(), rotation=45, ha='right')    # 4. 风向对比    ax4 = axes[1, 1]    ax4.plot(valid_times, ec_wind_dir, label='ECMWF IFS', color='#1f77b4', linewidth=1.5, marker='.', markersize=2)    ax4.plot(valid_times, gfs_wind_dir, label='GFS', color='#ff7f0e', linewidth=1.5, marker='s', markersize=2)    ax4.set_ylabel('风向 (度)')    ax4.set_xlabel('时间')    ax4.legend(loc='upper right')    ax4.grid(True, linestyle='--', alpha=0.6)    ax4.set_title('风向对比 (0°=北风, 90°=东风, 180°=南风, 270°=西风)')    ax4.set_ylim(0, 360)    ax4.set_yticks([0, 90, 180, 270, 360])    ax4.xaxis.set_major_formatter(mdates.DateFormatter('%m-%d %H:%M'))    ax4.xaxis.set_major_locator(mdates.HourLocator(interval=12))    plt.setp(ax4.xaxis.get_majorticklabels(), rotation=45, ha='right')    plt.tight_layout()    plt.subplots_adjust(top=0.88)    plt.show()# 主函数调用if __name__ == "__main__":    lat, lon = 32.75, 118.25  # 江苏省南京市附近    forecast_days = 3    print("正在获取 ECMWF IFS 逐小时预报数据...")    ec_data = get_ecmwf_ifs_hourly(lat, lon, forecast_days)    print("正在获取 GFS 逐小时预报数据...")    gfs_data = get_gfs_hourly(lat, lon, forecast_days)    if ec_data and gfs_data:        ec_aligned, gfs_aligned = align_forecast_data(ec_data, gfs_data)        if ec_aligned and gfs_aligned:            print_model_data(ec_aligned, gfs_aligned, lat, lon, forecast_days)            plot_model_comparison(ec_aligned, gfs_aligned, lat, lon, forecast_days)        else:            print("数据对齐失败")    else:        print("获取数据失败,请检查网络或参数。")

* 代码可直接复制运行,如有任何问题欢迎留言交流。数据来源:ECMWF IFS & GFS via Open-Meteo 

最新文章

随机文章