大概两年前,我第一次在课题组里接手处理ERA5数据的任务。导师给了一张变量清单,丢下一句话:“把这两年夏季的温度场和位势高度场处理一下。”我打开CDS官网那一排下拉菜单,当时就懵了。最让我迷的就是“reanalysis-era5-pressure-levels”这个数据集——它和“reanalysis-era5-single-levels”名字很像,但点进去之后硬是多出来一长串压力层数字。后来我花了一晚上才彻底想明白:这两个数据集一个描绘的是“从地面到高空的大气竖切面”,另一个只是“贴在地表的单层信息”。如果说single-levels是一张平面照片,那pressure-levels就是一叠从地面到平流层的CT切片。你要是研究天气系统、环流异常、高空急流、或者锋面过程,pressure-levels基本是绕不开的主食。
这篇文章就把我这两年和ERA5压力层数据打交道的过程好好整理一下,从再分析数据是什么,到怎么用Python下载,再到下载之后怎么处理、有哪些坑,一条龙讲清楚。适合刚接触气象数据的小白,也欢迎老手来评论区补充你自己踩过的雷。
1. ERA5再分析数据到底是什么,为什么气象从业者绕不开它
1.1 再分析不是单纯观测,也不是单纯模式,而是两者的“混合产物”
先解决一个最基本的问题:什么是再分析数据。
气象观测站再密集,也不可能覆盖每一寸土地。海洋上、高原上、沙漠里都有大片的观测盲区。如果直接拿插值方法去填补这些空白,得到的结果往往在动力上是不自洽的——风场和气压场对不上,温度和位势高度不匹配,分析天气系统会非常别扭。
再分析数据做的事情,就是把过去几十年积累的观测资料,包括地面站、探空、卫星、飞机报、浮标等等,统统喂给一个数值天气预报模式,用数据同化技术把观测“融合”进模式物理过程里,反推出一个符合大气运动规律、时间连续、空间完整的三维大气状态。你可以把它理解成“用现代模式重新复盘历史天气”,复盘结果把观测和物理规律焊在了一起。
ERA5就是ECMWF(欧洲中期天气预报中心)推出的第五代全球再分析产品。相比上一代ERA-Interim,它的水平分辨率从大约79公里提升到31公里,时间分辨率从6小时提升到1小时,垂直方向从60层增加到137层。这意味着它不仅空间细节更丰富,对快速演变的天气过程,比如对流爆发、急流波动,捕捉得更准。
需要注意的是,ERA5的实时产品(Real-Time)通常只滞后官方大约5天发布,适合做天气尺度的近期诊断;而经过完整质量控制的再分析版本会晚几个月更新,适合做气候统计和长期趋势分析。做研究论文的话,建议优先使用完整质量控制版本,而不是实时产品,后者遇到底层观测资料调整时数值可能会有细微改动。
1.2 压力层和单层数据怎么快速区分
我经常被问到:“为什么下载ERA5时有两个选项,pressure-levels和single-levels,我该选哪个?”区分方式其实非常直接:
- single-levels描述的是大气在某一特定边界或下边界上的状态,最典型的就是地面气压、2米温度、10米风、海表温度、降水和积雪,这些变量本身不具备三维垂直结构。
- pressure-levels则是把大气在垂直方向上按气压面拆开,用37层固定气压面,从1000 hPa一直往上到1 hPa,来描写大气的三维结构。温度、水平风、比湿、位势高度这些核心变量,在每一层都会有一个完整的水平场。
举一个生活化的类比:给一栋楼拍照。single-levels只拍楼门口和一楼大厅,pressure-levels则是从地下室到顶楼每隔几层就拍一张平面图,再把所有照片叠起来,看整栋楼的立体结构。
如果你研究的对象依赖高度变化,比如远距离水汽输送、季风演变、高空槽脊、平流层与对流层相互作用,那就需要用pressure-levels。如果只关心“地表附近发生了什么”,single-levels就够了。
| 对比维度 | single-levels | pressure-levels |
|---|---|---|
| 垂直结构 | 无,只代表某一层或地面 | 37层气压面,三维立体 |
| 常见变量 | 2米温度、10米风、降水、海表温度 | 温度、位势高度、风、比湿、垂直速度 |
| 典型用途 | 地表过程、降水统计、边界层诊断 | 环流分析、天气系统三维结构、垂直剖面 |
| 数据体积 | 相对小 | 相对大,要多一个level维度 |
1.3 37层气压面:从1000 hPa到1 hPa
ERA5压力层数据提供了37个标准气压面,从1000、975、950、925、900、850……一直到100、70、50、30、20、10、7、5、3、2、1 hPa。有些老手会提到ERA5其实有137层混合坐标,但那是模式原始层面,标准压力层产品只暴露这37层。
气象分析中特别常用的是这几个层次:
- 850 hPa,大约距离地面1500米,处于对流层低层,经常用来分析低空急流、水汽输送和锋面结构。
- 700 hPa,大约3000米,中层水汽通量分析常用。
- 500 hPa,大约5500米,中纬度高空槽脊分析的核心层,日常说的“500百帕形势图”就是这一层。
- 200 hPa,大约12000米,接近对流层顶,分析高空急流和辐散场最常用。
- 100 hPa,平流层下层,研究平流层对流层交换、准两年振荡QBO时会用到。
知道这些层次对应的天气学意义,比死记高度数字更重要。气压面坐标的好处是“靠得住”,在天气学里它是最自然的垂直坐标系。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 下载之前,先把这几件事想明白
要说下载ERA5最容易被低估的环节,不是写代码,而是准备工作。账号没注册、环境没配好、需求清单没列清,会浪费大量等待时间。这节讲我总结出来的三件必做事项。
2.1 注册CDS账号,拿到API密钥
ERA5数据托管在哥白尼气候变化服务的气候数据商店(CDS)上。想用Python拉数据,第一步是注册CDS账号,并接受数据许可协议。
实际流程是:
- 打开CDS官网,用邮箱注册账号,完成邮箱验证。
- 登录后进入个人账户页面,找到API key区域,里面会显示一个url和一个key。旧版key通常是“UID:UUID”的组合,新版可能是一段更长的token。
- 把url和key保存好,这是你访问数据的唯一凭证。
用Python调用CDS API时,需要配置一个~/.cdsapirc文件,内容类似:
code复制url: https://cds.climate.copernicus.eu/api
key: 你的UID:你的API密钥
如果你登录的是新版界面,面板里生成的那套密钥可能和要求填写的URL都跟旧版不一样,一切以官网个人面板显示为准。不同登录方式生成的key不一定通用,不要把旧版key硬塞到新版流程里。
2.2 本地Python环境搭建
下载和处理ERA5数据主要用到下面几个库:
cdsapi:和CDS服务器交互、提交下载请求的官方Python客户端。xarray:处理带多维坐标的netCDF数据的核心工具。netcdf4:让xarray能读取netCDF格式文件的底层驱动。matplotlib:绘图基础库。cartopy:画带地图投影和海岸线的气象地图。
安装命令如下,推荐用conda或venv建一个独立环境,避免和其他项目互相污染:
bash复制conda create -n era5 python=3.10
conda activate era5
pip install cdsapi xarray netcdf4 matplotlib cartopy
注意cdsapi的版本迭代比较快。旧版用client.retrieve(name, params, target)的写法,新版推荐链式调用。如果发现老脚本报参数个数错误,先别怀疑逻辑,pip install -U cdsapi更新到最新版,按新版语法改写通常就能解决。
2.3 动手前先列一张“数据需求清单”
这步看着简单,却最能救你命。
我亲历过一次教训:中午着急用数据,随便在网页上点了个覆盖全球、十年逐小时的地面温度下载请求,结果那个请求在队列里排了整整两天,文件下下来好几十GB,硬盘差点塞满。
后来我每次下载之前都会先写一张清单,至少包含:
- 数据时间范围:起始和结束日期,具体到月、日、时。
- 变量列表:temperature、geopotential、u/v风、specific_humidity等。
- 垂直层:研究近地面就选850/925/1000 hPa,研究高空急流就选200/250/300 hPa,要完整垂直剖面就全选37层。
- 区域范围:全球还是某一个经纬度矩形。
- 格式:netCDF或GRIB。
- 网格:用原始31公里网格,还是重采样到1°或2°网格。
这张清单写完,你的请求体积通常能缩小一个数量级。
3. 核心实操:用Python提交下载请求并拿到数据
这节是整篇操作密度最高的部分。我会从最小可运行脚本讲起,再拆解request里每个字段的含义,最后给出批量下载多年数据的写法。
3.1 最小可用的下载脚本
假设你需要下载2023年1月1日00时,500 hPa层上的全球温度场,保存成netCDF格式:
python复制import cdsapi
client = cdsapi.Client()
client.retrieve(
'reanalysis-era5-pressure-levels',
{
'product_type': 'reanalysis',
'variable': 'temperature',
'pressure_level': '500',
'year': '2023',
'month': '01',
'day': '01',
'time': '00:00',
'data_format': 'netcdf',
},
'era5_t500_20230101_00.nc'
)
这个请求提交后,CDS服务器会先校验参数,然后进入任务队列,处理完成后执行下载。脚本会保持等待状态,直到文件真正落到本地。文件不大,通常几分钟内能跑完。
新手需要记住:year、month、day、time在上面的写法里是单个字符串,但它们其实支持列表形式。想下载整个月每天00时的数据,可以直接写:
python复制'year': '2023',
'month': '01',
'day': ['01', '02', '03', ..., '31'],
'time': '00:00',
CDS会自动把这些条件做笛卡尔积组合,生成31个时次再打包成一个文件,不需要你写循环请求31次。这个特性非常实用,能极大降低请求次数。
3.2 request里的每个字段在说什么
我见过不少人的请求总失败,就是因为对字段含义理解不全。逐个过一下:
product_type:一般填reanalysis,即标准再分析产品。ensemble_members是集合成员,用于扰动分析;ensemble_mean是集合平均,日常研究用得不多。variable:要的物理量,可以填多个。比如同时要温度和风,写['temperature', 'u_component_of_wind', 'v_component_of_wind']。pressure_level:压力层,可以填多个,也可以把所有层级都放进去。year/month/day/time:时间维度,支持多值列表。需要特别注意,day指自然日,time指UTC时间,国内做业务分析时要换算到北京时间,也就是UTC+8。area:区域裁剪,格式是[北纬, 西经, 南纬, 东经]。注意不是所有平台都用这个顺序,写错的话数据区域会颠倒。data_format:netcdf或grib。grid:如果不想要ERA5原始31公里网格,可以重采样成自定义经纬度步长,比如[1.0, 1.0]表示1°×1°网格。这个参数对快速预览特别友好。
下面是一个完整的下载示例,同时下载多个变量、多个层次、多个时次,并裁剪到指定区域:
python复制import cdsapi
client = cdsapi.Client()
client.retrieve(
'reanalysis-era5-pressure-levels',
{
'product_type': 'reanalysis',
'variable': [
'temperature',
'geopotential',
'u_component_of_wind',
'v_component_of_wind'
],
'pressure_level': [
'850', '500', '250', '200'
],
'year': '2020',
'month': '07',
'day': ['01', '02', '03', '04', '05'],
'time': ['00:00', '12:00'],
'area': [60, 70, 20, 140],
'data_format': 'netcdf',
},
'era5_case_2020jul.nc'
)
这个请求会下载2020年7月1日至5日每天00和12两个时次、4层、4个变量的区域数据,文件大小大概几十MB,非常适合做一次完整的入门验证。
CDS官方在数据集页面提供一个Request Volume计算器,提交之前可以先预估一下请求体积,心里有底。
3.3 批量下载多年数据的实战写法
当时间跨度变大,比如一次下载1980到2020年逐月数据,再怎么组合也很难塞进一个请求。我的经验是“按一年一文件,或者一季节一文件”来拆分。
python复制import cdsapi
import calendar
client = cdsapi.Client()
for year in range(1980, 2021):
for month in range(1, 13):
days = calendar.monthrange(year, month)[1]
client.retrieve(
'reanalysis-era5-pressure-levels',
{
'product_type': 'reanalysis',
'variable': 'geopotential',
'pressure_level': '500',
'year': str(year),
'month': f'{month:02d}',
'day': [f'{d:02d}' for d in range(1, days + 1)],
'time': '00:00',
'data_format': 'netcdf',
'area': [60, 70, 30, 100],
},
f'era5_z500_{year}_{month:02d}.nc'
)
print(f'{year}-{month:02d} submitted')
几个细节说明:
calendar.monthrange用来算每个月有几天,避免手动写天数错误。- 每次请求保存成一个独立文件,文件名带上年月信息,方便断点续传,也避免单文件过大。
- CDS下载请求是异步的:脚本里
client.retrieve(...)提交后会等待服务器处理,一个请求完成才提交下一个,所以脚本可能得长时间挂着。 - 更健壮的做法是维护一个任务队列,循环检查请求状态,或者用官方异步客户端,但顺序提交对多数人已经够用。
批量下载还有一个隐藏逻辑:单个请求体积超限时,服务器会直接拒绝。拆小任务不只是为了排队快,更是为了把单个请求体积控制进安全范围。我的经验是,对全球数据而言,单次请求的变量数、层数和时次数的乘积,最好控制在2000以内。如果37层全选、时次多,哪怕只有一天,体积也会非常夸张,建议按变量拆分。
3.4 下载提速的两个小技巧
第一,善用grid参数做快速预览。要先看某一天的环流形势,没必要拿31公里原始网格全量下载,直接在request里加'grid': [2.0, 2.0],把数据重采样到2°网格,文件瞬间缩小几十倍。跑通整个流程之后,再下高分辨率版本做正式分析。
第二,把area参数真正用起来。很多人习惯“先下全球,再本地裁剪”,这个思维要改。直接在请求里用area参数裁剪,文件变小,排队和下载时间都会明显缩短,还省掉本地裁剪的步骤。
4. 文件拿到手之后:xarray读取、单位换算和第一张图
下载不是终点,处理才是真正开始的地方。这节用一段完整的代码串起读取、单位换算、绘制简图的流程。
4.1 用xarray打开netCDF文件,先看清结构
拿到一个下载好的.nc文件,第一件事不是急着画图,而是先看清楚数据长什么样:
python复制import xarray as xr
ds = xr.open_dataset('era5_case_2020jul.nc')
print(ds)
你会看到一个典型的xarray Dataset结构,包含:
dimensions: time, level, latitude, longitudecoordinates: time, level, latitude, longitudedata_vars: 实际的变量数组
level是一个整型数组,表示各压力层的气压值,单位hPa。latitude和longitude分别从北到南、从西到东排列。time是ISO8601格式的UTC时间。
我第一次拿数据时踩过一个小坑:直接把ds['t']拿去画图,发现图像经纬度方向反了。后来才意识到ERA5的netCDF文件里纬度是从高纬度到低纬度排列的,也就是从90°N到-90°S,和某些数据集正好相反。处理时要留意。
4.2 变量和单位:默认数据的“陷阱”
ERA5原始netCDF文件里的变量名经常是缩写形式,t代表温度,z代表位势,u和v代表水平风速分量,q代表比湿。CDS变量列表里能看到全称和短名的对应关系,但实际文件里是短名。
更重要的是单位换算:
t单位是开尔文(K),换算成摄氏度要减273.15。z单位是m²/s²,这是位势,不是几何高度。要换算成常用的位势高度,单位是位势米gpm,需要除以重力加速度g = 9.80665。u、v单位是m/s,直接用。q单位是kg/kg,一般数值在0.001量级,处理时注意量纲。
位势高度换算的例子:
python复制z = ds['z'] / 9.80665 # 转成gpm
这一步经常被忽略。我见过有人拿原始z值直接画图,数值是几万甚至几十万,整张图完全没法看。
4.3 快速画一张500 hPa位势高度图
来一个最经典的操作:画500 hPa位势高度场。
python复制import matplotlib.pyplot as plt
import xarray as xr
ds = xr.open_dataset('era5_case_2020jul.nc')
z500 = (ds['z'] / 9.80665).sel(level=500, time='2020-07-01T00:00:00')
fig, ax = plt.subplots(figsize=(10, 6))
contour = ax.contourf(
z500['longitude'], z500['latitude'], z500,
levels=20, cmap='RdYlBu_r'
)
ax.contour(
z500['longitude'], z500['latitude'], z500,
levels=10, colors='k', linewidths=0.5
)
plt.colorbar(contour, ax=ax, label='Geopotential Height (gpm)')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
plt.title('500 hPa Geopotential Height')
plt.show()
这个图虽然没加地图投影,但已经能直观看出等高线的槽脊分布。如果想让底图更像气象业务图,需要引入cartopy:
python复制import cartopy.crs as ccrs
fig = plt.figure(figsize=(12, 6))
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines()
ax.gridlines(draw_labels=True)
cf = ax.contourf(
z500['longitude'], z500['latitude'], z500,
levels=20, cmap='RdYlBu_r', transform=ccrs.PlateCarree()
)
ax.contour(
z500['longitude'], z500['latitude'], z500,
levels=10, colors='k', linewidths=0.8, transform=ccrs.PlateCarree()
)
plt.colorbar(cf, ax=ax, label='gpm', shrink=0.7)
加上投影之后,海岸线和经纬线比例正常,能比较真实地还原环流形势,也方便叠加站点或者路径信息。
5. 那些年我踩过的坑:排队、NaN和内存爆炸
这节专门记录真实踩坑经验。每一项都是我自己或者身边同事实际遇到过的。
5.1 请求一直显示“queued”,等了大半天没动静
这是最让人血压升高的一幕。原因主要有几种:
第一,CDS每天的请求量很大,高峰期排长队是正常现象。我有过连续十小时还在排队的时候。解决方法是避开欧美用户集中的高峰时段,或者把大请求拆小。拆小后的请求通常排在前面。
第二,请求体积太大,系统会认为你占用公共资源比较多,排队优先级降低。我试过把一个变量的全球逐小时数据全塞进一个请求,结果它在队列里待了两天。拆小之后,最长排队时间没超过十五分钟。
第三,本地网络连接不稳定,下载过程中一直卡在某个百分比。这种情况建议用client.retrieve返回的对象显式调用.download(),并且在脚本里加重试逻辑,连接断了就重新发起请求。
5.2 请求成功,但下载下来的文件打不开,或者打开后全是NaN
这种问题通常出在请求参数上。我犯过一次:选了data_format: 'grib',之后拿xarray直接打开,结果全是NaN。原因很简单,xarray原生读不了GRIB格式,需要额外安装cfgrib库。
更隐蔽的一个坑是grid参数。设置重采样网格之后又设置area,两者之间的交互和边界定义可能导致局部区域的值缺失,尤其当经纬度格点恰好落在边界上时。建议下载后先print(ds)看维度大小,再取样几个坐标值检查数据是否有效。
还有一种小概率情况:选择的level列表和variable列表的某种组合里,某些变量在特定层上本来就不存在。比如部分模式输出变量无法到标准压力层,就会造成文件里有维度但数据全为NaN。遇到这种情况,去CDS官方文档的变量可用性表格里查一下。
5.3 内存不足,系统直接死掉
ERA5全球数据体积不小。即便只下载某一个时次、某一层的全球温度,解压后的数组也很大。如果变量和层次多,再用xarray一次性加载到内存,8GB内存很容易直接吃不消。
我的几个实用对策:
- 下载时就裁剪好区域,别等本地再裁。
- 利用
xr.open_dataset的懒加载机制,先从文件读索引,用.sel()选择需要的时间层,最后.load()到内存。 - 不用把所有变量都常驻内存,算完一个就释放一个。
- 如果非得处理大文件,考虑用dask配合xarray做分块计算,比如
xr.open_dataset('file.nc', chunks={'time': 24}),按时间块逐步计算。
5.4 同一个请求代码,过几个月再跑却出错
常见原因是CDS接口或数据集版本更新。ERA5的CDS服务偶尔有维护窗口,或者某个变量在某段时间因为源数据问题暂时停用。我遇到过脚本去年还能跑通,今年重新提交却提示变量不可用。
解决办法是去CDS官网对应数据集的Download data选项卡,查看最新API文档和变量清单。数据集标识字段也可能从reanalysis-era5-pressure-levels变成别的形式。核心思路:不迷信老代码,定期核对官方文档。
6. 压力层数据的几种进阶玩法
最后这节,我从实际使用角度盘点几个高频的分析方向,给你写论文或者做项目提供一点线索。
6.1 用500 hPa位势高度看大尺度槽脊演变
500 hPa是做路径最多的“天气图传统层”。绘制500 hPa位势高度场并分析时间演变序列,可以清楚看到中纬度西风带的波动、阻塞高压的建立与崩溃、冷涡的移动路径。
我试过用ERA5的z500连续画一个夏季的逐日场,做成动画后,一眼看出某个冷涡的移动路径完全对应了那年一次持续性强降水过程。这种“从数据到过程”的解释能力,是再分析数据最大的价值之一。
6.2 做垂直剖面研究上下层配置
压力层数据的天然优势是垂直分辨力。把850 hPa和200 hPa的风场、温度差结合起来,可以分析对流层上下层的配置,比如高层辐散与低层辐合对应的上升运动。
比较常用的一个诊断是高层200 hPa散度与低层850 hPa垂直速度的配合。ERA5 pressure-levels里提供vertical_velocity变量,单位Pa/s,虽然是模式诊断量,但用来定性判断上升和下沉区域足够。把不同层的剖面拼在一起,能直观看到上升运动从低层到高层的演变。
6.3 和观测数据交叉验证
再分析数据再完善,也是模式加观测的估计,不是完全真实。做研究时把ERA5和探空、飞机观测、站点观测作对比是常规操作。比湿、温度的探空廓线与ERA5沿航迹或站点的插值廓线对比,能有效识别系统偏差。
用xarray沿着站点提取时间序列非常简单:
python复制# 提取某个站点位置上的整层温度廓线
site_temp = ds['t'].sel(latitude=39.9, longitude=116.4, method='nearest')
这个操作在ERA5上很友好,因为netCDF自带标准的经纬度网格。需要注意,站点与网格点的海拔高度差异可能会引入误差,山区尤其明显,对比时要考虑地形因素。
最后补一句实在话。我刚开始用压力层数据时,总想用一个请求把所有变量和所有层次全下回来,结果光排队就耗掉大半天。后来老老实实按变量拆分请求、按区域裁剪、按目标筛选层次,效率反而翻了不止一倍。ERA5的下载过程更像是在和CDS服务器合作维护一个公共数据池的使用纪律,你把请求拆得越合理,整体体验就越顺畅。希望这篇文章能帮你少走点弯路。
