1. 项目概述:从“数据饥渴”到精准获取

做气象、水文、生态或者农业模型的朋友,肯定对“数据饥渴”这个词深有体会。模型跑得再漂亮,算法写得再精妙,没有高质量、高时空分辨率的驱动数据,一切都是空中楼阁。这几年,欧洲中期天气预报中心(ECMWF)推出的ERA5-Land再分析数据集,几乎成了我们这些领域研究者的“标配”口粮。它提供了从1950年至今、全球范围、小时级、9公里分辨率的数十个地表变量,无论是做长时间序列的趋势分析,还是高精度的区域模拟,诱惑力都太大了。

然而,理想很丰满,现实往往卡在第一步: 下载 。ERA5-Land数据总量是PB级别的,对于特定研究,我们通常只需要某个区域、某个时间段、某几个变量。直接从官方渠道手动点点点,效率低下不说,还容易出错。更头疼的是网络稳定性,一个大文件下到90%断掉,那种心情可想而知。所以,一个稳定、高效、可复现的ERA5-Land数据自动化下载方案,不是“锦上添花”,而是“雪中送炭”的刚需。本文将彻底拆解如何利用ECMWF提供的官方工具和Python生态,构建一套属于你自己的、可靠的数据下载流水线。无论你是需要中国区域十年的逐日温度数据,还是非洲某河流域的月平均土壤湿度,这套方法都能让你像在本地读取文件一样,从容地获取全球数据。

2. 核心工具选型与认证配置

工欲善其事,必先利其器。下载ERA5-Land数据,主流且官方的途径是通过ECMWF的 气候数据存储(CDS)API 。别被“API”吓到,它的本质就是一个允许你用程序发送请求、然后后台帮你准备数据、最后推送下载链接的自动化服务。围绕这个核心,我们需要搭建一套工具链。

2.1 CDS API Key:你的数据通行证

一切始于在CDS官网注册账号并获取API密钥。这个过程是免费的,但需要一点耐心。

  1. 注册与登录 :访问ECMWF的CDS门户网站,用邮箱完成注册并登录。
  2. 定位API密钥 :登录后,在页面右上角用户菜单中找到“Your API key”或类似选项。
  3. 获取关键信息 :你会看到两行关键文本: url key url 通常是 https://cds.climate.copernicus.eu/api/v2 key 的格式是 [UID]:[API密钥] ,其中 UID 是一串数字。

注意 :这个 key 是你的私人凭证,相当于密码, 绝对不要 上传到公开的代码仓库(如GitHub)。一旦泄露,他人可以用你的配额下载数据,可能导致你的账号被限制或产生意外请求。

2.2 本地环境配置:让工具链就位

获取密钥后,需要在你的工作环境(个人电脑、服务器或高性能计算集群)上进行配置。

对于Linux/macOS或Windows的WSL环境: 通常推荐在用户家目录下创建配置文件。

# 在终端中执行
cd ~
vim .cdsapirc

然后将你的API信息写入这个文件,格式如下:

url: https://cds.climate.copernicus.eu/api/v2
key: [你的UID]:[你的API密钥]

保存退出后,这个配置就对当前用户生效了。

对于纯Windows环境或Python脚本内配置: 你可以不创建配置文件,而是在Python脚本中直接设置环境变量,这尤其适合在临时环境或容器中运行。

import os
os.environ[‘CDSAPI_URL’] = ‘https://cds.climate.copernicus.eu/api/v2‘
os.environ[‘CDSAPI_KEY’] = ‘[你的UID]:[你的API密钥]‘

2.3 Python库安装:核心引擎

配置好密钥后,就需要安装与CDS API交互的Python库。核心是 cdsapi

pip install cdsapi

这个库非常轻量,它主要负责帮你构建符合CDS API格式的请求,并处理后续的排队、状态查询和下载流程。除此之外,为了后续处理下载下来的NetCDF格式数据,我们通常还会安装 xarray netCDF4 库,它们能让你像操作多维数组一样轻松读写气象数据。

