
简介面向气象数据分析和Python初学者这份《使用Python批量下载ECMWF欧洲中心数据》教程包系统讲解如何借助ECMWF官方API批量获取气象再分析与模式预报数据。资源共5个文件包含可执行的Python下载脚本、图文并茂的Word操作教程、ECMWF API客户端压缩包、Python 3.6安装程序以及环境说明文本整体大小约29.53MB脚本与教程搭配便于边看边练。已有5686人学习示例代码覆盖API密钥管理、请求参数拼装、JSON解析、文件保存与重命名、异常拦截、tqdm进度条展示以及多线程并发加速等关键技术可直接改写应用于定时更新、区域裁剪等场景。另外附带的安装包和客户端库能大幅降低环境配置门槛适合气象科研人员、预报工程师和学生快速搭建自己的批量下载工具。1. 用Python批量下载ECMWF欧洲中心数据为什么你还在手动点网页做气象、气候或环境建模的人迟早会撞上同一个问题需要的数据不在硬盘里而在ECMWF欧洲中心那个数据仓库里。手动在网页上一层层点菜单、选日期、选变量一份文件几十MB下几十份就得熬几个通宵关键是网页还时不时超时断开断了下半截你要重新点一遍。用Python批量下载ECMWF欧洲中心数据本质就是把网页上那套交互动作打包成脚本一次提交几十个请求自动排队、自动重试、自动落盘。这篇笔记面向的是要拉长时间序列数据做分析、或者给模型喂数据集的人新手能跟着跑通第一份文件熟手可以拿我踩过的坑做对照。2. 拿到下载权限API Key配好批量下载才不是空谈很多人上来就写脚本结果第一步就卡在认证上。ECMWF欧洲中心的数据下载走的是CDS API不是普通HTTP直连你在网页上登录的账号密码不能直接用必须单独生成一个API Key并写到本地配置文件里。2.1 注册与账号套路你得先在ECMWF欧洲中心官网注册一个账号然后在个人中心的API Keys页面点生成系统会给你一个下载地址和一把Key格式类似UID:xxxxx-xxxx-xxxx。注意这里生成的是密钥不是登录密码别把两者搞混。常见做法是拿到Key后马上在终端敲下面这行命令测试连通性curl https://cds.climate.copernicus.eu/api/v2 -v -u UID:xxxxx-xxxx-xxxx如果返回HTTP 200或者认证错误信息说明网络通路没问题如果卡住长时间不响应可能是网络到该服务有延迟需要换一个网络环境再试。我一般会把这个URL存到笔记里因为后面所有脚本配置都和它相关。2.2 CDS API配置这个配置文件决定了你的下载下限下载库会默认读取用户主目录下的.cdsapirc文件里面存URL和Key。用文本编辑器创建它Windows用户注意路径是C:\Users\你的用户名\.cdsapircLinux和macOS是~/.cdsapirc。没有这个文件脚本跑起来直接报认证错误。url: https://cds.climate.copernicus.eu/api/v2 key: UID:xxxxx-xxxx-xxxx参数说明第一行是API端点第二行是UID加Key的组合中间用冒号分隔。这里有个容易踩的细节——key前面的UID:前缀不能丢很多人复制Key时只复制后半段丢掉前缀认证永远失败。配置完成后用Python验证一下import cdsapi client cdsapi.Client() print(配置没问题)如果能正常打印说明配置生效。注意这里千万不要把Key粘贴到代码里写死因为.cdsapirc已经在本地库会自己去读。把Key写死在脚本里一旦项目发布或备份上传到公开仓库Key就泄露了这是最低级的翻车。3. 单条请求跑通用Python脚本下载第一个数据文件配置好环境后我们先用单条请求跑通整个链路再谈批量。这个阶段的目标是能从CDS拉回来一个真实的NetCDF文件并且能看懂请求参数是什么意思。3.1 安装依赖cdsapi不是唯一选择Python侧最常用的库是cdsapi它封装了提交请求、轮询状态、下载文件这一套流程。直接用pip安装pip install cdsapi安装完后做一个导入测试避免跑着跑着才发现环境不对。你可能会问能不能直接用requests库调API可以但要自己处理认证签名、请求状态轮询和二进制的分片下载cdsapi把这三件事都封装了没必要自己重造轮子。另外注意这个库一直在更新接口偶尔有变动装完后用pip show cdsapi看一眼版本老版本可能不支持新数据集。3.2 请求参数解析别把数据集名写错单条下载的核心是构造请求字典。以最常用的ERA5再分析数据为例脚本长这样import cdsapi client cdsapi.Client() dataset reanalysis-era5-single-levels request { product_type: [reanalysis], variable: [2m_temperature], year: [2020], month: [01], day: [01], time: [00:00], area: [50, 10, 40, 20], format: netcdf, } target era5_t2m_20200101.nc client.retrieve(dataset, request, target)参数说明dataset是数据集名称必须和CDS网站上的一致少一个字母都会报数据集不存在variable是要下载的变量可以传列表area是经纬度裁剪范围依次是北纬、西经、南纬、东经很多人在太平洋区域会写反东西方向。format建议用netcdf后续用xarray处理最顺手。这段代码里面retrieve不是一个立刻下载的操作而是先提交请求CDS会把这个请求放进队列等处理后返回一个下载链接然后库自动把文件写到target指定的路径。如果你运行后发现一直卡在request submitted但没进度说明请求在服务端排队不是脚本卡死。3.3 本地落地从请求到文件的完整链路上面那个脚本跑完你会得到一个NetCDF文件。但注意CDS返回的netcdf并不意味着文件里一定是标准网格数据尤其是如果你用了area裁剪文件里可能会有一些边界上的不规则网格。落地后用xarray验证一下文件能不能正常打开import xarray as xr ds xr.open_dataset(era5_t2m_20200101.nc) print(ds)能正常打印变量维度和属性这单条请求就算通了。这一步别嫌麻烦很多批量脚本跑了一半才发现第一批下载的文件全是坏的那时候定位问题成本就高了。为什么强调先跑通单条因为批量下载里每一个请求都是单条请求的拷贝如果单条请求的参数有错批量相当于一次性提交了几十个错误请求不仅白白消耗配额还会因为连续失败被限流。我一般会先用一条最精简的请求验证数据集名、变量名和格式对不对确认无误后再放大到批量。4. 批量下载脚本的设计循环、重试与断点续传单条请求通了批量下载就只是把单条请求放进循环里。但真正落地的批量脚本远不止一个for循环还要考虑报错恢复和排队限流。4.1 按日期/压力层批量构造请求最常见的批量场景是固定区域和变量下载连续多年的月度数据。把时间列表化循环构造请求import cdsapi client cdsapi.Client() dataset reanalysis-era5-single-levels years [2015, 2016, 2017] months [f{m:02d} for m in range(1, 13)] for year in years: for month in months: request { product_type: [reanalysis], variable: [2m_temperature], year: [year], month: [month], day: [f{d:02d} for d in range(1, 32)], time: [00:00, 06:00, 12:00, 18:00], format: netcdf, } target fera5_t2m_{year}{month}.nc client.retrieve(dataset, request, target) print(f完成{target})逻辑说明外层按年循环内层按月循环每个请求下载一个月内所有天、四个时次的温度数据。这么拆分主要是为了方便断点续传——如果中间某个月失败只需要重跑那个月不需要整个列表重来。参数说明day这里用了列表表达式生成1到31号CDS会自动跳过不存在该日期的月份比如2月30号会自动忽略。time列表里的四个时次对应一天四次的再分析输出如果后续做日平均这里可以只取一个时次来减小体积。4.2 并发下载的边界循环串行下载虽然稳但特别慢。有人会想用多线程或协程同时提交多个请求。CDS其实允许并发但它的并发不是无限度的同一时刻每个用户的活动下载请求有上限超出部分会排队等待。盲目开几十个线程很可能触发429限流。我一般会用两到三个并发做平衡而且用concurrent.futures写一个简单的线程池import cdsapi from concurrent.futures import ThreadPoolExecutor, as_completed def download_one(key): year, month key request { variable: [2m_temperature], year: [year], month: [month], day: [f{d:02d} for d in range(1, 32)], time: [12:00], format: netcdf, } target fera5_t2m_{year}{month}.nc client cdsapi.Client() client.retrieve(reanalysis-era5-single-levels, request, target) return target keys [(2015, 01), (2015, 02), (2015, 03), (2015, 04)] with ThreadPoolExecutor(max_workers3) as executor: futures {executor.submit(download_one, k): k for k in keys} for future in as_completed(futures): print(f完成{future.result()})这里有个细节每个线程里单独创建了一个Client实例而不是所有线程共用一个。因为cdsapi.Client内部有请求状态标记共用一个实例在多线程里会互相覆盖容易拿到错误的下载链接。这是很多人并发翻车的根源。参数说明max_workers3表示同时最多三个请求在跑。如果你发现三个并发仍然经常排队可以降到2如果服务端响应快再慢慢往上加但不要超过5。4.3 断点续传即使加了并发批量下载仍然会偶发失败。最实用的兜底方案是每次下载前先判断目标文件是否已经存在且大小不为0文件存在就跳过。这样哪怕跑了一半崩了重启脚本它会自动跳过已完成的文件。import os target fera5_t2m_{year}{month}.nc if os.path.exists(target) and os.path.getsize(target) 0: print(f跳过{target}) continue # 否则执行下载 client.retrieve(dataset, request, target)这个判断逻辑看起来简单但它是整个批量脚本的核心保险。配上上面的按月循环哪个月的文件缺了重启脚本后只补缺的效率高很多。唯一要注意的是不要用retrieve返回的元数据来判断是否成功因为请求排进队列时就有返回但文件可能还没落地。5. 避坑指南ECMWF下载中五个高频翻车现场批量下载ECMWF欧洲中心数据最磨人的不是代码而是服务端各种不可控的行为。下面五条是我反复踩过的坑每条都按现象、原因、解决来写。5.1 现象429限流脚本跑着跑着突然弹出一堆HTTP Error 429: Too Many Requests。原因是你提交请求的频率太高或者同时活跃的下载任务超过了允许的额度。解决方式分两个层面一是降低并发把max_workers降到1或2二是每次提交请求之后加随机延时避免所有请求在同一秒发出去import time import random time.sleep(random.uniform(1, 3))注意429还有一个隐藏触发性连续多次请求失败后服务端会临时封禁一段时间。如果你看到429不要立刻重启脚本硬刚先等几分钟再继续。5.2 现象请求排队一直pending提交请求后retrieve长期不进入下载阶段日志一直是request submitted。这可能是因为CDS后台默认的调度策略是先来先服务某个大请求占住了资源后续小请求都在等。解决方法是把请求拆小比如原来一个请求包含一整年12个月的数据拆成12个请求每个只包含一个月。虽然请求数变多了但单个请求的处理时间短排队反而更快。另外检查一下request里的area是否设置了过大的区域全片区域下载的计算量比小区域大一两个量级排队时间自然更长。5.3 现象下载一半连接断开有时候进度条跑了一半脚本直接报连接中断或者文件停在半截。这通常发生在网络不稳定或文件特别大的场景。解决方法是给retrieve调用套一层重试配合断点续传for attempt in range(3): try: client.retrieve(dataset, request, target) break except Exception as e: print(f第{attempt1}次失败{e}) time.sleep(5) else: print(f仍然失败{target})注意CDS的下载链接是一次性的如果连接断开无法从断点继续只能重新走一遍请求——这就回到了第4.3节的续传设计先判断本地文件是否存在存在且大小大于0就直接跳过否则重新下载。所以重试逻辑要配合文件大小检查而不是盲目重试。5.4 现象数据文件是坏的下载完成了文件大小也正常用xarray打开时却报错。原因可能是下载过程中网络闪断导致文件损坏但CDS返回的状态码是成功的也可能是format设置与目标文件后缀不匹配比如你设置了format: netcdf但下载下来的内容其实是GRIB格式。解决方式是批量下载完成后统一校验一遍所有文件能否正常打开import glob import xarray as xr for f in sorted(glob.glob(era5_*.nc)): try: xr.open_dataset(f).load() print(fOK: {f}) except Exception as e: print(f损坏: {f}, 删除并重新下载) import os os.remove(f)这一步放到批量脚本最后执行能把坏文件筛出来。注意不要用文件大小作为唯一判断标准有些文件大小正常但内部网格数据不完整打开时才会暴露。5.5 现象配额不够账号默认有一个月的下载额度批量下载大文件时很容易超限。现象是请求提交直接失败提示超过配额。解决方式是提前在CDS官网页面查看剩余配额并在脚本里统计每次下载的文件大小。我一般会做一个本地累积统计下载前估算当天剩余量total_size 0 for f in glob.glob(era5_*.nc): total_size os.path.getsize(f) print(f已下载总量{total_size / 1024 / 1024:.1f} MB)如果配额快见底优先保证关键月份的数据把不太重要的月份往后排或者直接改用format: grib再本地转成NetCDF——GRIB文件体积通常比NetCDF小一些能省不少配额。6. 之后的提速技巧怎么让批量下载再快一点等批量脚本稳定跑起来后你可能会嫌弃它还是慢。其实CDS的提速空间主要在请求策略上而不是网络带宽。一个有效的做法是一次请求里多放几个变量。很多人习惯一个变量一个请求比如温度下一个、降水下一个这样既浪费配额又增加下载次数。其实ERA5允许在同一个请求里指定多个variable一次性下载所有需要的气象要素文件体积虽然变大但请求数量和计算总耗时显著下降。比如我下载做模型输入的数据会把温度、风场、湿度、气压全部放进去request { variable: [ 10m_u_component_of_wind, 10m_v_component_of_wind, 2m_temperature, mean_sea_level_pressure, ], year: [2020], month: [07], day: [f{d:02d} for d in range(1, 32)], time: [00:00, 06:00, 12:00, 18:00], format: netcdf, }另一个实用技巧是请求里加grid参数把网格精度调粗。如果你做的是气候统计而不是高分辨率模拟不需要ERA5原始0.25度网格可以直接在下载时重采样到1度grid: [1.0, 1.0],这样文件体积能缩小到原来的十几分之一下载速度和配额消耗都友好得多。代价是后续不能再反演高分辨率信息所以下载前想清楚用途。还有一个我自己习惯用的验证技巧下载完一批文件后不要立刻删掉临时脚本保留一个download_log.txt把每次成功下载的文件名和时间记下来。这样下次重新跑的时候可以直接根据日志判断哪些月份需要补下而不是靠手工数文件。从那次因为日志不清导致重复下载了三个月的数据、白耗配额之后我每次写批量脚本都会强制走一遍「文件名大小日志」三重确认。希望这套经验能帮你在拉数据时少走弯路把时间留在分析本身。本文还有配套的精品资源点击获取