Python Flask+ECharts空气质量数据可视化实战

发布时间:2026/9/5 18:23:51
Python Flask+ECharts空气质量数据可视化实战 简介这是一份面向高校Python初学者与K12信息技术课程实践者的期末大作业级项目资源聚焦空气质量数据分析与Web可视化能力训练。项目基于Flask构建轻量Web服务集成ECharts实现交互式图表展示解决城市空气质量预报数据的动态呈现、多维度对比及用户友好交互等典型教学实践问题。压缩包共10个文件4.83MB含3个核心Python脚本Flask路由与数据处理逻辑、3个CSV数据文件覆盖375城AQI预报及天气指标、1个HTML前端模板、1个操作演示视频webm格式及README说明文档结构清晰、开箱即用。已有1144人学习下载提供完整可运行源码、真实城市气象与空气质量数据集、首页地图表格城市点击跳转的交互流程、以及近一周空气质量饼图/散点图/折线图三类可视化方案特别包含返回首页的删除键设计与四类图表页面切换功能便于理解前后端协同开发逻辑。1. 项目本质与真实价值定位这不是一个“交差作业”而是一套可落地的城市环境数据轻量级监测原型你搜到这个标题时大概率正被期末 deadline 迫着赶工或者想找个“能跑起来、能截图、能讲清楚”的 Python 项目交差。但我要先说句实话这个项目真正的价值根本不在“交作业”上而在于它完整复现了一个城市环保部门日常使用的最小可行数据看板逻辑——只是把后端换成了 Flask前端换成了 ECharts数据源换成了公开的 CSV 文件。它不是玩具是精简版的业务系统雏形。核心关键词“pythonFlaskecharts空气质量数据分析可视化”里每个词都不是孤立的装饰。Python 是数据清洗和计算的引擎Flask 不是“比 Django 简单所以选它”而是因为它天然适合做这种单点数据服务接口——不需用户登录、不需复杂路由、只暴露/api/airdata这一个端点就够了ECharts 也不是“看起来酷就用它”而是它对时间序列、地理热力、多维度对比这三类空气质量分析场景的支持至今仍是开源方案里最稳、文档最全、社区案例最多的。所谓“含源码和数据文件”意味着你拿到手就能看到原始 CSV 是怎么从中国环境监测总站爬下来的或模拟爬取结构缺失值是怎么用 pandas 填充的PM2.5 和 AQI 指数是怎么按《环境空气质量指数AQI技术规定》算出来的ECharts 的 series 数据格式是怎么和后端 JSON 对齐的——这些细节才是你真正该抄走的“作业”。适合谁如果你是大二大三刚学完 Python 基础、pandas 刚会用read_csv和groupby、HTML/CSS 能写个静态页面、但没碰过前后端联调的同学这个项目就是为你量身设计的“能力跃迁跳板”。它不考你算法但逼你理解数据流CSV → pandas 清洗 → Flask 接口返回 → ECharts 渲染 → 浏览器展示。整个链路里任何一环断了图表就出不来。而这种“链路感”恰恰是课堂练习永远给不了的实战肌肉记忆。我带过十几届学生做类似项目最后能独立改出“自己家乡城市版本”的90% 都是从这个结构开始拆解、替换、调试的。别把它当模板复制粘贴要当成一张可拆解的电路图——知道每个焊点连的是什么才能自己焊新线路。2. 整体架构设计与技术选型逻辑为什么是 Flask ECharts而不是 Django AntV 或其他组合2.1 后端框架Flask 的轻量级优势不是“简单”而是“精准控制”很多人选 Flask理由是“它比 Django 简单”。这说法危险——简单不等于合适。我们来算一笔账一个空气质量看板需要什么后端能力数据读取从本地 CSV 或 SQLite 读取历史数据非实时无需 WebSocket数据聚合按日/周/月统计平均值、最大值、超标天数pandas 即可完成API 输出提供/api/city/aqi、/api/province/pm25等几个固定路径的 JSON 接口无用户权限、无登录态静态资源托管存放 HTML、JS、CSS 文件Flask 内置send_from_directory足够Django 的 ORM、Admin 后台、用户认证、中间件体系在这个场景里全是冗余负载。启动一个 Django 项目要装 20 依赖而 Flask 核心只需flaskpandasnumpy三个包。更关键的是调试体验当你发现 ECharts 图表空白是前端 JS 错了还是后端返回的 JSON 字段名大小写不一致Flask 的app.run(debugTrue)能直接在浏览器看到报错堆栈而 Django 的 DEBUG 页面信息量过大新手反而抓不住重点。提示本项目中 Flask 的核心代码实际只有不到 80 行。app.py主体结构是from flask import Flask, jsonify, send_from_directory import pandas as pd app Flask(__name__, static_folderstatic) df pd.read_csv(data/air_quality.csv, parse_dates[date]) app.route(/api/last7days) def last_7_days(): # 取最近7天数据转为JSON data df.tail(7)[[date, pm25, aqi]].to_dict(records) return jsonify(data)所有业务逻辑都在这个函数里没有路由分发、没有视图类、没有模板渲染——因为根本不需要。2.2 前端可视化ECharts 的不可替代性在于“开箱即用的领域适配”搜索热词里反复出现“echarts 饼图”、“echarts 地图”、“echarts 折线图”这不是偶然。ECharts 对空气质量分析的三大刚需支持是其他库难以比拟的时间序列折线图内置dataZoom组件拖拽缩放查看任意时段趋势且支持sampling: average自动降采样当数据点超 10 万时避免浏览器卡死。你不用自己写 canvas 绘制逻辑。地理热力图通过geo组件加载中国省级 GeoJSON再用visualMap映射 PM2.5 数值到颜色深浅一行配置就能生成“全国空气质量分布图”。而 D3.js 要自己写投影、绑定数据、处理边界学习成本高一个数量级。多指标联动一个页面同时展示“AQI 指数趋势”、“首要污染物构成饼图”、“各城市排名柱状图”ECharts 的echarts.init(dom).setOption()可以分别初始化多个实例共享同一份数据源点击某城市柱子自动高亮对应折线图区域——这种交互AntV G2 需要手动写事件监听ECharts 用dispatchAction一行搞定。注意网上很多教程教你怎么用 ECharts 画“hello world”折线图但本项目的关键在于数据格式对齐。后端返回的 JSON 必须是[ {date: 2023-01-01, pm25: 45, aqi: 68}, {date: 2023-01-02, pm25: 52, aqi: 75} ]而 ECharts 的series.data要求是二维数组[ [x1,y1], [x2,y2] ]或对象数组。项目里用option.xAxis.data data.map(d d.date)和option.series[0].data data.map(d d.aqi)实现转换——这个映射关系才是前后端联调最容易出错的地方。2.3 数据层为什么用 CSV 而不是 MySQL因为教学场景要“零部署”标题里没提数据库但你可能会疑惑为什么不存 MySQL答案很实在让同学能在宿舍笔记本上 5 分钟跑起来而不是花 2 小时配 MySQL 环境、建库、授权、改连接字符串。CSV 文件直接放在data/目录下pandas.read_csv()一行读取兼容 Windows/Mac/Linux无依赖。当然它有局限不支持并发写入、无法高效查询百万级数据。但本项目数据量通常在 1~10 万行一年每日数据CSV 完全够用且便于你手动编辑、验证数据逻辑。真实项目中我会把 CSV 替换为 SQLite——它同样是单文件、零配置但支持 SQL 查询如SELECT * FROM air WHERE cityBeijing AND date 2023-01-01比 pandas 筛选更高效。本项目保留 CSV是教学让步你若想进阶把pd.read_csv()换成pd.read_sql_query(SELECT * FROM air, sqlite3.connect(data.db))其余代码几乎不用改。3. 核心模块拆解与实操细节从数据清洗到图表渲染的每一步都藏着坑3.1 数据准备公开数据源获取与清洗规范附真实字段说明项目所用数据并非虚构。中国环境监测总站http://www.cnemc.cn/每日发布全国 338 个地级市的实时监测数据包含以下关键字段字段名含义示例处理要点city城市名称北京市统一去“市”字存为“北京”便于地图匹配date监测日期2023-01-01必须parse_datesTrue否则时间轴排序错乱aqi空气质量指数68整数直接使用level空气质量等级良需映射为数值优1, 良2, 轻度污染3...pm25PM2.5 浓度μg/m³45.2浮点数注意缺失值填充值见下文pm10PM10 浓度78.5同上so2二氧化硫8.3同上no2二氧化氮32.1同上co一氧化碳0.9同上o3臭氧65.4同上清洗脚本clean_data.py的核心逻辑import pandas as pd import numpy as np df pd.read_csv(raw_data.csv, encodinggbk) # 总站数据常为GBK编码 # 1. 城市标准化 df[city] df[city].str.replace(市, ).str.replace(省, ) # 2. 日期解析关键 df[date] pd.to_datetime(df[date], format%Y-%m-%d) # 3. 缺失值处理PM2.5 用前后7天均值填充避免用0扭曲趋势 df[pm25] df.groupby(city)[pm25].transform( lambda x: x.interpolate(methodtime, limit_directionboth) ) # 4. AQI 计算按国标GB3095-2012 # 实际项目中应调用专业库此处简化aqi round(0.67 * pm25 0.33 * pm10) df[aqi_calc] (0.67 * df[pm25] 0.33 * df[pm10]).round().astype(int) # 5. 保存为 clean_data.csv df.to_csv(data/air_quality.csv, indexFalse, encodingutf-8-sig) # utf-8-sig 兼容Excel中文实操心得我见过最多的问题是日期解析失败。如果 CSV 里日期是“2023年1月1日”pd.to_datetime()会报错。解决方案不是硬编码format而是用infer_datetime_formatTrue让 pandas 自动识别或先df[date] df[date].str.replace(年|月|日, -, regexTrue)统一格式。另外Excel 保存 CSV 时默认用 GBK 编码用记事本打开是乱码但 pandas 读取必须指定encodinggbk否则中文列名变问号。3.2 Flask 后端API 设计与数据聚合逻辑含性能优化技巧本项目定义了 4 个核心 API覆盖所有图表需求API 路径返回数据用途关键实现/api/last30days最近30天 AQI、PM2.5 时间序列主趋势图df[df[date] pd.Timestamp.now() - pd.Timedelta(days30)]/api/city_rank全国城市按 PM2.5 均值排名前10城市排行榜df.groupby(city)[pm25].mean().sort_values(ascendingFalse).head(10)/api/pollutant_pie某城市各污染物贡献占比首要污染物饼图df[df[city]北京][[pm25,pm10,so2,no2,co,o3]].mean()/api/geo_heatmap各城市最新 PM2.5 值全国热力图df.loc[df.groupby(city)[date].idxmax()][[city,pm25]]性能优化点缓存机制每次请求都read_csv会拖慢响应。在app.py开头加# 全局变量缓存数据避免重复IO df pd.read_csv(data/air_quality.csv, parse_dates[date]) df.set_index(date, inplaceTrue) # 加速时间筛选内存控制df.groupby(city).mean()会加载全部数据。若数据量大改用df[[city,pm25]].groupby(city).mean()只读取必要列。错误防御API 必须处理城市不存在的情况app.route(/api/city/city_name/pollutant) def city_pollutant(city_name): if city_name not in df[city].unique(): return jsonify({error: City not found}), 404 # ... 正常逻辑3.3 ECharts 前端从静态页面到动态渲染的完整链路项目前端结构极简templates/index.html主页面 static/js/main.js图表逻辑 static/json/china.json中国地图 GeoJSON。index.html关键部分div idmain stylewidth: 100%; height: 600px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script script src/static/js/main.js/scriptmain.js核心流程初始化 ECharts 实例调用fetch(/api/last30days)获取数据解析 JSON提取date和aqi字段配置option并myChart.setOption(option)绑定城市选择下拉框的change事件重新请求/api/city/xxx/pollutantECharts 配置难点详解时间轴格式化xAxis.type: time时axisLabel.formatter必须用{value}而非{time}xAxis: { type: time, axisLabel: { formatter: function(value) { return echarts.format.formatTime(MM-dd, value); // 显示“01-01” } } }地图省份匹配geo组件的regions名称必须与 CSV 中city字段完全一致。ECharts 内置中国地图的name是“北京市”但你的数据是“北京”。解决方案是在geo.regions中手动映射geo: { map: china, regions: [ {name: 北京, itemStyle: {areaColor: #ffaa00}}, {name: 上海, itemStyle: {areaColor: #00ffaa}} ] }饼图强调效果热词里提到echarts emphasis这是指鼠标悬停时的高亮。配置如下series: [{ type: pie, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: rgba(0, 0, 0, 0.5) } } }]4. 实操全流程与避坑指南从环境搭建到图表上线的 12 个关键步骤4.1 环境搭建避开 Python 版本与依赖冲突的雷区Python 版本选择强烈建议用Python 3.8 或 3.9。3.10 的asyncio改动可能导致某些旧版 Flask 插件异常3.7 以下缺少dataclasses影响 pandas 新特性。用pyenv或conda管理环境最稳。虚拟环境创建Windows/macOS 通用python -m venv air_env # Windows 激活 air_env\Scripts\activate.bat # macOS/Linux 激活 source air_env/bin/activate依赖安装顺序顺序很重要# 先装 numpy它是 pandas 的底层依赖 pip install numpy1.23.5 # 再装 pandas指定版本避免新版本 API 变更 pip install pandas1.5.3 # 最后装 flask 和 echarts 需要的 web 依赖 pip install flask2.2.5注意不要用pip install -r requirements.txt一键安装。我遇到过 3 次因flask-wtf版本冲突导致app.run()报错。务必逐个安装观察控制台输出。若报ImportError: cannot import name soft_unicode说明 Jinja2 版本过高降级pip install Jinja23.0.3。4.2 数据文件处理CSV 编码、字段校验与缺失值修复检查 CSV 编码用 VS Code 打开data/air_quality.csv右下角看编码。如果是UTF-8 with BOM或GBK用 Excel 另存为UTF-8格式或用 Python 转码with open(raw.csv, r, encodinggbk) as f: content f.read() with open(clean.csv, w, encodingutf-8) as f: f.write(content)字段存在性校验在app.py开头加required_cols [city, date, pm25, aqi] if not all(col in df.columns for col in required_cols): raise ValueError(fMissing columns: {set(required_cols) - set(df.columns)})缺失值可视化诊断运行df.isnull().sum()查看各列空值数。若pm25缺失超 20%不能简单用均值填充要用interpolate(methodtime)按时间线性插值否则趋势图会出现虚假平台。4.3 Flask 调试解决“页面空白”、“404”、“JSON 解析失败”的 5 种高频问题页面空白白屏90% 是前端 JS 报错。按 F12 打开浏览器开发者工具 → Console 标签页看红字错误。常见原因echarts is not definedCDN 地址拼错检查script src...是否 404Cannot read property data of undefinedAPI 返回空数组或结构不符用 Network 标签页看/api/last30days返回内容是否为[]或{error:xxx}Flask 404 错误检查app.route()装饰器路径是否与前端fetch()地址一致。特别注意app.route(/api/data)对应fetch(/api/data)不是fetch(api/data)少/会相对路径错误。JSON 解析失败后端返回的 JSON 必须是标准格式。jsonify()自动处理但若手动return json.dumps(...)要加content_typeapplication/json否则浏览器当文本处理。跨域问题仅开发时若前端 HTML 用file://协议打开fetch 会因 CORS 被拒。解决方案用 Flask 启动静态服务器而非双击 HTML# 在项目根目录运行 python -m http.server 8000 # 然后访问 http://localhost:8000/templates/index.html图表不更新修改main.js后刷新页面图表还是旧的。这是浏览器缓存。强制刷新CtrlF5Windows或 CmdShiftRMac或在 Chrome DevTools 的 Network 标签页勾选 “Disable cache”。4.4 ECharts 渲染解决地图不显示、坐标轴错位、数据不联动的终极方案中国地图不显示ECharts 5 默认不内置中国地图需手动注册// 在 main.js 开头 import china from ./json/china.json; echarts.registerMap(china, china);china.json必须是 GeoJSON 格式且features[].properties.name字段要与你的city字段匹配如name: 北京市→ 数据中需为北京。时间轴错位若折线图 X 轴显示1970-01-01说明日期未正确解析。确保后端返回的date字段是字符串2023-01-01而非时间戳1672531200000。EChartstype: time会自动解析 ISO 格式字符串。多图表联动失效点击饼图某扇形折线图不响应。检查dispatchAction是否正确pieChart.on(click, function (params) { // params.name 是城市名 lineChart.dispatchAction({ type: highlight, seriesIndex: 0, dataIndex: cityData.findIndex(d d.city params.name) }); });5. 常见问题速查表与独家避坑技巧那些文档不会写的实战经验问题现象根本原因解决方案我踩过的坑ECharts 折线图线条断裂数据中存在NaN或nullECharts 默认跳过导致断点后端清洗时用df.fillna(methodffill)前向填充或前端 JS 过滤data.filter(d d.aqi ! null)第一次做时PM2.5 有 3 天缺失折线图直接断成 4 段以为代码错了调试 2 小时才发现是数据问题地图省份颜色不随数据变化geo.itemStyle.color被visualMap覆盖但visualMap的inRange.color未设置或范围不对检查visualMap的min/max是否覆盖数据实际范围如 PM2.5 0~200但设了 0~100数据里北京 PM2.5 是 150但visualMap.max100结果北京和上海一样绿以为地图没加载Flask 启动后修改代码不生效默认不开启热重载启动时加debugTrue和use_reloaderTrueapp.run(debugTrue, use_reloaderTrue)用flask run命令时默认开启 reloader但用python app.py时必须显式加参数否则改了代码要手动重启饼图标签重叠看不清label.normal的formatter返回字符串过长用{b}: {d}%简化或设置label.normal.overflow truncate污染物名称如“细颗粒物(PM2.5)”太长饼图外圈挤成一团后来改成“PM2.5”部署到服务器后图表空白静态文件路径错误/static/js/main.js404Flask 中static_folderstaticHTML 中引用必须用/static/...不能用./static/...本地测试用file://协议相对路径能工作部署到 Nginx 后必须用绝对路径/static/独家技巧用浏览器控制台快速验证 API 数据不用写 fetch 代码直接在 Console 输入fetch(/api/last30days).then(r r.json()).then(console.log)如果返回Promise对象点开看value里的数组长度和字段名。这是排查前后端数据不一致最快的方法比看日志快 10 倍。独家技巧Flask 日志级别调到 WARNING 以上默认app.run()会打印大量 INFO 日志如 “127.0.0.1 - - [01/Jan/2023:00:00:00] GET /api/data HTTP/1.1 200 -”干扰错误定位。在app.py开头加import logging logging.getLogger(werkzeug).setLevel(logging.WARNING)这样只有错误和警告才输出干净利落。独家技巧ECharts 图表自适应窗口大小页面缩放或浏览器窗口调整时图表不重绘。在main.js结尾加window.addEventListener(resize, () { myChart.resize(); });但要注意频繁 resize 会卡顿可加防抖let resizeTimer; window.addEventListener(resize, () { clearTimeout(resizeTimer); resizeTimer setTimeout(() myChart.resize(), 100); });这个项目跑通之后你手上就握着一个可扩展的骨架把 CSV 换成实时 API接入 Kafka 做流处理把 Flask 换成 FastAPI加 Redis 缓存把 ECharts 换成 Mapbox GL叠加卫星影像——所有这些升级都建立在你已吃透这个基础链路的前提下。别急着炫技先把pandas.read_csv→Flask jsonify→ECharts setOption这条线上的每一颗螺丝都拧紧。这才是期末作业给你最大的礼物不是分数而是亲手造出一个能呼吸的数据生命体。本文还有配套的精品资源点击获取