pip install xarray netCDF4

3. 数据请求参数深度解析

配置好环境只是拿到了钥匙,接下来要知道保险箱的密码——即如何准确描述你想要的数据。CDS API通过一个Python字典(dict)来接收请求参数,每一个键值对都至关重要。下面我们以最常用的几个需求为例,拆解这些参数。

3.1 时空范围定义:画好你的数据框

这是最基本,也最容易出错的部分。ERA5-Land数据是网格化的,你需要用经纬度和时间来界定范围。

产品类型 ( product_type ) : 对于ERA5-Land,通常选择 "reanalysis" (再分析数据)。

变量 ( variable ) : 这是核心。你需要从CDS官网的数据目录里找到确切的变量名。例如:

  • "2m_temperature" (2米气温)
  • "total_precipitation" (总降水量)
  • "volumetric_soil_water_layer_1" (第一层土壤体积含水量)
  • "surface_solar_radiation_downwards" (地表太阳辐射向下短波辐射) 一次请求可以申请多个变量,用列表表示: ["2m_temperature", "total_precipitation"]

时间 ( year , month , day , time ) : 这里的格式必须严格遵守。

  • year : 列表,如 ["2010", "2011", "2012"]
  • month : 两位数的字符串列表,如 ["01", "02", ..., "12"] ["01"] 只下载一月。
  • day : 两位数的字符串列表,如 ["01", "02", ..., "31"]
  • time : 24小时制,UTC时间,格式为 "HH:MM" 。对于小时数据,可以是 ["00:00", "01:00", ..., "23:00"] 。如果想下载日平均或日累计,CDS也提供了预处理选项,但更常见的做法是下载所有小时数据后本地计算,灵活性更高。

地理区域 ( area ) : 这是一个由4个数字组成的列表: [北纬, 西经, 南纬, 东经] 顺序固定,且是“北-西-南-东” 。例如,下载中国大部分区域的数据,可以定义为 [55, 70, 15, 140] 。这里有一个关键细节:ERA5-Land数据是0.1度×0.1度的规则网格,你请求的区域边界会自动对齐到最近的网格点。这意味着你实际下载的数据范围可能比你指定的区域稍微大一点点。

数据格式 ( format ) : 强烈推荐使用 "netcdf" 。NetCDF是气象和海洋领域的标准格式,被 xarray , netCDF4 等库完美支持,便于后续分析。

3.2 一个完整的请求字典示例

假设我们需要下载中国区域2018年全年的每日2米气温和降水数据(通过下载逐小时数据实现),请求字典如下:

request_params = {
    ‘product_type’: ‘reanalysis’,
    ‘variable’: [‘2m_temperature’, ‘total_precipitation’],
    ‘year’: [‘2018’],
    ‘month’: [‘01’, ‘02’, ‘03’, ‘04’, ‘05’, ‘06’, ‘07’, ‘08’, ‘09’, ‘10’, ‘11’, ‘12’],
    ‘day’: [‘01’, ‘02’, ‘03’, …, ’31’], # 实际中需要列出所有日期
    ‘time’: [‘00:00’, ‘01:00’, …, ‘23:00’], # 所有小时
    ‘area’: [55, 70, 15, 140], # 中国区域
    ‘format’: ‘netcdf’,
}

可以看到,这个请求的数据量已经非常庞大(1年 * 12月 * 31天 * 24小时 * 2个变量 * 高分辨率网格)。在实际操作中,我们几乎不会这样一次性请求,因为CDS服务器对单次请求的数据量有限制,且容易失败。这就需要引入 分而治之 的策略。

4. 高效下载策略与实战代码

面对大数据量请求,最稳健的策略是化整为零: 按时间分块 。通常按月,甚至按周进行请求,是成功率和效率最高的方式。

4.1 构建自动化下载脚本

我们将编写一个Python函数,用于下载指定年份和月份的数据。这个函数会处理参数构建、提交请求、等待处理和下载文件的全过程。

