从数据到洞察:Python爬虫与机器学习驱动的空气质量预测实战

当城市上空笼罩着灰蒙蒙的雾霾,我们是否能够提前预知明天的空气质量?本文将带你深入探索如何用Python构建从数据采集到预测分析的完整解决方案,揭示隐藏在空气质量数据中的规律与趋势。

1. 空气质量数据采集:高效爬虫设计

获取准确、及时的空气质量数据是预测分析的第一步。我们选择PM2.5.in作为数据源,这个网站提供了全国主要城市的实时空气质量指数(AQI)及各项污染物浓度数据。

1.1 爬虫核心组件设计

import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import random

class AQISpider:
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
        self.base_url = "https://www.pm25.in"
        
    def get_city_list(self):
        """获取所有支持的城市列表"""
        response = requests.get(self.base_url, headers=self.headers)
        soup = BeautifulSoup(response.text, 'html.parser')
        city_links = soup.select('.city_list a')
        return {link.text: link['href'] for link in city_links}

关键点说明

  • 使用随机User-Agent轮换避免反爬
  • 采用BeautifulSoup解析HTML结构
  • 城市列表动态获取确保数据全面性

1.2 数据抓取与存储优化

def fetch_city_aqi(self, city_name, city_path):
    """获取单个城市的详细AQI数据"""
    url = f"{self.base_url}{city_path}"
    try:
        response = requests.get(url, headers=self.headers)
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 解析表格数据
        table = soup.find('table', {'id': 'detail-data'})
        rows = table.find_all('tr')[1:]  # 跳过表头
        
        data = []
        for row in rows:
            cols = row.find_all('td')
            record = {
                'city': city_name,
                'time': cols[0].text.strip(),
                'aqi': int(cols[1].text),
                'pm2_5': int(cols[3].text),
                # 其他污染物字段...
            }
            data.append(record)
        
        return pd.DataFrame(data)
    
    except Exception as e:
        print(f"Error fetching {city_name}: {str(e)}")
        return None

存储策略对比

存储方式优点缺点适用场景
CSV文件简单易用,无需额外服务查询效率低,不适合大数据量小型项目,临时存储
SQLite轻量级,单文件数据库并发性能有限中小型项目,本地分析
MySQL支持复杂查询,性能好需要单独部署维护生产环境,团队协作
MongoDB灵活schema,适合非结构化数据内存占用较高快速迭代,文档型数据

提示:对于长期监测项目,建议采用MySQL+MongoDB混合存储方案,结构化数据存入MySQL,原始HTML快照存入MongoDB

2. 数据清洗与特征工程

原始数据往往包含噪声和缺失值,高质量的特征工程能显著提升模型性能。

2.1 数据清洗实战

def clean_aqi_data(df):
    """数据清洗流程"""
    # 处理缺失值
    df = df.dropna(subset=['aqi', 'pm2_5'])
    
    # 去除异常值
    df = df[(df['aqi'] > 0) & (df['aqi'] <= 500)]
    df = df[(df['pm2_5'] >= 0) & (df['pm2_5'] <= 500)]
    
    # 时间字段标准化
    df['datetime'] = pd.to_datetime(df['time'])
    df['hour'] = df['datetime'].dt.hour
    df['day_of_week'] = df['datetime'].dt.dayofweek
    
    return df

2.2 特征构建技巧

  • 时间特征:小时、星期、是否为节假日
  • 气象特征:温度、湿度、风速(可从气象API获取)
  • 空间特征:城市经纬度、海拔高度
  • 滞后特征:前3/6/12小时AQI值
  • 移动统计:24小时AQI移动平均、标准差
# 创建滞后特征示例
def create_lag_features(df, lags=[3, 6, 12]):
    for lag in lags:
        df[f'aqi_lag_{lag}'] = df.groupby('city')['aqi'].shift(lag)
    return df

# 添加移动统计量
df['aqi_rolling_24h_mean'] = df.groupby('city')['aqi'].rolling(24).mean().values

3. 时间序列预测模型构建

空气质量数据具有明显的时间依赖性,我们需要选择合适的时序模型。

3.1 模型选型对比

模型类型优点缺点适用场景
ARIMA理论成熟,解释性强需手动确定参数,线性假设平稳时间序列
Prophet自动处理季节项,鲁棒性强计算资源消耗较大具有明显季节规律的数据
LSTM捕捉长期依赖,非线性建模需要大量数据,调参复杂复杂时序模式
XGBoost特征重要性明确,性能优越需手动构建时序特征特征丰富的场景

