告别手动点击!用Python脚本批量下载ERA5气象数据的保姆级教程(附.cdsapirc配置避坑指南)

气象数据分析的第一步永远是数据获取。对于需要处理长时间序列、多变量的研究者来说,手动在网页上逐个勾选日期和参数无异于一场噩梦。我曾见过同行为了下载10年的逐小时数据,连续三天守在电脑前重复点击操作——直到浏览器崩溃。本文将彻底解决这个痛点,教你用Python脚本构建自动化工作流,把重复劳动交给代码,把宝贵时间留给真正的科研。

1. 环境准备与CDS API密钥配置

1.1 注册CDS账户并获取API凭证

访问 Copernicus Climate Data Store 完成注册后,个人资料页的"API key"选项卡藏着自动化下载的钥匙。这里需要特别注意:

  • UID :形如 123456 的数字串
  • API Key :由32位字母数字组成的字符串(如 a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8

这两个凭证的组合将构成 .cdsapirc 文件的核心内容。常见错误包括混淆UID与登录用户名,或误用包含特殊字符的旧版密钥。

1.2 创建.cdsapirc配置文件

在用户根目录(Linux/Mac的 ~ 或Windows的 C:\Users\<用户名> )创建这个隐藏文件。以下是典型错误与正确示例对比:

# 错误示例(缺少冒号、格式错乱)
url: https://cds.climate.copernicus.eu/api/v2
key 123456:a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8

# 正确格式
url: https://cds.climate.copernicus.eu/api/v2
key: 123456:a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8

验证配置是否生效的最快方法是在终端执行:

cat ~/.cdsapirc | grep -E "url|key"  # Linux/Mac
type %USERPROFILE%\.cdsapirc         # Windows

2. Python自动化下载核心代码解析

2.1 基础请求模板生成技巧

在CDS网页选择数据时,"Show API request"按钮生成的代码往往需要优化。原始代码可能长这样:

import cdsapi

c = cdsapi.Client()

c.retrieve(
    'reanalysis-era5-single-levels',
    {
        'product_type': 'reanalysis',
        'variable': '2m_temperature',
        'year': '2020',
        'month': '01',
        'day': '01',
        'time': '12:00',
        'format': 'netcdf',
    },
    'download.nc')

改进后的模板应包含错误处理和参数化设计:

def era5_download(variable, years, months, days, times, output_file):
    """增强版下载函数"""
    try:
        c = cdsapi.Client()
        request = {
            'product_type': 'reanalysis',
            'format': 'netcdf',
            'variable': variable,
            'year': years,
            'month': months,
            'day': days,
            'time': times,
        }
        c.retrieve('reanalysis-era5-single-levels', request, output_file)
        print(f"成功下载: {output_file}")
    except Exception as e:
        print(f"下载失败: {str(e)}")

2.2 多时间批次下载实战

处理跨年数据时,直接传入所有年份可能导致服务器拒绝请求。更稳健的做法是分批次处理:

from datetime import datetime, timedelta

def date_range(start_date, end_date):
    """生成日期序列"""
    delta = end_date - start_date
    return [start_date + timedelta(days=i) for i in range(delta.days + 1)]

# 示例:下载2020-2022年每日00时和12时数据
dates = date_range(datetime(2020,1,1), datetime(2022,12,31))
for dt in dates:
    filename = f"era5_{dt.strftime('%Y%m%d')}.nc"
    era5_download(
        variable='2m_temperature',
        years=dt.strftime('%Y'),
        months=dt.strftime('%m'),
        days=dt.strftime('%d'),
        times=['00:00', '12:00'],
        output_file=filename
    )

3. 高频报错解决方案精要

3.1 证书验证失败问题

当遇到 SSL: CERTIFICATE_VERIFY_FAILED 错误时,可通过以下方式解决:

import ssl
ssl._create_default_https_context = ssl._create_unverified_context

但更安全的做法是更新本地证书库:

# Ubuntu/Debian
sudo apt-get install ca-certificates

# Mac (Homebrew)
brew install openssl

3.2 请求配额与限流处理

CDS对并发请求有限制,建议:

  1. 添加随机延迟(5-30秒)
import random
import time

time.sleep(5 + random.random() * 25)  # 随机等待5-30秒
  1. 使用指数退避重试机制
import math

def download_with_retry(max_retries=3):
    retries = 0
    while retries < max_retries:
        try:
            # 下载代码...
            break
        except Exception as e:
            wait_time = math.pow(2, retries)
            print(f"等待{wait_time}秒后重试...")
            time.sleep(wait_time)
            retries += 1

4. 高级技巧与性能优化

4.1 多变量并行下载

使用 concurrent.futures 实现并行下载(注意遵守CDS使用政策):

from concurrent.futures import ThreadPoolExecutor

variables = [
    '2m_temperature',
    'mean_sea_level_pressure',
    'total_precipitation'
]

def download_var(var):
    era5_download(
        variable=var,
        years=['2020', '2021'],
        months=['{:02d}'.format(m) for m in range(1,13)],
        days=['{:02d}'.format(d) for d in range(1,32)],
        times=['00:00', '12:00'],
        output_file=f'{var}_2020-2021.nc'
    )

with ThreadPoolExecutor(max_workers=3) as executor:
    executor.map(download_var, variables)

4.2 数据分块与合并策略

对于超大范围数据,采用分块下载后合并的策略:

# 下载欧洲和亚洲分区数据
regions = {
    'europe': [35, 70, -10, 40],  # lat_min, lat_max, lon_min, lon_max
    'asia': [10, 55, 60, 140]
}

for name, area in regions.items():
    era5_download(
        variable='2m_temperature',
        years='2020',
        area=area,  # 关键参数:North/West/South/East
        output_file=f'temp_{name}.nc'
    )

# 使用xarray合并文件
import xarray as xr
ds = xr.open_mfdataset('temp_*.nc', combine='by_coords')
ds.to_netcdf('global_temp_2020.nc')

4.3 元数据自动记录

为每个下载文件添加说明性元数据:

import netCDF4 as nc
from datetime import datetime

def add_metadata(filename):
    with nc.Dataset(filename, 'a') as ds:
        ds.download_date = datetime.now().isoformat()
        ds.download_script = "era5_auto_downloader v1.2"
        ds.creator_email = "your@email.com"

5. 完整工作流示例

结合上述技术点的典型应用场景:

# config.py
CDS_API_URL = "https://cds.climate.copernicus.eu/api/v2"
API_KEY = "your:key"

# era5_downloader.py
import os
import time
import random
from config import CDS_API_URL, API_KEY

def configure_cds():
    """确保API配置正确"""
    cds_dir = os.path.expanduser('~')
    cds_file = os.path.join(cds_dir, '.cdsapirc')
    
    if not os.path.exists(cds_file):
        with open(cds_file, 'w') as f:
            f.write(f"url: {CDS_API_URL}\nkey: {API_KEY}")

def batch_download(variables, years, output_dir):
    """批量下载主函数"""
    os.makedirs(output_dir, exist_ok=True)
    
    for year in years:
        for var in variables:
            filename = os.path.join(output_dir, f"{var}_{year}.nc")
            if os.path.exists(filename):
                continue
                
            try:
                era5_download(
                    variable=var,
                    years=str(year),
                    months=[f"{m:02d}" for m in range(1,13)],
                    days=[f"{d:02d}" for d in range(1,32)],
                    times=['00:00', '06:00', '12:00', '18:00'],
                    output_file=filename
                )
                time.sleep(10 + random.random() * 20)  # 限流延迟
            except Exception as e:
                print(f"Error downloading {var} {year}: {str(e)}")
                time.sleep(60)  # 出错后延长等待

if __name__ == "__main__":
    configure_cds()
    important_vars = [
        '2m_temperature',
        'total_column_water_vapour',
        'surface_pressure'
    ]
    batch_download(important_vars, range(2010, 2023), "era5_data")

更多推荐