import cdsapi
import os
from datetime import datetime

def download_era5_land_monthly(year, month, variables, area, output_dir=‘./data’):
    “””
    下载指定年份和月份的ERA5-Land数据。

    参数:
    year (str): 年份,如 ‘2020‘
    month (str): 月份,两位数字,如 ‘01‘
    variables (list): 变量名列表,如 [‘2m_temperature‘]
    area (list): 区域 [北, 西, 南, 东]
    output_dir (str): 输出目录
    “””
    # 创建输出目录
    os.makedirs(output_dir, exist_ok=True)

    # 生成文件名,包含关键信息便于识别
    filename = f“era5_land_{year}_{month}_{‘_‘.join([v.replace(‘_‘, ‘-‘) for v in variables])}.nc”
    filepath = os.path.join(output_dir, filename)

    # 如果文件已存在,跳过下载(支持断点续传)
    if os.path.exists(filepath):
        print(f“文件 {filename} 已存在,跳过。”)
        return

    # 构建请求参数字典
    request = {
        ‘product_type’: ‘reanalysis’,
        ‘variable’: variables,
        ‘year’: year,
        ‘month’: month,
        ‘day’: [f‘{d:02d}’ for d in range(1, 32)], # 生成01-31,API会自动处理无效日期(如2月30日)
        ‘time’: [f‘{h:02d}:00’ for h in range(24)], # 生成00:00到23:00
        ‘area’: area,
        ‘format’: ‘netcdf’,
    }

    # 创建CDS客户端并提交请求
    c = cdsapi.Client()
    print(f“正在提交请求: {year}-{month}, 变量: {variables}”)

    try:
        # retmax 参数控制重试次数和等待策略
        c.retrieve(‘reanalysis-era5-land’, request, filepath)
        print(f“下载成功: {filepath}”)
    except Exception as e:
        print(f“下载失败 {year}-{month}: {e}”)
        # 可以选择将失败的任务记录到日志文件,后续重试
        with open(‘download_failures.log’, ‘a’) as f:
            f.write(f“{datetime.now()}: {year}-{month} - {e}\n”)

# 使用示例
if __name__ == ‘__main__’:
    # 定义你要下载的区域(例如:华北地区)
    north_china_area = [45, 110, 35, 120]

    # 定义要下载的变量
    target_variables = [‘2m_temperature’, ‘total_precipitation’]

    # 定义要下载的时间范围(例如:2021年全年)
    years = [‘2021’]
    months = [f‘{m:02d}’ for m in range(1, 13)] # ‘01‘ 到 ’12‘

    # 循环下载每个月的数据
    for year in years:
        for month in months:
            download_era5_land_monthly(year, month, target_variables, north_china_area, output_dir=‘./era5_land_nc’)

4.2 策略优化与高级技巧

上面的脚本是基础框架,在实际生产环境中,还需要考虑以下几点:

  1. 错误处理与重试机制 :CDS服务偶尔会因服务器负载或网络问题失败。 cdsapi 库内置了重试逻辑( retmax 参数),但你可能需要增加重试次数和等待时间。更健壮的做法是在脚本外层再加一个循环,捕获异常后等待一段时间再重试。
  2. 请求队列状态查询 :大规模下载时,你的请求会在CDS后台排队。你可以通过 cdsapi.Client().status() 查看队列状态,但更简单的做法是控制提交频率,比如每成功下载一个文件后,暂停几分钟,避免对服务器造成过大压力,也降低自己被临时限制的风险。
  3. 利用CDS Toolbox进行预处理 :如果你需要的不是原始小时数据,而是日平均、月平均,或者空间子集、时间聚合后的数据,可以在请求时使用CDS Toolbox语法。这能极大减少下载数据量。例如,在请求字典中添加 ‘product_type’: ‘monthly_averaged_reanalysis’ 可以直接下载月平均数据。但这需要仔细阅读官方文档,因为可用的预处理产品和参数与原始数据不同。
  4. 并行下载 :如果你有足够的网络带宽和CDS账号配额(通常免费用户也有充足的配额用于科研),可以谨慎地使用多进程或异步IO来同时下载多个月份的数据,显著提升效率。但务必注意控制并发数,建议不要超过3-5个并发请求,以示友好并避免被封。

