AERONET 多源数据批量抓取:Python + Selenium 实战与 CURL/WGET 高效替代方案
1. AERONET数据抓取的核心挑战
做大气污染研究的朋友应该都深有体会,AERONET这个宝藏数据库虽然数据质量高,但每次手动下载数据简直能让人崩溃。我去年做京津冀地区十年气溶胶分析时,需要下载37个站点不同年份的AOD、SDA、FMF等数据,手动操作整整花了两周时间,期间还因为网络波动导致多次中断重来。
AERONET数据获取主要面临三个痛点:
- 动态参数依赖:下载链接需要拼接十多个参数,包括站点名、时间范围、数据类型等,手动构造极易出错
- 反爬机制:直接访问数据文件URL会返回404,必须先在网页完成查询操作生成临时令牌
- 数据分散:不同产品(AOD/SDA/TOT)分布在独立子系统,统一采集需要处理多个接口
实测发现,单纯用Requests库直接访问会触发反爬,而完全依赖Selenium又太慢。后来我摸索出一套混合方案:用Selenium处理动态令牌,用CURL进行批量下载,速度比纯浏览器方案快8倍。下面具体说说这两种方案的实现细节和优化技巧。
2. Selenium自动化方案全解析
2.1 环境配置与核心组件
建议使用Python 3.8+搭配最新版ChromeDriver,这里有个坑我踩过:不同版本的WebDriver对XPath解析有差异,会导致元素定位失败。我的配置清单:
# requirements.txt selenium==4.1.0 webdriver-manager==3.5.3 pandas>=1.3.0初始化浏览器实例时务必添加这些参数,能显著提升稳定性:
from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument("--headless") chrome_options.add_argument("--disable-gpu") chrome_options.add_argument("--window-size=1920x1080") chrome_options.add_argument("--log-level=3") driver = webdriver.Chrome(options=chrome_options)2.2 站点列表获取实战
AERONET官网的地图界面其实暗藏玄机。通过分析网络请求,我发现这个技巧:先访问https://aeronet.gsfc.nasa.gov/aeronet_locations.txt 可以直接获取所有站点的经纬度信息,比解析HTML高效得多。结合BeautifulSoup提取站点详情页URL的完整代码:
def get_station_metadata(): base_url = "https://aeronet.gsfc.nasa.gov/cgi-bin/type_one_station_op_v3" resp = requests.get(base_url) soup = BeautifulSoup(resp.text, 'html.parser') stations = [] for tr in soup.select('tr[bgcolor="#CCCCCC"], tr[bgcolor="#FFFFFF"]'): tds = tr.find_all('td') if len(tds) < 5: continue station = { 'name': tds[0].text.strip(), 'url': tds[0].find('a')['href'], 'lat': float(tds[1].text), 'lon': float(tds[2].text), 'elev': float(tds[3].text), 'active': tds[4].text.strip() == 'Active' } stations.append(station) return pd.DataFrame(stations)2.3 动态令牌获取技巧
AERONET的防直连机制非常特殊,必须先在页面执行这三个步骤:
- 选择年份范围(触发AJAX请求)
- 勾选需要的数据类型(AOD/SDA等)
- 点击Submit按钮生成临时令牌
关键代码实现:
def generate_download_token(driver, year_range, data_types): # 设置时间范围 select1 = Select(driver.find_element(By.ID, "Year1")) select1.select_by_value(year_range[0]) select2 = Select(driver.find_element(By.ID, "Year2")) select2.select_by_value(year_range[1]) # 勾选数据类型 for dtype in data_types: checkbox = driver.find_element(By.NAME, dtype) if not checkbox.is_selected(): checkbox.click() # 提交查询 submit = driver.find_element(By.NAME, "Submit") submit.click() # 等待令牌生成(关键!) WebDriverWait(driver, 30).until( EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, "Download")) ) return driver.current_url3. CURL/WGET高效方案详解
3.1 链接构造的玄机
通过抓包分析,我发现AERONET的下载API遵循固定格式:
https://aeronet.gsfc.nasa.gov/cgi-bin/print_web_data_v3? site={站点}&year={开始年}&month={开始月}&day={开始日} &year2={结束年}&month2={结束月}&day2={结束日} &{数据类型}=1&AVG={时间分辨率}参数说明表:
| 参数 | 必选 | 示例值 | 说明 |
|---|---|---|---|
| site | 是 | Beijing | 站点ID(区分大小写) |
| AVG | 是 | 10 | 10=原始数据,20=日均值 |
| AOD20 | 可选 | 1 | 1表示需要该数据类型 |
| hour | 否 | 8 | 起始小时(0-23) |
| lunar_merge | 否 | 0 | 0=不含月数据,1=包含 |
3.2 批量下载性能对比
测试100个AOD20数据文件下载(单位:秒):
| 方法 | 首次运行 | 缓存后 | 稳定性 |
|---|---|---|---|
| Selenium | 382 | 355 | 高 |
| CURL | 47 | 29 | 中 |
| WGET | 52 | 31 | 中 |
| 混合模式 | 58 | 33 | 高 |
混合模式实现逻辑:
# 先用Selenium获取令牌 python get_token.py --site Beijing --year 2020 > token.txt # 使用令牌批量下载 while read url; do curl -s -k -o "data/$(date +%s).csv" "$url" & if (( $(jobs | wc -l) >= 4 )); then wait -n fi done < token.txt4. 混合策略进阶技巧
4.1 智能路由方案
根据网络环境自动选择最优方案:
def download_strategy_selector(site, years): if is_campus_network(): # 教育网走CURL return curl_download(prepare_curl_params(site, years)) else: # 其他网络走Selenium with ChromeDriver() as driver: token = get_selenium_token(driver, site, years) return threaded_download(token)4.2 错误处理机制
针对常见问题的应对策略:
- 403 Forbidden:更换User-Agent,添加随机延迟
- 404 Not Found:重新获取令牌,检查参数格式
- Timeout:指数退避重试(最多3次)
增强版下载函数:
def robust_download(url, retry=3): for i in range(retry): try: with requests.Session() as s: s.headers.update({'User-Agent': get_random_user_agent()}) resp = s.get(url, timeout=30) resp.raise_for_status() return resp.content except Exception as e: if i == retry - 1: raise time.sleep(2 ** i + random.random())5. 实战案例:长三角地区数据采集
最近帮某研究所搭建的自动化流程,核心配置:
# config.yaml regions: - name: Yangtze_Delta bbox: [30.0, 119.0, 32.5, 122.0] years: [2015-2022] data_types: [AOD20, SDA20, FMF] strategy: hybrid output_dir: ./data/YRD执行效果:
- 自动识别区域内12个活跃站点
- 按年分目录存储各站点数据
- 失败任务自动重试并记录日志
- 日均数据更新耗时约7分钟
关键优化点:
- 使用Spatialite进行地理范围筛选
- 采用Zstandard压缩存储原始数据
- 集成Prometheus监控下载质量
这个方案已经稳定运行6个月,累计下载数据文件超过1.2TB。最大的收获是:一定要为每个下载任务添加完整的元数据注释,否则三个月后根本记不清某个数据文件的具体参数。我现在每个CSV文件头都自动写入这样的信息:
# Site: Shanghai # Period: 2020-01-01 to 2020-12-31 # DataType: AOD Level 2.0 # GeneratedAt: 2023-08-15T14:22:18Z # Checksum: sha256:9f86d08...