告别手动点击!用Python+Selenium搞定AERONET AOD数据批量下载(附完整代码)
科研效率革命:Python+Selenium全自动抓取AERONET气溶胶数据实战指南
每次为了获取AERONET站点数据而重复点击网页的日子该结束了。当我们需要分析多个站点、跨年度的气溶胶光学厚度(AOD)数据时,传统的手动下载方式不仅耗时耗力,还容易出错。这里将分享一套经过实战检验的自动化解决方案,用Python和Selenium构建可靠的数据抓取工作流。
1. 自动化方案设计原理
AERONET官网的数据下载流程本质上是一系列可预测的网页交互操作:选择年份、勾选数据级别、点击提交按钮。这正是浏览器自动化工具Selenium的用武之地。与直接调用API不同,我们的方案模拟真实用户操作,完美绕过反爬机制。
核心优势对比:
| 下载方式 | 稳定性 | 复杂度 | 可扩展性 | 适用场景 |
|---|---|---|---|---|
| 手动点击 | 高 | 低 | 差 | 单次少量数据 |
| 直接API调用 | 中 | 中 | 中 | 熟悉API结构的开发者 |
| Selenium自动化 | 高 | 中 | 高 | 批量、定期数据采集 |
提示:NASA官方未提供标准API文档,Selenium方案在数据获取合规性上更具优势
实现流程分为三个关键阶段:
- 站点发现:通过解析AERONET地图页面获取目标区域所有站点URL
- 数据检索:模拟用户选择时间范围和数据级别的操作
- 文件下载:自动捕获生成的下载链接并保存到本地
2. 环境配置与基础搭建
开始前需要准备以下工具链:
- Python 3.8+(建议使用Anaconda发行版)
- Chrome浏览器(版本需与驱动匹配)
- ChromeDriver(下载地址)
安装必要的Python包:
pip install selenium beautifulsoup4 pandas webdriver-manager浏览器自动化基础配置:
from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.by import By def init_driver(headless=True): options = webdriver.ChromeOptions() if headless: options.add_argument('--headless') options.add_argument('--disable-gpu') options.add_argument('--window-size=1920,1080') driver = webdriver.Chrome( service=Service(ChromeDriverManager().install()), options=options ) return driver常见配置问题解决方案:
- 若出现
SessionNotCreatedException,检查浏览器与驱动版本匹配 - 添加
--no-sandbox参数可解决部分Linux环境下的权限问题 - 使用
webdriver-manager可自动管理驱动版本
3. 站点发现与数据定位
高效获取目标站点列表是整个流程的第一步。AERONET的地图界面实际上包含了完整的站点元数据,我们可以通过HTML解析提取这些信息。
站点解析关键技术点:
- 使用BeautifulSoup处理动态加载的HTML
- 正则表达式匹配特定模式的URL
- 异常处理应对网络波动
示例代码实现:
import re from bs4 import BeautifulSoup import requests def get_station_metadata(region_url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' } response = requests.get(region_url, headers=headers) soup = BeautifulSoup(response.text, 'html.parser') stations = [] for link in soup.find_all('a', href=re.compile(r'data_display_aod_v3')): station_name = link.text.strip() station_url = link['href'] geo_info = link.parent.get_text(strip=True) # 提取经纬度信息 coords = re.findall(r'\(([\d.-]+),\s*([\d.-]+)\)', geo_info) lat, lon = coords[0] if coords else (None, None) stations.append({ 'name': station_name, 'url': f"https://aeronet.gsfc.nasa.gov{station_url}", 'latitude': lat, 'longitude': lon }) return stations注意:实际应用中建议添加重试机制和请求间隔,避免触发反爬限制
4. 数据下载自动化实现
核心下载流程需要处理AERONET网站的几个关键交互环节:
- 时间范围选择:通过
<select>元素操作年份下拉框 - 数据级别选择:勾选AOD 1.5或2.0等复选框
- 提交查询:点击Submit按钮触发数据生成
- 文件下载:捕获生成的ZIP文件链接
完整自动化脚本:
from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException import time import os def download_aod_data(driver, station, start_year, end_year, data_level='AOD20'): base_url = "https://aeronet.gsfc.nasa.gov/cgi-bin/data_display_aod_v3" driver.get(f"{base_url}?site={station}") try: # 设置时间范围 Select(driver.find_element(By.ID, "Year1")).select_by_value(str(start_year)) Select(driver.find_element(By.ID, "Year2")).select_by_value(str(end_year)) # 选择数据级别 driver.find_element(By.NAME, data_level).click() # 提交查询 driver.find_element(By.NAME, "Submit").click() time.sleep(15) # 等待数据生成 # 获取下载链接 download_link = driver.find_element( By.XPATH, "//a[contains(text(),'All Points')]" ).get_attribute("href") # 下载文件 local_path = f"./data/{station}_{start_year}-{end_year}.zip" os.makedirs(os.path.dirname(local_path), exist_ok=True) with requests.get(download_link, stream=True) as r: r.raise_for_status() with open(local_path, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): f.write(chunk) return True except NoSuchElementException as e: print(f"元素未找到: {e}") return False性能优化技巧:
- 使用
headless模式减少资源占用 - 合理设置
time.sleep间隔避免请求过载 - 实现断点续传功能应对网络中断
- 采用多线程处理多个站点并行下载
5. 异常处理与日志系统
健壮的自动化系统必须包含完善的错误处理机制。以下是AERONET数据抓取中常见的异常情况:
- 404错误:数据尚未生成或URL构造错误
- 元素定位失败:网页结构变动或加载延迟
- 认证问题:需要处理Cookie或会话状态
- 网络波动:请求超时或连接中断
增强型错误处理实现:
import logging from datetime import datetime def setup_logger(): logger = logging.getLogger('aeronet_downloader') logger.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) # 文件日志 file_handler = logging.FileHandler('download.log') file_handler.setFormatter(formatter) # 控制台日志 console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger def safe_download(driver, station, year, logger): try: success = download_aod_data(driver, station, year, year) if not success: logger.warning(f"{station} {year} 下载失败") return False logger.info(f"{station} {year} 下载完成") return True except Exception as e: logger.error(f"{station} {year} 发生错误: {str(e)}") return False重要:日志系统应记录足够上下文信息,便于问题回溯和批量重试
6. 进阶应用与扩展
基础功能实现后,可以考虑以下增强功能:
多数据类型支持:
- 修改代码支持下载SSA(单次散射反照率)
- 添加FMF(细模分数)数据抓取
- 支持混合数据类型批量请求
地理空间筛选:
def filter_stations_by_bbox(stations, min_lat, max_lat, min_lon, max_lon): return [ s for s in stations if (min_lat <= float(s['latitude']) <= max_lat) and (min_lon <= float(s['longitude']) <= max_lon) ]定时任务集成:
- 使用APScheduler设置定期执行
- 与Airflow等调度系统集成
- 添加邮件通知功能
数据质量检查:
import zipfile def validate_zip_file(file_path): try: with zipfile.ZipFile(file_path) as zf: return len(zf.namelist()) > 0 except zipfile.BadZipFile: return False实际项目中,这套系统将数据处理时间从原来的数小时缩短到几分钟,特别是当需要获取全球数百个站点多年数据时,效率提升更为显著。一个值得注意的细节是合理设置请求间隔,过短的间隔会导致临时IP封锁,而间隔过长又影响效率,经过测试,15-30秒的间隔在稳定性和速度之间取得了良好平衡。