5. 数据后处理与质量检查

数据下载到本地,工作只完成了一半。通常我们需要对原始的NetCDF文件进行一些后处理,才能投入模型或分析中使用。

5.1 使用Xarray进行基本操作

xarray 是处理网格数据的利器,其接口类似 pandas ,但支持多维数据。

import xarray as xr
import numpy as np

# 打开下载的单个文件
ds = xr.open_dataset(‘./era5_land_nc/era5_land_2021_01_2m-temperature_total-precipitation.nc’)

# 查看数据集结构
print(ds)
# 你会看到数据变量、坐标(经度、纬度、时间)、属性等信息

# 选择特定变量
temp_2m = ds[‘t2m’] # 2米气温,注意变量名可能是短名
precip = ds[‘tp’] # 总降水量

# 计算日平均气温
# 原始数据是开尔文(K),转换为摄氏度(°C)
temp_2m_celsius = temp_2m - 273.15
daily_mean_temp = temp_2m_celsius.resample(time=‘1D’).mean()

# 计算日累计降水量
# 原始数据是米(m),转换为毫米(mm)
precip_mm = precip * 1000
daily_total_precip = precip_mm.resample(time=‘1D’).sum()

# 选择特定区域(例如,经纬度范围子集)
subset = ds.sel(latitude=slice(40, 35), longitude=slice(115, 120))

5.2 多文件合并与时间序列构建

我们按月下载的数据是分散的,分析时往往需要连续的时间序列。

# 方法1:使用open_mfdataset (适用于文件结构一致的情况)
# 这会惰性加载,非常高效,适合处理大量文件
data_dir = ‘./era5_land_nc/’
file_pattern = ‘era5_land_2021_*.nc’
ds_annual = xr.open_mfdataset(f‘{data_dir}/{file_pattern}’, combine=‘by_coords’, parallel=True)

# 方法2:循环读取并拼接
import glob
file_list = sorted(glob.glob(f‘{data_dir}/era5_land_2021_*.nc’))
datasets = [xr.open_dataset(f) for f in file_list]
ds_annual_manual = xr.concat(datasets, dim=‘time’)

# 计算2021年全国年平均气温(简单区域平均)
# 注意:直接平均未考虑网格面积差异(高纬度网格面积小),严谨的做法需进行面积加权
annual_mean_temp = ds_annual[‘t2m’].mean(dim=‘time’) - 273.15
print(f“区域平均气温概况:{annual_mean_temp.mean().values:.2f} °C”)

5.3 数据质量常识性检查

在投入正式分析前,做一次快速的质量检查能避免后续很多麻烦。

  1. 缺失值检查 :查看数据中是否有填充值( _FillValue )或缺失值(NaN)。ERA5-Land数据质量很高,通常没有缺失,但边缘海洋区域可能无效。
    # 检查缺失值比例
    missing_ratio = ds[‘t2m’].isnull().mean().values
    print(f“缺失值比例:{missing_ratio}”)
    
  2. 物理范围合理性 :检查数据值是否在合理范围内。例如,中国地区的2米气温在-50°C到50°C之间;小时降水量应为非负值。
    # 检查异常值
    temp_valid = ds[‘t2m’].where((ds[‘t2m’] > 200) & (ds[‘t2m’] < 350)) # 合理物理范围约-73°C到77°C
    if temp_valid.isnull().any():
        print(“发现异常温度值!”)
    
  3. 时间连续性 :检查时间坐标是否连续,有无重复或跳变。
    time_coords = ds[‘time’].values
    print(f“时间起始:{time_coords[0]}”)
    print(f“时间结束:{time_coords[-1]}”)
    print(f“时间长度:{len(time_coords)}”)
    

6. 常见踩坑点与实战经验

