手把手教你用Python脚本批量下载NASA夜间灯光数据(VNP46A1/A2)

张开发
2026/4/20 17:00:01 15 分钟阅读

分享文章

手把手教你用Python脚本批量下载NASA夜间灯光数据(VNP46A1/A2)
用Python全自动抓取NASA夜间灯光数据的工程化实践深夜的城市灯光像星辰般闪烁这些人类活动的痕迹被NASA的VIIRS传感器精准捕捉形成VNP46系列科学数据集。对于城市规划、经济分析或环境监测的研究者来说这些夜间灯光数据堪称无价之宝——直到你开始手动下载数百个HDF文件每个都需要点击五次以上还要处理频繁的会话超时。作为曾经花了整个周末下载2018年中国区数据的亲历者我将分享如何用Python构建工业级下载方案让数据采集像夜灯一样自动点亮。1. 理解NASA数据服务的工程挑战VNP46A1/A2作为Black Marble产品套件的核心成员其数据价值与获取难度呈正相关。在开始编码前我们需要解剖NASA数据服务的几个关键特性认证体系EarthData使用OAuth2.0API Key双重验证会话通常2小时失效限流机制单个IP并发请求限制为10个/分钟超过会触发HTTP 429文件分布全球数据按MODIS瓦片hXXvXX组织单个日期包含400文件网络特性美国境外下载速度常低于1MB/s需考虑断点续传# 典型EarthData文件URL结构示例 # https://ladsweb.modaps.eosdis.nasa.gov/archive/allData/5000/VNP46A1/2023/001/VNP46A1.A2023001.h00v08.001.NRT.h5提示优先使用VNP46A2月光校正版进行社会经济分析但需注意其数据仅更新至2018年。VNP46A1适合大气研究但需要额外处理月相影响。2. 构建自动化下载框架的关键组件2.1 认证管理模块NASA的API访问需要动态维护认证令牌。这里采用requests.Session保持会话并实现令牌自动刷新import os from datetime import datetime, timedelta import requests from dotenv import load_dotenv class EarthDataAuth: def __init__(self): load_dotenv() self.base_url https://urs.earthdata.nasa.gov self.session requests.Session() self.token_expiry None def _refresh_token(self): auth_payload { username: os.getenv(EARTHDATA_USER), password: os.getenv(EARTHDATA_PWD), client_id: python_script, grant_type: password } response self.session.post( f{self.base_url}/oauth/token, dataauth_payload ) response.raise_for_status() self.token_expiry datetime.now() timedelta(seconds3600) return response.json()[access_token]2.2 智能重试与限流控制针对NASA服务的稳定性特点我们需要实现指数退避重试策略from tenacity import retry, wait_exponential, stop_after_attempt retry( waitwait_exponential(multiplier1, min4, max60), stopstop_after_attempt(5) ) def download_file(session, url, save_path): try: with session.get(url, streamTrue) as r: r.raise_for_status() with open(save_path, wb) as f: for chunk in r.iter_content(chunk_size8192): f.write(chunk) return True except requests.exceptions.RequestException as e: print(fDownload failed: {e}) raise3. 实现区域数据精准获取3.1 地理空间过滤算法通过NASA的CMR API查询数据时采用空间相交算法筛选目标瓦片import geopandas as gpd from shapely.geometry import box def get_target_tiles(bbox, tile_gridMODIS_Grid_Sinusoidal): # 加载MODIS瓦片网格 grid gpd.read_file(fhttps://raw.githubusercontent.com/giswqs/leafmap/master/examples/data/{tile_grid}.geojson) # 创建查询几何体 query_box box(*bbox) # 空间筛选 matched grid[grid.geometry.intersects(query_box)] return matched[Name].tolist()3.2 多线程下载优化使用concurrent.futures实现可控并发下载from concurrent.futures import ThreadPoolExecutor, as_completed def batch_download(url_list, save_dir, max_workers4): auth EarthDataAuth() session auth.session with ThreadPoolExecutor(max_workersmax_workers) as executor: futures { executor.submit( download_file, session, url, os.path.join(save_dir, url.split(/)[-1]) ): url for url in url_list } for future in as_completed(futures): url futures[future] try: if future.result(): print(f✓ {url.split(/)[-1]}) except Exception as e: print(f✗ Failed {url}: {str(e)})4. 构建端到端解决方案4.1 配置管理采用YAML文件统一管理参数# config.yaml region: bbox: [115.7, 39.4, 117.4, 41.0] # 北京边界 dates: start: 2023-01-01 end: 2023-01-31 product: VNP46A2 output_dir: ./data/beijing_20234.2 主控流程实现整合各模块形成完整工作流import yaml from datetime import datetime, timedelta def main(): # 加载配置 with open(config.yaml) as f: config yaml.safe_load(f) # 生成日期序列 date_range [ (datetime.strptime(config[dates][start], %Y-%m-%d) timedelta(daysx)).strftime(%Y.%m.%d) for x in range((datetime.strptime(config[dates][end], %Y-%m-%d) - datetime.strptime(config[dates][start], %Y-%m-%d)).days 1) ] # 获取目标瓦片 target_tiles get_target_tiles(config[region][bbox]) # 构建下载URL列表 base_url https://ladsweb.modaps.eosdis.nasa.gov/archive/allData/5000 urls [ f{base_url}/{config[product]}/{date.replace(.,/)}/{config[product]}.A{date.replace(.,)}.{tile}.001.NRT.h5 for date in date_range for tile in target_tiles ] # 执行批量下载 os.makedirs(config[output_dir], exist_okTrue) batch_download(urls, config[output_dir])5. 高级技巧与异常处理5.1 断点续传实现通过记录下载状态实现任务恢复import json def load_progress(log_fileprogress.json): try: with open(log_file) as f: return set(json.load(f)[completed]) except FileNotFoundError: return set() def save_progress(completed, log_fileprogress.json): with open(log_file, w) as f: json.dump({completed: list(completed)}, f)5.2 数据完整性验证NASA提供MD5校验文件可自动验证下载质量import hashlib def verify_file(file_path, expected_md5): with open(file_path, rb) as f: file_hash hashlib.md5() while chunk : f.read(8192): file_hash.update(chunk) return file_hash.hexdigest() expected_md56. 部署与监控方案对于长期运行的下载任务建议添加以下增强功能邮件通知使用SMTP协议发送任务完成/失败通知Prometheus监控暴露下载指标供Grafana可视化Docker容器化解决环境依赖问题# 监控指标示例 from prometheus_client import start_http_server, Counter DOWNLOAD_COUNTER Counter(nasa_downloads, File download counts, [status]) def instrumented_download(session, url, save_path): try: success download_file(session, url, save_path) DOWNLOAD_COUNTER.labels(success).inc() return success except Exception: DOWNLOAD_COUNTER.labels(failed).inc() raise当脚本在凌晨3点因为网络波动自动重试第17次下载时我才真正体会到自动化带来的解放——现在我的工作站可以持续运转数周按计划采集全球任意区域的夜间灯光数据而我要做的只是泡杯咖啡检查邮件里的完成报告。或许这就是现代科研该有的样子让机器处理重复劳动让人专注于真正的发现。

更多文章