如何解决Python下载NOAA数据时的SSL CERTIFICATE_VERIFY_FAILED错误
问题描述
在Airflow 2.1(运行于Amazon Linux 2的EC2实例,Python 3.7)上运行DAG从NOAA公开数据集下载文件时,持续遭遇SSL证书验证错误:
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1091)>
负责下载的代码片段:
import urllib.request from contextlib import closing from shutil import copyfileobj from time import sleep from airflow import AirflowException import certifi import ssl # Set the SSL certificate authority bundle to use certifi ssl_context = ssl.create_default_context(cafile=certifi.where()) # download file tries = 0 while tries < 5: try: with closing(urllib.request.urlopen(url, context=ssl_context)) as r: with open(cdc_file, "wb") as f: copyfileobj(r, f) print("File has been downloaded and copied successfully") break except Exception as e: print(f"####### Error: {str(e)} #######") print("####### Sleeping for 10 secs before trying again #######") sleep(10) print("####### Trying again #######") tries += 1 if tries >= 5: raise AirflowException("It wasn't possible to download NOAA files due to connection refused by NOAA's server") continue
已尝试但无效的方法:
- 开发环境创建未验证SSL上下文(生产环境不可用,且返回403 Forbidden)
- 添加10秒休眠的循环重试机制
- 更新certifi和ssl包
- 通过
openssl s_client -connect downloads.psl.noaa.gov:443 -showcerts获取CA证书并创建自定义pem文件
可行解决方法
方法1:使用系统级CA证书替代certifi
Amazon Linux 2的系统CA证书存储在/etc/pki/tls/certs/ca-bundle.crt,直接指定该路径创建SSL上下文:
# 替换原SSL上下文初始化代码 ssl_context = ssl.create_default_context(cafile="/etc/pki/tls/certs/ca-bundle.crt")
系统级CA bundle通常包含更完整的公共CA证书,可覆盖certifi缺失的根CA。
方法2:追加自定义CA证书到系统CA bundle
若之前通过openssl获取的CA证书有效,将其追加到系统默认CA文件:
sudo cat /path/to/your/custom-noaa-ca.pem >> /etc/pki/tls/certs/ca-bundle.crt
之后重启Airflow服务,确保进程读取更新后的CA列表:
sudo systemctl restart airflow-webserver airflow-scheduler
方法3:排查代理证书问题
如果EC2通过企业代理访问外网,代理可能替换SSL证书,需添加代理CA到信任列表:
- 获取代理CA证书并保存为
proxy-ca.pem - 追加到系统CA bundle:
sudo cat proxy-ca.pem >> /etc/pki/tls/certs/ca-bundle.crt
- 在Airflow配置中设置代理(或代码中配置):
修改airflow.cfg:
[core] http_proxy = http://your-proxy:port https_proxy = https://your-proxy:port
或在代码中添加代理配置:
proxy_handler = urllib.request.ProxyHandler({'http': 'http://your-proxy:port', 'https': 'https://your-proxy:port'}) opener = urllib.request.build_opener(proxy_handler) urllib.request.install_opener(opener)
方法4:使用完整证书链文件
重新获取NOAA服务器的完整证书链:
openssl s_client -connect downloads.psl.noaa.gov:443 -showcerts < /dev/null | awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/{print}' > full-noaa-chain.pem
在代码中指定该完整链文件:
ssl_context = ssl.create_default_context(cafile="/path/to/full-noaa-chain.pem")
内容的提问来源于stack exchange,提问作者Fábio Antunes
相关产品推荐
相关产品推荐

