简介:面向气象数据分析和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\你的用户名\.cdsapirc,Linux和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 = f"era5_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 = f"era5_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_workers=3) 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_workers=3表示同时最多三个请求在跑。如果你发现三个并发仍然经常排队,可以降到2;如果服务端响应快,再慢慢往上加,但不要超过5。
4.3 断点续传
即使加了并发,批量下载仍然会偶发失败。最实用的兜底方案是:每次下载前先判断目标文件是否已经存在,且大小不为0;文件存在就跳过。这样哪怕跑了一半崩了,重启脚本它会自动跳过已完成的文件。
import os target = f"era5_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"第{attempt+1}次失败:{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(f"OK: {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,把每次成功下载的文件名和时间记下来。这样下次重新跑的时候,可以直接根据日志判断哪些月份需要补下,而不是靠手工数文件。从那次因为日志不清导致重复下载了三个月的数据、白耗配额之后,我每次写批量脚本都会强制走一遍「文件名+大小+日志」三重确认。希望这套经验能帮你在拉数据时少走弯路,把时间留在分析本身。
本文还有配套的精品资源,点击获取