
做气象、气候、环境研究的朋友大概率都绕不开ERA-5这套再分析数据。我最早接触ERA-5是要整理一段连续多年的降水序列去做趋势分析当时第一个想法就是“这种官方数据肯定有个统一下载入口”结果一查发现ECMWF提供的是CDSClimate Data Store平台配套有一个叫cdsapi的Python库。研究半天把流程跑通之后最大的感受是下载ERA-5这件事本身不难难的是把那些隐藏的规则搞清楚——比如API Key怎么配、区域范围怎么写、变量代码去哪查、批量下载怎么做到断点续传。这篇就把我实际操作中验证过的方法完整写出来目标读者是刚接触ERA-5、需要用Python批量取数的科研人员和工程师你能从零配好环境再到写脚本批量下载最后避开那些坑。1. 需求分析与项目思路1.1 先说清楚ERA-5到底是个什么东西ERA-5是ECMWF欧洲中期天气预报中心发布的第五代全球再分析资料时间覆盖从1940年至今空间分辨率约0.25度大约31公里输出频率可以到小时级。你可以把它理解成一份用数值模式和数据同化技术“重新回放”出来的全球大气历史档案——它把过去几十年分散的观测数据气象站、探空、卫星、浮标等统一融合进一个物理一致的网格场里使得任意地点、任意时刻的大气状态都能被“复盘”。对科研和工程来说这意味着你没有自己的观测站也能拿到一份连续、均一、覆盖全球的气象数据用来驱动水文模型、分析极端天气、验证气候模式都是常规操作。再分析数据不是实测但比单纯的卫星反演或插值产品更可靠因为它有完整的动力学约束。这也是它被广泛用作“基准数据”的原因。你在论文里写“本研究使用ERA-5数据”审稿人基本不会质疑数据的权威性但他们会关心你是怎么下载、怎么处理、怎么保证时间一致性的——这就回到了我们这篇文章的主题。1.2 为什么选官方CDS接口而不是第三方下载服务早期下载ERA-5很多人会去第三方网站找打包好的数据比如某些高校的镜像站、百度网盘分享的大文件包甚至有人会去爬一些抓好的GRIB文件。我不建议你这么干理由有三个第一数据时效性没法保证。ERA-5是滚动更新的今天下载的数据和三个月前下载的数据可能在同一时间戳上存在细微差异新版本会用更优的输入数据和同化方案回溯更新第三方打包的往往是旧版本而且你不知道它哪一天断了更新。第二可定制性差。你通常只需要某个区域、某些变量、某几个气压层第三方包往往是全球全变量全层的一个文件几十GB光存储就是负担。通过官方接口按需切取你只需要下载自己关心的那一小块磁盘占用小几个数量级。第三毕竟是学术数据来源可追溯、方法可复现很重要。用官方API配合脚本你能完整记录下载了哪个数据集、哪个时间层、哪个区域写方法学的时候清清楚楚。有的期刊审稿人会要求你提供数据获取方式用官方接口就好交代得多。所以我的结论很明确官方CDS API cdsapi库是下载ERA-5的正确打开方式。虽然偶尔会遇到请求排队、连接中断等问题但整体可靠性是第三方渠道完全没法比的。1.3 核心需求拆解一次完整的下载你需要做哪些决策在动笔写代码之前最好先把需求拆清楚。我一般会按照下面这个清单快速过一遍避免后期反复修改请求参数浪费时间。选数据集ERA-5按“产品类型”分很多种最常见的是单层数据reanalysis-era5-single-levels和气压层数据reanalysis-era5-pressure-levels。单层数据适用于地表变量2米气温、降水量、10米风速、海平面气压等气压层数据才有不同高度上的温压湿风比如500hPa位势高度。这个选择直接决定数据集的名称和可用的变量代码千万别搞混。选变量去ECMWF官方的变量列表页面查代码比如2米气温的代码是2m_temperature总降水量是total_precipitation。变量代码看起来像变量名但又不完全等同于常见的缩写复制粘贴最稳妥不要手打。选时空范围时间上要明确年份、月份、日、小时ERA-5完整数据是逐小时的空间上要明确是全球还是某个经纬度矩形区域。区域范围直接决定返回文件的大小建议能裁剪就裁剪。选网格和格式ERA-5本身是0.25度规则网格但API允许你重采样到更粗的网格比如0.5度或1度。输出格式一般建议选netCDF因为Python生态里xarray对netCDF支持最好如果你要直接用气象可视化工具GRIB也行但处理起来麻烦一点。这些决策看似琐碎但每一个都关系到后续数据能不能直接用、文件会不会大到爆盘。接下来我会把每个环节的实操细节都过一遍。2. 环境准备与依赖安装2.1 Python环境的首选方案做数据下载和处理我建议直接用Anaconda/Miniconda建一个独立环境别往base环境里乱装东西。一个干净的conda环境能让你在出问题时快速重建不至于把整个系统搞乱。如果你还没装PythonMiniconda是最省事的去官网下载对应系统的安装包一路默认安装即可。装好后打开终端Windows下是Anaconda Prompt新建一个专门的环境我用的是conda create -n era5 python3.11 -y conda activate era5Python版本选3.9到3.12都行cdsapi对版本要求不严格但太老的版本比如3.7以下可能依赖装不上太新的版本3.13刚出的时候某些科学计算库还没适配。3.11是我目前实测最稳的。2.2 安装cdsapi与数据处理配套库下载ERA-5的核心库就是cdsapi它是ECMWF官方为CDS接口写的Python客户端。安装方式很简单pip install cdsapi后面处理数据大概率要用到xarray、netCDF4、pandas这些一并装上pip install xarray netCDF4 pandas这里有个小技巧如果你所在网络环境访问官方Python源速度一般可以临时更换成国内镜像源比如清华源。命令是pip install cdsapi xarray netCDF4 pandas -i https://pypi.tuna.tsinghua.edu.cn/simple镜像源解决的是下载速度问题和后面CDS接口的访问速度无关两码事别混在一起想。2.3 配置CDS API凭据.cdsapirc文件的正确姿势这一步是新手最容易卡住的地方。cdsapi调用接口时需要一个密钥这个密钥不是写在代码里而是放在用户目录下的一个叫.cdsapirc的配置文件里。具体操作先去CDS官网cds.climate.copernicus.eu注册账号、登录。登录后点击页面右上角的头像进入个人主页找到“API key”这一栏你会看到两行信息一行是url一行是keyurl形如https://cds.climate.copernicus.eu/apikey形如你的uid:一串很长的字符串。把这两行原样写进配置文件url: https://cds.climate.copernicus.eu/api key: 你的uid:你的API KeyWindows上的路径是C:\Users\你的用户名.cdsapircLinux/macOS是~/.cdsapirc。注意key字符串以uid开头冒号后面是一长串十六进制字符这串东西相当于你的账号密码别贴到代码仓库里也别发给别人。我有一次不小心把key写进了一个公开的GitHub仓库意识到后赶紧上CDS重新生成了一组这种事能免则免。写完配置文件后可以用一行代码验证是否成功import cdsapi client cdsapi.Client()如果这一步没报错说明凭据配置成功如果报HTTP 403大概率是key复制错了或者配置文件路径不对。3. 核心实操从写脚本到拿到数据3.1 最简版本下载单年单月单变量的全球数据先从一个最简单但能完整跑通的例子开始。假设我要下载2023年1月1日0点时刻的全球2米气温单层数据要netCDF格式脚本如下import cdsapi client cdsapi.Client() client.retrieve( reanalysis-era5-single-levels, { product_type: reanalysis, variable: 2m_temperature, year: 2023, month: 01, day: 01, time: 00:00, grid: 0.25/0.25, format: netcdf }, download_2t_20230101.nc )这个脚本里retrieve方法的第一个参数是数据集名称第二个参数是一个字典描述了数据请求的完整条件第三个参数是保存到本地的文件名。运行之后控制台会输出你的请求已经提交并给你一个请求ID然后进入排队状态等待CDS服务器处理完成后自动开始下载。这个最简版本虽然能跑但实际应用场景很少——因为大多数人不可能只需要某一个小时的全球数据。但它是理解整套流程的最小单元后面的复杂请求都是在这个字典上做加法。3.2 批量下载时间循环的正确打开方式假设你要下载2015年到2023年每年1月、2月的日均2米气温区域限定在中国大陆东部纬度20到45经度95到125那就要在请求里加区域裁剪并循环提交。关于区域参数CDS API里的写法是area: [北, 西, 南, 东]注意是“北纬、西经、东经、南纬”的顺序写成[45, 95, 20, 125]。这个顺序我每次都要确认一遍因为很多人的习惯是先列四个角容易搞反。北、西、南、东想象成一个矩形先写上边界再写左边界再写下边界再写右边界就不会错了。批量下载的脚本框架import cdsapi import os client cdsapi.Client() years [2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023] months [01, 02] area [45, 95, 20, 125] # 北, 西, 南, 东 for year in years: for month in months: filename fera5_t2m_{year}_{month}.nc if os.path.exists(filename): print(f{filename} already exists, skip) continue client.retrieve( reanalysis-era5-single-levels, { 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], area: area, grid: [0.25, 0.25], format: netcdf }, filename )这里有两个细节值得说明。第一day和time的值用列表时表示“所有这些日期的所有这些时刻都下载”比如day给全部31天time给4个时刻一个月的文件里就包含了31×4124个时间切片。如果你给的day是1号到10号那只有10天。这种写法比逐日循环提交要高效得多——请求次数少服务器排队也少最终文件是合并好的。第二我在循环开头加了一个文件是否已存在的判断。这个看似简单的小技巧在下载大量月份时能救命——CDS请求偶尔会超时或失败重跑脚本时已经下好的文件可以自动跳过不用从头再来。后续我会在断点续传部分详细展开。3.3 多变量与气压层数据一次请求里放多个参数很多场景要的不止一个变量。比如做蒸发估算需要2米气温、2米露点温度、地表气压、10米风速做天气分析要500hPa位势高度、850hPa气温、海平面气压。CDS的API允许你在一次请求里申请多个变量多个气压层也是一样。单层多变量的写法把variable从字符串改成列表variable: [2m_temperature, 2m_dewpoint_temperature, surface_pressure, 10m_u_component_of_wind, 10m_v_component_of_wind]气压层数据的请求则不同数据集名称变成reanalysis-era5-pressure-levels变量的值通常填的是比如geopotential位势、temperature气温、u_component_of_wind纬向风这种“没有高度属性”的变量名高度信息用pressure_level单独指定client.retrieve( reanalysis-era5-pressure-levels, { product_type: reanalysis, variable: [geopotential, temperature], pressure_level: [500, 850, 925], year: 2023, month: 01, day: 01, time: 00:00, area: area, grid: [0.25, 0.25], format: netcdf }, download_pl_20230101.nc )注意单层数据里可能也有一个叫temperature的变量但那是2米气温气压层数据的temperature才是各个高度层的温度。两者不要搞混。气压层数据的变量代码里geopotential对应的是位势不是位势高度如果你需要位势高度要么自己除以重力加速度要么用geopotential再转一次。多变量请求的好处是变量之间天然对齐在同一个时间和网格上后续做计算比如比湿、假相当位温这些需要多个变量组合的量时特别方便不用自己去对齐时间轴。4. 常见问题与排查技巧实录4.1 HTTP 400/403错误请求被拒绝实际使用中遇到的第一类报错就是HTTP 4xx。403通常是API Key不对400则复杂一些常见的原因是参数名写错了、数据集名称不匹配、变量代码不存在。排查思路我总结成一句话逐项核对请求字典里的每个键值对。最典型的几个坑把单层数据集去请求气压层变量比如把geopotential塞进reanalysis-era5-single-levels里接口直接拒绝。变量代码多了个空格或下划线比如2m_temperature写成了2m_temperature_报错。pressure_level的值写成了字符串500hPa而不是500报错。日期格式错误比如月份写1而不是01有的接口能忍有的不能忍统一写成两位数最省心。CDS的错误信息其实写得比较清楚会告诉你具体是哪个键出了问题但新手常犯的一个错误是一看到报错就直接看最后一行的提示忽略了前面的错误详情。我的习惯是先把完整报错复制到一个文本里再把请求字典逐行对一遍90%的问题都能自己找出来。4.2 请求一直在队列里怎么回事这是使用CDS下载ERA-5最常见的等待问题。CDS是共享服务平台请求提交后会进入队列高峰期比如每年秋季开学前后、重大天气事件后等待时间可能从几分钟延长到几十分钟甚至更久这是正常现象不是因为你的请求卡死了。我对付排队问题有三个办法第一把大请求拆小。比如你一次请求十年的逐小时数据服务器需要处理的数据量非常庞大排队的优先级也不会高。拆成按月请求每个文件就几十MB到几百MB处理快优先级也高。实测下来小请求的等待时间通常只需几秒到几十秒。第二避开整点提交。别问我为什么我观测到的现象是每个整点附近的提交量明显增加可能是很多人设了定时任务。避开整点排队体验会好一些。第三如果你经常要做大量请求可以了解一下CDS的“脚本方式批量提交”机制用cdsapi的扩展功能把请求拆成多个子请求并行提交。注意不是让你开几百个线程去同时提交那样容易被平台限流甚至封禁账号。合理的方式是每次提交几个请求等一个完成后再提交下一批。4.3 下载大文件时连接中断怎么断点续传这是我在长期批量下载中遇到最多的另一个实际问题。一个包含多年逐小时数据的请求返回文件可能有十几个GB下载过程中一旦网络连接中断cdsapi默认是直接报错退出不会断点续传之前下载的部分全部作废非常恼人。我这里提供一个实际可用的断点续传思路核心是两步第一步让cdsapi的请求结果先保存到CDS服务器端获取一个资源链接再自己用分段下载的方式把文件拉下来。方法是在retrieve调用里传入一个downloadFalse参数这样不会直接下载而是返回一个Result对象里面包含下载链接import cdsapi client cdsapi.Client() result client.retrieve( reanalysis-era5-single-levels, { product_type: reanalysis, variable: 2m_temperature, year: 2023, month: 01, day: [01, 02, 03], time: 00:00, grid: [0.25, 0.25], format: netcdf } ) print(result.download_url)第二步拿到下载链接后用支持断点续传的下载工具比如Linux的wget配合-c参数或者写一个带分块请求的Python脚本从上次断开的位置继续下载。wget的用法很简单wget -c 下载链接 -O mydata.nc这里要特别说明下载链接是有时效性的通常是几十分钟到几小时过期后需要重新提交请求。所以拿到链接后尽快开始下载。如果文件特别大拆成小请求再分段下载是更稳的策略。另外cdsapi本身在新版本中也对下载稳定性做了优化比如增加了重试机制如果你只是偶尔下几个小文件直接用默认方式问题也不大。4.4 文件太大内存吃不消怎么办有时候好不容易把数据下载好了打开时发现一个19GB的netCDF文件xarray.open_dataset直接把内存吃了20多个GB机器当场卡死。我踩过这个坑之后总结了三条经验第一真正常用的数据量没有你想象的那么大。绝大多数分析根本不需要全部变量、全部小时、全球范围。在请求阶段就把范围裁剪好比事后处理省几百倍内存。下载前先估算一下一个变量、一天、全球、一个时刻按标准做法计算文件大约几十MB如果限定到中国区域就只有几MB到十几MB。先按小请求试一下文件大小再决定是否扩大范围。第二用xarray的chunk参数做延迟读取。如果你的数据已经下载好了又不想重新下可以用dask分块读取import xarray as xr ds xr.open_dataset(big_file.nc, chunks{time: 100})这样数据不会一次性全进内存需要计算哪部分就读取哪部分。第三netCDF和GRIB的底层数据结构都是自描述的你可以先用工具检查数据集的维度、变量和属性。比如用xarray打开的Dataset对象打印出来看看有哪些坐标和变量再决定是否需要进一步提取子集。很多时候你以为需要整个文件实际只需要其中某几个时次的某几个变量这时用ds.sel或者ds.isel截取后重新保存成小文件再释放大文件。4.5 变量单位容易忽略计算结果对不上ERA-5里有些变量的单位和我们日常习惯不太一样稍不注意就会在后续计算中出错。最典型的是降水量ERA-5的total_precipitation单位是米m表示的是单位面积上的等效水深不是毫米。你要是直接按毫米去解读数值会小1000倍得出完全错误的结果。正确做法是在读取后乘以1000转成毫米。另一个高频坑是温度2m_temperature单位是开尔文K下载下来是280多这样的数值很多做应用的人下意识以为是摄氏度不去减273.15后面的统计分析全偏了。再比如风速的u/v分量单位虽然是m/s但u代表纬向风正值为西风v代表经向风正值为南风。如果你要做风速大小记得要算sqrt(u^2v^2)而不是直接用某个分量。我在脚本里处理单位的习惯是每次下载完数据后先打印出变量的units属性用xarray可以这样查import xarray as xr ds xr.open_dataset(era5_t2m_2023_01.nc) for var in ds.data_vars: print(var, ds[var].attrs.get(units, no unit))这个习惯帮我避免了很多因为单位对不上导致的返工建议大家都养成。5. 数据质量与后续处理的经验之谈5.1 拿到文件后的第一步先做数据体检别急着分析数据下载成功只是一个开始。我在拿到任何一批新下载的ERA-5数据后不会直接开始分析而是先做一次“数据体检”整个流程大概十分钟但能省下后面几十个小时的debug时间。体检项目包括文件能否正常打开、维度大小是否符合预期、时间轴是否有缺失、空间范围是否正确、变量值域是否在合理区间。检查时间轴和空间范围用xarray非常方便import xarray as xr ds xr.open_dataset(era5_t2m_2015_01.nc) print(ds[time].min(), ds[time].max(), len(ds[time])) print(ds[latitude].min(), ds[latitude].max()) print(ds[longitude].min(), ds[longitude].max())如果请求的是一个月的逐6小时数据那么时间轴应该包含124个时次31天×4前后边界应该正好是月初和月末。如果时间点少了说明请求参数或者下载过程中出了问题趁早发现不要等到分析完才发现数据有明显缺口。值域检查也很直观2米气温在地球上不会低于-100摄氏度173K不会高于60摄氏度333K总降水量不可能是负值。如果数值超出物理合理范围那大概率是变量代码选错了或者混合了不同的产品类型。5.2 把netCDF转成更趁手的数据结构netCDF是科研数据标准格式但不是所有下游工具都能直接读。如果你需要把ERA-5数据转成CSV表格或者TXT文本比如要输入到某个地方水文模型里可以用pandas打平数据import xarray as xr import pandas as pd ds xr.open_dataset(era5_t2m_2015_01.nc) # 选择某一个经纬度点的时间序列 ts ds[t2m].sel(latitude30, longitude120, methodnearest) df ts.to_dataframe().reset_index() df.to_csv(t2m_point_30120.csv, indexFalse)这里需要说明两点一是所有变量名在netCDF里保存的通常是短名称如例子里的t2m有些数据集里叫2m_temperature以你下载文件里实际打印出来的为准二是如果选了methodnearest实际取到的是距离你指定经纬度最近的网格点不是精确插值如果你对精度要求高需要用插值算法处理。5.3 多个文件合并和长时间序列处理批量下载按年月拆分的好处是请求稳定但后续分析往往需要一个连续的长序列。用xarray合并多个文件有两种方式取决于你的数据结构。如果是同一次请求拆成的多个文件它们的时间变量不重叠直接用concat合并import xarray as xr files [fera5_t2m_{year}_{month}.nc for year in years for month in months] ds_list [xr.open_dataset(f) for f in files] ds xr.concat(ds_list, dimtime)如果你下载的是同一时间段的多个变量变量结构可能不同合并用mergeds xr.merge([ds_t2m, ds_precip, ds_pressure])这里有一个容易踩的细节ERA-5的经纬度网格是全球规则格点不同文件的时间和坐标通常是完全对齐的但如果请求参数里网格设置不一致比如一个文件是0.25度另一个是0.5度合并时会有坐标不匹配的冲突。解决办法是统一在请求阶段就使用完全相同的area、grid参数这是最省心的做法。5.4 关于数据版本的一点提醒ERA-5不是静态数据集ECMWF会根据新的再分析输入或同化版本对历史数据进行更新所以同一天提交的同一个“2023年1月”的请求在不同时期下载到的底层数据版本可能略有差异。对大多数研究和工程应用来说这种差异可以忽略但如果你要发表学术论文建议在方法部分写清楚下载日期和所使用的数据集版本号审稿人一般会认可。CDS平台在数据集的详情页会提供当前版本号信息下载时留意一下写进方法学里就没问题。6. 自动化与批量下载的进阶技巧6.1 用脚本文件管理请求参数而不是写一堆重复代码批量下载最容易失控的就是脚本变得越来越长这个变量改一下那个年份加一下。我的建议是把所有请求参数集中定义在一个地方甚至直接用配置文件来管理脚本只负责读取并提交。比如用yaml文件存请求参数dataset: reanalysis-era5-single-levels product_type: reanalysis variable: - 2m_temperature - total_precipitation years: [2015, 2016, 2017] months: [1, 2] area: [45, 95, 20, 125] grid: [0.25, 0.25] format: netcdfPython脚本读这个配置再生成具体的请求改参数时只动yaml文件不用改代码。这个方法在数据需求经常变化的场景下特别有用比如导师今天让你下华东区域的降水明天又让你改成西北区域的温度改配置文件会比改代码快得多。6.2 日志记录让下载过程可复盘下载大量数据时难免遇到中途报错、需要重跑的情况。如果没有日志你根本不知道哪些文件已经成功下载了。我的做法是在脚本里加一个简单的日志记录模块import logging logging.basicConfig( filenamedownload.log, levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s ) # 每次成功或失败都写一行日志 logging.info(f{filename} downloaded successfully) logging.error(f{filename} failed: {str(e)})配合前面提到的文件存在性检查即使脚本跑挂了重跑时也能自动跳过已完成的部分只处理失败的下载效率提升很明显。6.3 定时任务与监控长周期下载的省心方案如果下载任务非常大比如几十年的逐小时数据可能得跑几天甚至几周。这种情况下不建议人守着终端干等。可以把脚本放到服务器上用cronLinux或计划任务Windows定时跑配合日志记录和文件存在性检查实现无人值守断点续跑。我的经验是分三层第一层每个文件下载时记录状态第二层每次脚本运行时先扫描已有文件跳过已完成的第三层每天定时运行一次脚本只补当天未完成的部分。这样即使某天请求排队特别久或者网络中断第二天脚本会自动重试整个下载流程很省心。6.4 注意请求配额别把自己号搞封了CDS虽然免费但不是无限量的。它有一套请求队列和配额管理机制单账号短时间内提交大量请求可能会被限制甚至临时封禁。我的建议是控制单次请求的数据量不要一次性把十年的逐小时数据塞进一个请求控制提交频率批次之间加一点间隔比如sleep 1到2秒避免短时间内密集调用如果遇到HTTP 403或者请求被拒绝不要立刻重试先停下来查一下是不是触发了配额限制。从我自己长时间下载的经验看把请求拆成“每月一个文件”的粒度是最稳妥的单文件不大排队快失败了重跑代价低配额消耗也比较均匀。全局一次性请求也不是不行但文件大到一定量级后从排队到下载完成的整个周期可能长达数小时期间任何一次网络抖动都可能导致全部白费。写在最后的一点体会把ERA-5下载这条路彻底走通之后回头看最花时间的其实不是写代码而是搞清楚那些藏在文档角落里的“约定俗成”——比如area参数的顺序、变量单位的坑、请求队列的脾气。我可以负责任地说只要你把上面这些环节都处理明白了从零开始到拿到第一份可用数据不会超过一个小时。之后不管是做气候分析、水文模拟还是天气诊断这套下载流程都会成为你工具箱里一个稳定、可复用的起点。