ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Python自动获取NREL风速数据:从API申请到分析落地

用Python自动获取NREL风速数据:从API申请到分析落地 干这行的人应该都有过这种经历项目里要风资源数据跑去NREL官网一个个选点、选年份、选高度下载下来一堆CSV然后还得手动整理格式。做一次两次还能忍要是做区域风资源普查或者多场景对比那真是体力活而且特别容易在重复操作里出错。后来我用Python写了个小脚本直接调NREL的API拉风速数据输入经纬度和年份几十秒就能拿到干净的表格整个过程从原来的一下午压缩到五分钟。今天就把这套完整思路和代码分享出来从API申请到数据落地一次讲透。这个内容特别适合三类人一是刚接触风资源数据分析的工程师二是做能源规划、选址评估的科研人员三是想用Python写自动化数据获取脚本的开发者。你不需要有多深的API基础只要会装依赖库、能跑Python文件跟着操作就能把NREL的风速数据自动拉下来。1. 先搞清楚NREL API到底能拿到什么风速数据1.1 NREL数据平台能提供哪些数据NREL全称是美国国家可再生能源实验室National Renewable Energy Laboratory它对外开放的开发者平台积累了大量的可再生能源基础数据其中风资源相关的数据主要来自Wind Toolkit风资源模型数据库。这套数据库基于中尺度气象模型和观测数据融合生成覆盖了北美以及全球相当大范围的地理区域提供了多个高度的风速、风向、温度、气压等参数时间分辨率可以做到小时级甚至更高。Wind Toolkit的数据不是简单的站点观测值而是经过模型模拟和再分析得到的网格化数据所以你在取数的时候不用关心某个气象站有没有覆盖到你选的点位只要在范围内任意经纬度都能拿到对应的模拟时序数据。这对于工程初测、资源普查、选址对比这些场景来说参考价值非常高。1.2 为什么用API而不是手动下载早期我手动下载的时候流程是这样的打开网页、输入经纬度、选择数据集、选择年份和高度、提交请求、等待生成、下载结果、解压、导入Excel。听起来每步都不难但一个地块要对比三四个高度、近十年的数据就得重复操作几十次而且网页端常常合并打包成大文件还要翻目录找参数。偶尔遇到网络问题还得从头再来。用API之后这些操作全部被脚本替代。你把坐标、年份、高度、需要的参数写进配置文件脚本自动循环请求返回的数据直接是结构化格式存成CSV或DataFrame跟pandas无缝衔接。更重要的是API可以方便地批量处理几十上百个坐标点只需要加一个for循环这在手动操作下几乎是不可行的。1.3 风速数据的典型应用场景拉下来的风速数据一般怎么用最常见的是做威布尔分布拟合估算年平均风速、风功率密度为风电项目做前期宏观选址也有人在建筑通风分析、风机选型、航线规划里面用。还有做光伏监测系统的人喜欢把风速数据一起采下来用来关联发电量的波动。不管用途是什么数据落地的第一步都是要把“下载数据”这件事自动化把重复劳动交给代码把时间留给分析。这篇文章的核心就是解决“怎么快速、稳定、批量地从NREL拉风速数据”这个问题。2. 准备环境注册API Key和安装Python依赖2.1 获取NREL API Key的完整步骤调用NREL的接口需要一个API Key这个密钥用来标识你的应用和配额。申请流程非常简单打开NREL开发者平台官网developer.nrel.gov点击注册填写邮箱、姓名等信息。注册完成后系统会给你一个类似DEMO_KEY或一串十六进制字符串的密钥。把密钥复制保存后面所有请求都需要带上这个参数。这里有一个重要提示NREL提供DEMO_KEY用于测试但是请求次数和并发数都有限制真实项目务必换成自己注册的Key。如果你频繁用同一个Key大批量请求还可能触发频率限制所以后面我会专门讲怎么做好请求间隔和异常重试。2.2 Python环境与依赖库安装我默认你已经装了Python 3.8以上的版本。没有装的话去官网下载安装包勾选“Add Python to PATH”一路默认安装就行。需要装的库只有两个requests和pandas。如果你后续要做可视化再加一个matplotlib。安装命令pip install requests pandas如果你用的是Anaconda一般已经自带pandas只需要补一个requestsconda install requests这里要提醒一下requests是用来发送HTTP请求的pandas则是把返回的数据整理成表格这两个是整个脚本的基础。安装完成后在Python里测试导入import requests import pandas as pd print(环境正常)能正常打印说明环境没问题。2.3 理解API请求的核心参数NREL Wind Toolkit API的请求路径和参数我从实际调用中整理了一份常用清单参数名含义示例值必填api_key你的密钥abc123...是lat纬度范围-90到9040.12是lon经度范围-180到180-105.23是hubh轮毂高度米100是attr需要的参数风速为wind_speedwind_speed是year数据年份2020是format返回格式建议csv或jsoncsv否不要小看这些参数很多时候脚本报错就是因为hubh写成了hubheight或者attr里同时要了多个参数但逗号格式不对。所以建议先把一个参数调通再逐步扩展。另外lon在西半球是负数很多人第一次调用出的数据不在预期位置多半是经纬度正负没有处理好。3. 核心实操写一个能跑通的NREL风速数据获取脚本3.1 完整的API调用代码示例直接上代码这个脚本我在Windows和Linux上都跑通过逻辑很简单构造请求、发送请求、解析响应、保存文件。import requests import pandas as pd import time from datetime import datetime # 配置信息 API_KEY YOUR_API_KEY BASE_URL https://developer.nrel.gov/api/wind-toolkit/v2/wind/wtk-download def fetch_wind_data(lat, lon, hubh100, attrwind_speed, year2020, output_formatcsv): 从NREL Wind Toolkit API获取指定坐标、高度和年份的风速时序数据。 返回pandas DataFrame。 params { api_key: API_KEY, lat: lat, lon: lon, hubh: hubh, attr: attr, year: year, format: output_format } print(f正在请求: lat{lat}, lon{lon}, hubh{hubh}, year{year}) resp requests.get(BASE_URL, paramsparams, timeout30) if resp.status_code ! 200: print(f请求失败状态码: {resp.status_code}) print(resp.text[:500]) return None # 根据返回格式解析数据 if output_format csv: # 先写到临时文件再用pandas读取避免编码问题 with open(temp_wind.csv, w, encodingutf-8) as f: f.write(resp.text) df pd.read_csv(temp_wind.csv) else: # JSON格式直接转DataFrame data resp.json() df pd.DataFrame(data.get(data, [])) return df if __name__ __main__: df fetch_wind_data(lat40.12, lon-105.23, hubh100, attrwind_speed, year2020) if df is not None: print(数据预览:) print(df.head()) df.to_csv(nrel_wind_data.csv, indexFalse) print(文件已保存为 nrel_wind_data.csv)如果你把这个脚本原样运行记得把API_KEY换成你自己的密钥。请求成功后会在当前目录生成temp_wind.csv和nrel_wind_data.csv两个文件。3.2 每一行代码在做什么这段脚本的核心就是把手动操作变成了四步。第一步构造params字典。这里的所有键值都是NREL接口约定好的一个都不能错。注意year我特意用了字符串类型有些API版本如果传整数会导致参数校验失败统一用字符串最稳妥。第二步用requests.get发送GET请求。我设置了30秒超时防止因为网络波动导致脚本无限卡住。params参数会由requests库自动编码拼接到URL后面不需要手动处理特殊字符。第三步判断响应状态码。这里是最容易被忽略的地方很多人一上来就解析resp.text结果返回的是400 Bad Request或403 Forbidden解析半天才发现是权限问题。所以我先把非200的响应打印出来方便定位问题。第四步解析数据。CSV格式直接写入临时文件用pd.read_csv读取因为有时候接口返回的CSV带BOM头直接StringIO解析会出现列名\ufeff的怪现象。JSON格式则直接转DataFrame不过Wind Toolkit API返回的JSON层级较深需要根据实际结构调整所以我这里默认推荐用CSV。3.3 数据清洗与时区处理下载下来的风速数据一般包含time列、wind_speed列可能还有其他附加参数。如果你的attr只填了wind_speed那DataFrame会很干净主要就是两个字段。但有时候返回的时间是UTC时间而你需要的是当地时间怎么处理NREL的原始数据多数采用UTC时间记录因为它要保证全球统一。工程上做分析建议把时间列转成本地时区df[time] pd.to_datetime(df[time], errorscoerce) df[time_local] df[time].dt.tz_localize(UTC).dt.tz_convert(Asia/Shanghai) df[hour] df[time_local].dt.hour df[month] df[time_local].dt.month转换之后你可以按小时、按月做统计聚合方便后续算平均风速、月均风功率密度。还有一个小技巧如果原始时间列是ISO格式带毫秒用pd.to_datetime能直接处理但如果数据里有缺失值加上errorscoerce可以把非法时间转成NaT不会让整个脚本崩掉。另外如果你的数据量比较大比如一次拉了几年的小时数据建议在保存CSV前把列顺序调整好、去重索引之后读数据更顺手df df[[time_local, wind_speed]].sort_values(time_local).reset_index(dropTrue)这样得到的表就很干净了。4. 进阶玩法批量取数、错误重试和并发优化4.1 循环获取多个坐标点的风速数据实际项目中几乎不会只跑一个点。比如你要给全省做风资源图谱可能有几十个候选点位这时候只需要套一层循环。locations [ {lat: 36.65, lon: -101.24}, {lat: 35.20, lon: -100.50}, {lat: 34.30, lon: -102.10}, ] all_dfs [] for loc in locations: df fetch_wind_data( latloc[lat], lonloc[lon], hubh80, attrwind_speed,wind_direction, year2021 ) if df is not None: # 给数据打上坐标标签方便后续合并区分 df[lat] loc[lat] df[lon] loc[lon] all_dfs.append(df) # 控制请求间隔避免触发限流 time.sleep(1) result pd.concat(all_dfs, ignore_indexTrue) result.to_csv(multi_site_wind.csv, indexFalse) print(多站点数据已保存共, len(result), 行)注意我把attr改成了wind_speed,wind_direction中间用英文逗号隔开。不同API版本对多参数的处理可能不同有些要求重复传参有些要求逗号分隔这里以实际返回为准。如果返回里没有风向列就说明这个版本不支持还是保持只拉wind_speed。4.2 遇到API错误怎么办常见状态码与重试机制调用外部API最怕的就是各种意外错误。我遇到过的情况有401 UnauthorizedAPI Key没填对或者已经失效。403 Forbidden请求被拒绝常见原因是免费Key的每日次数用完了。400 Bad Request参数格式不对比如hubh漏了、year传了无效值、经纬度越界。429 Too Many Requests限流了请求太频繁。500/502/503NREL服务端临时故障。针对这些最直接的方法就是在网络层加一个重试装饰器。下面是一个简单的重试逻辑def fetch_with_retry(lat, lon, hubh, attr, year, retries5, delay3): for attempt in range(retries): try: df fetch_wind_data(lat, lon, hubhhubh, attrattr, yearyear) if df is not None: return df except Exception as e: print(f第{attempt1}次请求异常: {e}) time.sleep(delay) print(重试多次仍失败请检查网络或参数) return None这里需要注意delay不要固定不变最好使用指数退避比如每次重试后翻倍延迟time.sleep(delay * (2 ** attempt))这样既能快速响应临时性错误又不会把服务端打爆。4.3 用多线程批量请求提升效率如果坐标点有几十个串行一个点等1秒那也要接近一分钟勉强能接受。但如果要跑几百个点位优化就很有必要。我通常会先用concurrent.futures.ThreadPoolExecutor把请求放到多线程里并发执行。from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_one(loc): df fetch_wind_data(loc[lat], loc[lon], hubh80, attrwind_speed, year2020) if df is not None: df[code] loc[code] return df with ThreadPoolExecutor(max_workers5) as executor: futures [executor.submit(fetch_one, loc) for loc in locations] results [] for future in as_completed(futures): result future.result() if result is not None: results.append(result)多线程虽快但有一个前提不要同时发太多请求否则很容易触发429。我建议max_workers设置在5以内并且每个请求之间保持一点随机间隔。还有一点批量请求时最好在本地缓存已下载的数据避免中途失败后重新拉一遍。5. 常见报错与排查技巧实录5.1 请求状态码200但数据为空这种情况最容易让人困惑。状态码200说明接口通了可是打印df.head()却没有任何行或者字段对不上。我遇到过几次原因多数是attr传的参数名不对比如把wind_speed写成了speed接口不报错但返回的数据里没有值。排查方法很简单先把format改成json打印返回的原始文本看看里面的字段名是什么。或者把响应保存下来用文本编辑器打开。不要盲目相信代码逻辑数据源才是最终标准。5.2 参数校验报错400 invalid schema我见过很多新手在调这个API时返回400错误信息里写着类似invalid schema其实就是参数名拼写错误或者类型不匹配。NREL这类接口对参数校验很严格多一个空格、少一个下划线都不行。一个很好用的调试技巧先用浏览器直接打开带参数的完整URL。比如https://developer.nrel.gov/api/wind-toolkit/v2/wind/wtk-download?api_keyYOUR_KEYlat40lon-105hubh100attrwind_speedyear2020如果浏览器能正常返回数据说明URL和参数没问题问题一定在Python代码那边的转义或者headers设置上。如果浏览器里同样报400那就对照官方文档一格一格检查参数。5.3 网络超时与连接异常有些网络环境访问海外接口会很慢甚至出现Connection timed out。虽然咱们不做任何网络加速但可以通过调整超时时间、增加重试次数来提升成功率。比如把timeout从30秒改成60秒resp requests.get(BASE_URL, paramsparams, timeout60)还有一种情况是代理设置干扰。如果你电脑里已经配置了系统代理requests默认会走系统代理有时候反而导致连接失败。这时候可以显式关闭代理requests.get(BASE_URL, paramsparams, proxies{http: None, https: None})这个方法在局域网环境里特别实用。5.4 数据量太大导致内存不足如果你拉的年限比较长比如连续5年小时级数据一个坐标点就有4万多行几十个坐标点累计到上百万行pandas处理起来没有问题但如果你在循环里不断concat中途可能会内存暴涨。我的建议是每个点保存一个单独CSV全部跑完之后再聚合或者用dask做分布式DataFrame但对大多数人来说批量循环里直接追加写入CSV就够用了。这里分享一个追加写入的小技巧first True for loc in locations: df fetch_one(loc) if df is not None: header True if first else False df.to_csv(all_wind_data.csv, modea, headerheader, indexFalse) first False这样内存占用非常小即使中间断了已经保存的数据也不会丢。6. 用可视化把风速数据变成业务结论6.1 快速画出风速时间序列图拿到的风速数据画图是最快验证数据是否合理的方法。比如取一个坐标点的年数据把它整年风速时序画出来import matplotlib.pyplot as plt df[time_local] pd.to_datetime(df[time_local]) plt.figure(figsize(12, 6)) plt.plot(df[time_local], df[wind_speed], linewidth0.3, alpha0.7) plt.title(Hourly Wind Speed at Site (Year 2020)) plt.xlabel(Time) plt.ylabel(Wind Speed (m/s)) plt.grid(True) plt.tight_layout() plt.savefig(wind_speed_series.png, dpi150) plt.show()如果画出来是一条纯直线或者频繁出现同一个值那就要警惕数据源是不是有问题。正常的风速时序是波动剧烈、植被式的锯齿状而且有明显的季节和昼夜特征。图表从来不只是给别人看的先给自己做质量检查。6.2 风速频率分布与威布尔拟合在风资源评估里大家不只看时序瞬时值更关注风速的频率分布。可以用直方图粗看一下df[wind_speed].hist(bins50, densityTrue, alpha0.7)如果数据量够大拟合威布尔分布是比较标准的一步。这里用scipy.stats里的weibull_min拟合from scipy.stats import weibull_min params weibull_min.fit(df[wind_speed]) print(威布尔拟合参数:, params)这样做的价值在于你把一大串时间序列压缩成了两个参数形状参数和尺度参数后续做发电量估算时可以直接使用。这个步骤可能只是整个项目的一小段但能把API取数的价值体现出来从数据到指标才是闭环。7. 我踩过几次坑之后的一些体会运行NREL数据获取脚本最大的感受就是这玩意儿本身不难但细节凑在一起能把人逼疯。比如我第一次跑批量脚本因为经纬度正负没注意拉了一堆南半球的数据还浑然不觉后面画地图才发现坐标点全跑偏了。后来我在脚本里加了坐标范围校验凡是不在北美范围内的点直接跳过并警告。再比如API Key的额度免费额度看着挺多但跑几百个点位连续请求很快就用完了。我的建议是先用一两个点位测试确认代码无误后再铺开量同时把每天的量控制在安全线以内避免第二天被锁。还有一个小经验把配置和业务逻辑分开。不要每换一个项目就去改主脚本干脆写一个config.yaml或者直接在脚本顶部用字典维护配置改坐标、改年份只用动配置区。我现在跑任何数据项目都默认这个模式习惯以后换数据源只要替换中间层函数非常方便。如果你只是临时拉一次数据直接抄上面的代码就能用。如果打算长期维护建议把那段重试逻辑也加进去真的能省很多事。最后再分享一个小技巧下载数据后第一时间打印df.describe()看看风速的最大值、最小值、缺失率是否在合理范围内这一个动作能帮你过滤掉80%的数据坑。
返回列表