3.2 LSTM模型实现

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout

def build_lstm_model(input_shape):
    model = Sequential([
        LSTM(64, return_sequences=True, input_shape=input_shape),
        Dropout(0.2),
        LSTM(32),
        Dropout(0.2),
        Dense(1)
    ])
    
    model.compile(optimizer='adam', loss='mse')
    return model

# 数据标准化
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaled_data = scaler.fit_transform(df[['aqi', 'pm2_5']])

# 创建时间步长数据
def create_dataset(data, time_steps=24):
    X, y = [], []
    for i in range(len(data)-time_steps):
        X.append(data[i:(i+time_steps)])
        y.append(data[i+time_steps, 0])  # 预测aqi
    return np.array(X), np.array(y)

X, y = create_dataset(scaled_data)
model = build_lstm_model((X.shape[1], X.shape[2]))
model.fit(X, y, epochs=50, batch_size=32, validation_split=0.2)

4. 预测结果可视化与分析

直观的视觉呈现有助于理解模型预测效果和数据规律。

4.1 动态预测可视化

import plotly.express as px

def plot_prediction(actual, predicted, city):
    fig = px.line(title=f'{city} AQI预测对比')
    fig.add_scatter(y=actual, name='实际值')
    fig.add_scatter(y=predicted, name='预测值')
    fig.update_layout(
        xaxis_title='时间',
        yaxis_title='AQI',
        hovermode='x unified'
    )
    return fig

# 生成未来24小时预测
future_steps = 24
last_sequence = scaled_data[-time_steps:]
predictions = []
for _ in range(future_steps):
    pred = model.predict(last_sequence.reshape(1, time_steps, -1))
    predictions.append(pred[0,0])
    last_sequence = np.roll(last_sequence, -1, axis=0)
    last_sequence[-1] = pred[0]
    
predictions = scaler.inverse_transform(
    np.column_stack([np.array(predictions), np.zeros(future_steps)])
)[:,0]

4.2 污染物相关性分析

import seaborn as sns

corr_matrix = df[['aqi', 'pm2_5', 'pm10', 'so2', 'no2', 'co', 'o3']].corr()
plt.figure(figsize=(10,8))
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm', center=0)
plt.title('污染物相关性热力图')
plt.show()

典型发现

  • PM2.5与AQI相关系数达0.92,是主要影响因素
  • O3与其他污染物呈负相关,夏季表现明显
  • 风速与AQI呈负相关,验证了扩散作用

5. 系统优化与生产部署

将预测模型转化为可持续运行的服务需要考虑多方面因素。

5.1 自动化数据管道

from airflow import DAG
from airflow.operators.python_operator import PythonOperator
from datetime import datetime, timedelta

default_args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2023, 1, 1),
    'retries': 3,
}

dag = DAG(
    'aqi_prediction_pipeline',
    default_args=default_args,
    schedule_interval='0 3 * * *',  # 每天凌晨3点运行
)

def fetch_data():
    spider = AQISpider()
    df = spider.run()
    df.to_parquet(f'/data/raw/{datetime.now().date()}.parquet')

fetch_task = PythonOperator(
    task_id='fetch_aqi_data',
    python_callable=fetch_data,
    dag=dag,
)

# 添加其他任务节点...

5.2 模型性能监控指标

指标计算公式健康范围说明
预测准确率1 - MAPE>80%平均绝对百分比误差
数据新鲜度当前时间 - 最新数据时间<1小时数据时效性
特征覆盖率可用特征数/总特征数>95%数据完整性
预测稳定性预测结果标准差<10输出波动程度

在实际项目中,我们还需要考虑模型的可解释性。SHAP值分析可以帮助理解各个特征对预测结果的影响程度:

import shap

# 对XGBoost模型进行SHAP分析
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)

plt.figure(figsize=(10,6))
shap.summary_plot(shap_values, X_test, feature_names=feature_names)
plt.title('特征重要性分析')
plt.show()

从分析结果可以发现,历史AQI值、PM2.5浓度和小时时段是最具影响力的三个特征,这与空气质量变化的物理规律高度一致。这种可解释性分析不仅验证了模型的合理性,也为进一步优化特征工程提供了方向。

更多推荐