空气质量预测分析系统是一个结合数据采集、存储、清洗、分析和可视化的完整解决方案。作为一名长期从事数据工程开发的从业者,我选择Python作为核心开发语言主要基于以下几个考量:
生态完整性:Python在数据科学领域拥有最完整的工具链,从爬虫(Scrapy/Requests)到数据处理(Pandas/Numpy),再到机器学习(Scikit-learn/Prophet)和可视化(Matplotlib/Echarts),形成了无缝衔接的工作流。
开发效率:相比Java等语言,Python的简洁语法和动态特性能够快速实现业务逻辑迭代。特别是在数据处理环节,Pandas的DataFrame操作比传统SQL更加灵活。
社区支持:遇到加密反爬或算法调优问题时,Python社区总能提供丰富的解决方案参考。
技术栈的完整构成如下:
提示:在实际企业级应用中,建议将爬虫模块独立部署,通过消息队列(如RabbitMQ)与主系统解耦,避免反爬策略影响核心业务。
目标空气质量数据网站采用了典型的动态加密策略,主要防御手段包括:
解决方案采用分层突破策略:
python复制import requests
import execjs
# 1. 初始化会话
session = requests.Session()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
# 2. 加载JS加密函数
with open('encrypt.js') as f:
js_code = f.read()
ctx = execjs.compile(js_code)
# 3. 构造加密参数
params = {
'timestamp': ctx.call('getEncryptedTime'),
'token': ctx.call('generateToken', 'secret_key')
}
# 4. 发起请求
response = session.get(
'https://air-quality.com/api/data',
params=params,
headers=headers
)
在实际爬取过程中,我们遇到了几个典型问题及解决方案:
加密函数动态更新:
请求指纹检测:
数据延迟加载:
重要提示:商业项目务必遵守robots.txt协议,控制请求频率在合理范围(建议≤1req/s),避免对目标服务器造成负担。
采用SQLAlchemy作为ORM工具,核心表结构设计如下:
python复制from sqlalchemy import Column, Integer, String, Float, DateTime
class AirQuality(Base):
__tablename__ = 'air_quality'
id = Column(Integer, primary_key=True)
city = Column(String(50), index=True) # 建立索引提高查询效率
aqi = Column(Integer)
pm25 = Column(Float)
pm10 = Column(Float)
co = Column(Float)
no2 = Column(Float)
so2 = Column(Float)
record_time = Column(DateTime, index=True)
# 复合索引提升时间范围查询性能
__table_args__ = (
Index('idx_city_time', 'city', 'record_time'),
)
实际应用中我们发现了几个性能优化点:
bulk_insert_mappings比单条insert快20倍pool_size=20, max_overflow=30应对并发高峰原始数据常见问题及处理方案:
| 问题类型 | 检测方法 | 处理方案 |
|---|---|---|
| 缺失值 | df.isnull().sum() | 时间序列使用线性插值 |
| 异常值 | 3σ原则或IQR | 用滑动窗口均值替换 |
| 重复数据 | df.duplicated() | 保留最后一条记录 |
| 单位不一致 | 值域分析 | 统一转换为μg/m³ |
清洗代码示例:
python复制def clean_air_data(df):
# 处理缺失值
df['pm25'] = df['pm25'].interpolate(method='time')
# 去除异常值
q_low = df['aqi'].quantile(0.01)
q_hi = df['aqi'].quantile(0.99)
df = df[(df['aqi'] > q_low) & (df['aqi'] < q_hi)]
# 时间字段标准化
df['record_time'] = pd.to_datetime(df['record_time'],
format='%Y-%m-%d %H:%M:%S')
return df
Facebook Prophet算法本质上是基于加性模型的时序预测方法,其核心公式:
code复制y(t) = g(t) + s(t) + h(t) + εₜ
其中:
针对空气质量数据的特点,我们进行了以下调优:
季节参数:
python复制model = Prophet(
yearly_seasonality=True,
weekly_seasonality=True,
daily_seasonality=False, # AQI通常不显示日周期
seasonality_mode='multiplicative'
)
变点检测:
python复制model.add_seasonality(
name='monthly',
period=30.5,
fourier_order=5
)
特殊事件处理:
python复制lockdown = pd.DataFrame({
'holiday': 'lockdown',
'ds': pd.to_datetime(['2020-01-23', '2020-04-08']),
'lower_window': 0,
'upper_window': 7
})
model.holidays = lockdown
使用滚动预测法评估模型性能:
python复制from sklearn.metrics import mean_absolute_error
# 时间序列交叉验证
df_cv = cross_validation(
model,
initial='180 days',
period='30 days',
horizon='60 days'
)
# 计算MAE
mae = mean_absolute_error(df_cv['y'], df_cv['yhat'])
print(f'MAE: {mae:.2f}')
实际项目中我们发现:
前端架构采用经典的三层模式:
数据接口层:Django REST framework提供JSON API
python复制class AQIViewSet(viewsets.ModelViewSet):
queryset = AirQuality.objects.filter(city='北京')
serializer_class = AirQualitySerializer
@action(detail=False)
def forecast(self, request):
data = get_prophet_forecast()
return Response(data)
可视化配置层:ECharts选项动态生成
javascript复制function initChart(city) {
$.get(`/api/aqi/?city=${city}`, function(data) {
let option = {
tooltip: { trigger: 'axis' },
xAxis: { type: 'category' },
yAxis: { name: 'AQI指数' },
series: [{
data: data.map(item => item.aqi),
type: 'line',
smooth: true
}]
};
chart.setOption(option);
});
}
交互控制层:Bootstrap响应式布局
html复制<div class="row">
<div class="col-md-8">
<div id="main-chart" style="height:400px"></div>
</div>
<div class="col-md-4">
<select class="form-control" onchange="initChart(this.value)">
<option value="北京">北京</option>
<option value="上海">上海</option>
</select>
</div>
</div>
数据缓存:对预测结果使用Redis缓存
python复制from django.core.cache import cache
def get_forecast_data():
key = 'forecast_cache'
data = cache.get(key)
if not data:
data = expensive_calculation()
cache.set(key, data, timeout=3600)
return data
按需加载:实现地图数据的动态导入
javascript复制function loadMapData() {
import('echarts/map/js/china').then(() => {
chart.setOption({
series: [{
type: 'map',
map: 'china'
}]
});
});
}
WebWorker:将复杂计算移出主线程
javascript复制const worker = new Worker('calc.js');
worker.postMessage({data: bigData});
worker.onmessage = function(e) {
updateChart(e.data);
};
推荐使用Docker-Compose编排服务:
yaml复制version: '3'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- ./app:/app
depends_on:
- redis
- db
redis:
image: redis:alpine
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: example
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
关键配置要点:
Gunicorn调优:
bash复制gunicorn --workers=4 --threads=2 --bind 0.0.0.0:8000 core.wsgi
Celery定时任务:
python复制@app.task
def update_air_data():
scrape_data()
clean_data()
update_forecast()
日志监控:
python复制LOGGING = {
'handlers': {
'file': {
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/var/log/airquality.log',
'maxBytes': 1024*1024*5,
}
}
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 预测结果异常 | 节假日未配置 | 添加重要事件到Prophet |
| 图表加载慢 | 数据量过大 | 启用分页或采样 |
| 爬虫被封 | 行为特征明显 | 使用代理IP轮询 |
| 数据库连接耗尽 | 连接泄漏 | 检查Session关闭 |
我在实际运维中总结的几个经验:
VACUUM ANALYZE优化PostgreSQL基于现有系统,可以进一步深化:
技术选型建议:
这个项目的核心价值在于展示了如何将多种技术有机整合,构建端到端的数据应用系统。从爬虫攻防到预测算法,再到可视化呈现,每个环节都有值得深入优化的空间。