Pandas read_html 与 PyExecJS 双方案对比:解析 aqistudy.cn 加密数据的 2 种技术路径

在数据采集领域,动态渲染页面和加密接口一直是技术攻坚的重点难点。本文将以中国空气质量在线监测平台(aqistudy.cn)为例,深入剖析两种截然不同的数据解析方案:基于浏览器自动化的 Pandas read_html 表格解析,以及通过 PyExecJS 执行 JavaScript 的解密方案。这两种技术路径各有优劣,适用于不同的应用场景。

1. 技术方案概述与适用场景

当我们面对动态渲染的网页数据时,传统的数据抓取方式往往会遇到各种障碍。以 aqistudy.cn 为例,该平台采用了前端渲染和接口加密的双重保护机制,使得常规的 requests + BeautifulSoup 组合难以奏效。

Pandas read_html 方案 的核心思路是借助浏览器自动化工具(如 Selenium)完整加载页面后,直接解析渲染完成的 HTML 表格。这种方法最大的优势是开发简单直观,适合以下场景:

  • 目标数据以标准表格形式呈现
  • 不需要处理复杂的接口加密逻辑
  • 项目对爬取效率要求不高

PyExecJS 方案 则另辟蹊径,通过逆向分析前端 JavaScript 代码,直接在 Python 中执行加密解密逻辑。这种方案更适合:

  • 数据通过加密接口传输
  • 需要高频次、大批量采集数据
  • 对爬取效率和资源占用有严格要求

提示:选择方案前务必确认目标网站的服务条款,遵守 robots.txt 规定,控制请求频率避免对服务器造成压力。

2. Selenium + Pandas read_html 实战解析

这种组合方案的技术实现相对直接,主要分为浏览器环境准备、页面加载控制和数据提取三个环节。

2.1 浏览器环境配置

现代网站普遍会检测自动化工具,因此需要进行适当的反检测配置:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_experimental_option("useAutomationExtension", False)

driver = webdriver.Chrome(options=chrome_options)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined
    })
    """
})

关键配置点解析:

  • disable-blink-features :禁用自动化控制特征
  • excludeSwitches :移除自动化标志
  • navigator.webdriver 重定义:消除最常见的检测点

2.2 数据抓取核心逻辑

完整的抓取流程需要处理页面导航和数据提取两个关键环节:

import pandas as pd
from time import sleep

def fetch_month_data(driver, city, month):
    url = f"https://www.aqistudy.cn/historydata/daydata.php?city={city}&month={month}"
    driver.get(url)
    sleep(2)  # 确保表格加载完成
    
    try:
        df = pd.read_html(driver.page_source, header=0)[0]
        df['城市'] = city
        return df
    except Exception as e:
        print(f"获取{month}数据失败: {str(e)}")
        return None

实际项目中还需要添加以下增强功能:

  • 智能等待机制(替代固定 sleep)
  • 失败重试逻辑
  • 数据验证检查
  • 反爬虫检测处理

2.3 数据存储与优化

对于大规模采集,建议采用增量存储策略:

import sqlite3

def save_to_db(dataframe, db_path="air_quality.db"):
    conn = sqlite3.connect(db_path)
    dataframe.to_sql('air_quality', conn, if_exists='append', index=False)
    conn.close()

性能优化技巧:

  • 使用 executemany 批量插入
  • 建立适当的数据库索引
  • 考虑使用内存数据库暂存后批量写入

3. PyExecJS 解密方案深度剖析

当面对加密接口时,直接调用 JavaScript 解密逻辑往往比模拟浏览器更高效可靠。下面详细解析实现过程。

3.1 JavaScript 逆向分析要点

通过浏览器开发者工具分析,可以发现 aqistudy.cn 的数据接口具有以下特征:

  1. 请求参数加密:使用 Base64 + AES 加密
  2. 响应数据加密:采用 Base64 + AES + DES 多层加密
  3. 核心函数混淆:关键函数名被随机化处理

典型的加密调用链:

getData() → getServerData() → getParam() → 发起请求 → decodeData() 解密

3.2 PyExecJS 环境搭建

确保系统已安装 Node.js,然后配置 Python 环境:

pip install PyExecJS

建议验证执行环境:

import execjs
print(execjs.get().name)  # 应显示 Node.js 或其它 JavaScript 环境

3.3 完整加解密实现

将反混淆后的 JavaScript 保存为 aqistudy.js ,然后实现以下调用逻辑:

import execjs
import requests

class AQIDecryptor:
    def __init__(self, js_file='aqistudy.js'):
        with open(js_file, 'r', encoding='utf-8') as f:
            self.ctx = execjs.compile(f.read())
    
    def get_encrypted_params(self, method, city, start_time, end_time):
        js_func = f'getPostParamCode("{method}", "{city}", "HOUR", "{start_time}", "{end_time}")'
        return self.ctx.eval(js_func)
    
    def decrypt_data(self, encrypted):
        return self.ctx.eval(f'decodeData("{encrypted}")')

# 使用示例
decryptor = AQIDecryptor()
params = decryptor.get_encrypted_params(
    method="GETCITYWEATHER",
    city="北京",
    start_time="2023-01-01 00:00:00",
    end_time="2023-01-01 23:00:00"
)

response = requests.post(
    "https://www.aqistudy.cn/apinew/aqistudyapi.php",
    data={'d': params}
)

decrypted = decryptor.decrypt_data(response.text)

关键点说明:

  • 保持 JavaScript 环境与浏览器一致
  • 处理可能的编码问题(特别是中文)
  • 注意时间参数的格式匹配

4. 技术方案对比与选型建议

我们从六个维度对两种方案进行系统对比:

评估维度 Selenium + Pandas PyExecJS
开发复杂度 低 ★★★★☆ 高 ★★☆☆☆
执行效率 低 ★★☆☆☆ 高 ★★★★☆
资源占用 高 ★★☆☆☆ 低 ★★★★☆
反爬绕过能力 中 ★★★☆☆ 高 ★★★★☆
数据完整性 高 ★★★★☆ 中 ★★★☆☆
长期维护成本 中 ★★★☆☆ 高 ★★☆☆☆

选型决策树:

  1. 是否需要处理非表格数据? → 是 → 考虑 PyExecJS
  2. 是否要求高频采集? → 是 → 优先 PyExecJS
  3. 是否涉及复杂交互? → 是 → 选择 Selenium
  4. 是否有 JavaScript 逆向经验? → 否 → 选择 Selenium

在实际项目中,我曾遇到一个需要同时采集历史数据和实时预警的场景。最终采用混合方案:用 Selenium 处理复杂页面交互获取元数据,通过 PyExecJS 高频调用接口获取实时数据,既保证了可靠性又提升了效率。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