在实际操作中,我遇到过各种各样的问题,这里总结几个最常见的“坑”和解决思路。

6.1 网络与请求失败问题

  • 症状 cdsapi 报错,提示连接超时、认证失败或服务器错误。
  • 排查与解决
    1. 检查API Key :确认 .cdsapirc 文件格式正确, UID Key 无误,没有多余空格。
    2. 网络代理 :如果你在机构内网,可能需要配置代理。 cdsapi 库默认使用系统代理,如果不行,可以在代码中设置:
      import os
      os.environ[‘HTTP_PROXY’] = ‘http://your-proxy:port’
      os.environ[‘HTTPS_PROXY’] = ‘http://your-proxy:port’
      
    3. 服务器繁忙 :CDS服务在全球被广泛使用,高峰时段(欧洲工作时间)可能排队较长或易失败。尝试在非高峰时段(如北京时间深夜至早晨)运行脚本。
    4. 请求太大 :这是最常见的原因。如果请求单个月的数据都失败,尝试进一步拆分:按周、甚至按变量分别请求。 核心原则:单次请求的数据量越小,成功率越高。

6.2 数据内容与预期不符

  • 症状 :下载的数据变量名不对、单位奇怪、或者空间范围有偏差。
  • 排查与解决
    1. 仔细核对变量名 :一定要去CDS官网的ERA5-Land数据集页面,查看最新的变量列表和命名。变量名可能包含层级信息(如 soil_temperature_level_1 )。
    2. 理解数据单位 :ERA5-Land数据的单位是国际单位制(SI)。例如,降水量的原始单位是“米”,需要乘以1000得到“毫米”;气温是“开尔文”,需要减去273.15得到“摄氏度”。这些信息存储在NetCDF文件的变量属性( units )中。
    3. 确认区域对齐 :如前所述,你请求的 area 会被对齐到最近的网格点。使用 xarray 打开数据后,首先打印 ds.latitude ds.longitude 的值,确认其范围是否与你预期的基本一致。

6.3 效率与存储优化

  • 问题 :下载大量数据速度慢,且占用巨大磁盘空间。
  • 经验技巧
    1. 预处理优于后处理 :如果最终只需要日数据或月数据,且空间范围固定,强烈建议在CDS请求阶段就使用Toolbox进行时间聚合和空间子集,这能节省90%以上的下载时间和存储空间。虽然学习Toolbox语法有成本,但对于长期、固定的数据需求,回报巨大。
    2. 压缩存储 :NetCDF文件支持压缩。在写入处理后的数据时,可以指定压缩级别。
      encoding = {var: {‘zlib’: True, ‘complevel’: 5} for var in ds.data_vars}
      ds.to_netcdf(‘compressed_data.nc’, encoding=encoding)
      
    3. 使用Zarr格式 :对于超大的、需要频繁切片分析的数据集,可以考虑转换为Zarr格式。Zarr支持分块存储和并行读写,特别适合在云存储或高性能计算环境中使用。

6.4 长期自动化运维

如果你需要持续更新数据(例如,每月自动下载上个月的数据),可以考虑以下方案:

  1. 脚本+定时任务 :将下载脚本部署到服务器上,使用 cron (Linux)或 任务计划程序 (Windows)设置每月固定时间执行。
  2. 状态记录与监控 :脚本应记录详细的日志,包括下载开始时间、结束时间、文件大小、是否成功等。可以将日志发送到监控系统或简单写入文件。
  3. 配额管理 :虽然免费配额通常够用,但最好定期在CDS官网查看你的请求统计,了解使用情况,避免意外超限。

最后,ERA5-Land数据是科研的宝贵资源,在享受其便利的同时,也别忘了在研究成果中按照ECMWF的要求进行规范的数据引用和致谢。这套从认证、请求、下载到后处理的完整流程,是我经过多次项目实践总结出来的稳定方案,希望能帮你彻底打通数据获取的“任督二脉”,把更多精力投入到有价值的科学分析中去。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