告别手动点击!用Python+Selenium搞定AERONET AOD数据批量下载(附完整代码)

张开发
2026/6/9 4:55:01 15 分钟阅读
告别手动点击!用Python+Selenium搞定AERONET AOD数据批量下载(附完整代码)
科研效率革命PythonSelenium全自动抓取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(headlessTrue): options webdriver.ChromeOptions() if headless: options.add_argument(--headless) options.add_argument(--disable-gpu) options.add_argument(--window-size1920,1080) driver webdriver.Chrome( serviceService(ChromeDriverManager().install()), optionsoptions ) 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, headersheaders) soup BeautifulSoup(response.text, html.parser) stations [] for link in soup.find_all(a, hrefre.compile(rdata_display_aod_v3)): station_name link.text.strip() station_url link[href] geo_info link.parent.get_text(stripTrue) # 提取经纬度信息 coords re.findall(r\(([\d.-]),\s*([\d.-])\), geo_info) lat, lon coords[0] if coords else (None, None) stations.append({ name: station_name, url: fhttps://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_levelAOD20): 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_okTrue) with requests.get(download_link, streamTrue) as r: r.raise_for_status() with open(local_path, wb) as f: for chunk in r.iter_content(chunk_size8192): 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秒的间隔在稳定性和速度之间取得了良好平衡。

更多文章