ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Django+ECharts空气质量可视化:从CSV到交互式看板的完整实战

Django+ECharts空气质量可视化:从CSV到交互式看板的完整实战 简介本资源是一套基于Python与Django框架的城市PM2.5空气质量数据可视化分析项目源码面向计算机相关专业学生及需要完成课程设计、期末大作业的开发者也适合希望入门Django与数据分析的小白实战练习。压缩包共64个文件约12.38MB包含17个py源码文件、24个csv数据文件、3个html模板以及sql数据库脚本、xml配置等覆盖数据导入、模型定义、视图渲染与前端展示的完整链路。项目内置北京、上海、广州、成都、沈阳五座城市六年PM2.5数据围绕月份变化、时段分布、温度、湿度、露点、大气压、风向等维度展开可视化分析并配有登录注册页面与MySQL数据库文件目录结构清晰便于按模块阅读与二次开发。目前已有328人学习下载可作为高分大作业参考模板帮助读者快速理解Django项目组织方式与空气质量数据分析思路。1. 从一份 95 分课设说起这套 Django 空气质量可视化到底能跑出什么期末周前两周实验室里最常见的一幕是选题定了「城市 PM2.5 数据可视化」数据也下了结果卡在「怎么把 CSV 变成能点的网页」这一步。这套源码就是冲着这个场景来的——Python Django 打底把北上广成沈五个城市六年的 PM2.5 数据做成了一套带登录、带多维度图表、带数据库落地的完整 Web 项目。它不是那种只丢一个index.html的玩具而是有models.py、views.py、migrations、db129.sql的完整 Django 工程数据文件按分析维度拆成了「一年中各个月份变化」「pm2.5 与露点/温度/湿度/气压/风向的关系」「一天中不同时段」「不同季度逐年」「时间序列逐年」等十来张 CSV。适合两类人一是要交期末大作业、课程设计的学生二是想拿一个真实数据集练 Django ECharts 前后端串联的入门开发者。下面我按「它是什么 → 怎么跑起来 → 数据怎么进库 → 图表怎么出 → 坑在哪」的顺序拆一遍。2. 环境与工程结构先把 Django 跑起来再谈可视化2.1 为什么是 Django 而不是 Flask这套项目选 Django核心原因是它自带 ORM、admin 后台和 migrations 机制。空气质量数据有十几个维度、五个城市、六年跨度如果用 Flask 手写 SQL 和路由光是建表和增删改查就够写一天。Django 的models.py定义好字段后makemigrationsmigrate两条命令就能把表建出来admin.py注册一下还能直接在后台看数据。对于课设这种「功能要全、时间要短」的场景Django 的「全家桶」属性是省事的关键。项目里app01是主应用untitled是工程配置目录templates放login.html、reg.html、index.html三个页面结构是标准的 Django 布局。2.2 依赖安装与启动步骤拿到压缩包后先别急着runserver按顺序来。项目根目录有requirements.txt先看它锁了哪些版本再决定用不用虚拟环境。# 1. 建虚拟环境推荐避免污染全局 python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 2. 按依赖清单安装 pip install -r requirements.txt # 3. 如果 requirements.txt 里没锁 Django 版本手动补一个兼容版 pip install django pymysql装完依赖后进untitled/settings.py改数据库配置。项目里带了mysql数据库/db129.sql说明原始环境用的是 MySQL。如果你本地没装 MySQL最省事的做法是临时切到 SQLite把DATABASES里的ENGINE改成django.db.backends.sqlite3NAME指向一个本地.sqlite3文件即可。改完执行迁移python manage.py makemigrations python manage.py migrate python manage.py runserver浏览器打开http://127.0.0.1:8000/正常的话会跳到登录页。这里有个细节settings.py里的ALLOWED_HOSTS在开发阶段留空或写[*]都行但DEBUG保持True方便看报错。如果启动时报No module named MySQLdb说明 Django 默认走的是 MySQLdb 驱动而 Python 3 下一般用 pymysql需要在untitled/__init__.py里加两行import pymysql pymysql.install_as_MySQLdb()这两行的作用是让 pymysql 冒充 MySQLdbDjango 的 MySQL 后端就能正常加载。参数上没什么可调的位置必须在包初始化文件里放settings.py里无效因为 Django 加载数据库后端早于 settings 解析完成。2.3 目录里哪些是核心、哪些可以忽略工程里有一堆.idea、misc.xml、inspectionProfiles这些是 PyCharm 的工程配置换编辑器后完全用不上可以直接删。__pycache__是字节码缓存也不影响运行。真正要关注的是四块app01/models.py表结构、app01/views.py业务逻辑和图表数据接口、templates/页面、get_data/数据导入脚本和原始 CSV。data/目录下那批 CSV 和get_data/下的有重叠前者偏分析结果后者偏原始数据和导入脚本别搞混。3. 数据导入把十几张 CSV 灌进 MySQL 的正确姿势3.1 先看清数据文件的维度划分这套项目的数据组织方式值得单独说因为它直接决定了后面图表怎么画。get_data/下有五份城市原始数据上海.csv、广州.csv、成都.csv、北京.csv、沈阳.csv还有一份北上广成沈五城市六年PM2.5数据汇总 1.csv。另外那批按维度命名的 CSV——「一年中各个月份变化」「pm2.5 与露点之间的关系」「一天中不同时段」「与风向之间的关系」「pm2.5 与大气压/温度/相对湿度之间的关系」「不同季度逐年数据」「时间序列逐年数据」——是把汇总数据按分析角度切出来的中间结果。理解这个分层很重要原始数据是「一行一条记录」分析数据是「已经聚合好的统计结果」导入时用的表结构不一样。3.2 用 Django ORM 批量导入而不是手写 SQLget_data/数据导入.py和通用脚本.py是现成的导入脚本但直接跑之前建议先读一遍确认字段名和models.py对得上。如果要对齐自己的表结构常见做法是用 Django 的bulk_create批量插入比逐条save()快一个数量级。下面是一个可复用的导入模板import os import django import pandas as pd # 让脚本能独立运行必须设置 DJANGO_SETTINGS_MODULE os.environ.setdefault(DJANGO_SETTINGS_MODULE, untitled.settings) django.setup() from app01.models import AirQuality # 换成你实际的模型名 def import_csv(filepath, city): df pd.read_csv(filepath, encodingutf-8) # 字段名统一转小写并去空格避免 CSV 表头和模型字段对不上 df.columns [c.strip().lower() for c in df.columns] records [] for _, row in df.iterrows(): records.append(AirQuality( citycity, daterow.get(date), pm25row.get(pm2.5) or row.get(pm25), temperaturerow.get(temperature), humidityrow.get(humidity), pressurerow.get(pressure), dew_pointrow.get(dew_point), wind_directionrow.get(wind_direction), )) # batch_size 控制单次 SQL 的条数太大容易超 max_allowed_packet AirQuality.objects.bulk_create(records, batch_size500) print(f{city} 导入完成共 {len(records)} 条) if __name__ __main__: city_files { 北京: get_data/北京.csv, 上海: get_data/上海.csv, 广州: get_data/广州.csv, 成都: get_data/成都.csv, 沈阳: get_data/沈阳.csv, } for city, path in city_files.items(): import_csv(path, city)逻辑上分三步先django.setup()让脚本能访问 ORM再用 pandas 读 CSV 并规范化列名最后bulk_create批量落库。参数上batch_size500是个经验值MySQL 默认max_allowed_packet是 4MB一条记录按 200 字节算500 条也就 100KB留足了余量。如果 CSV 编码是 GBK国内数据常见把encoding改成gbk否则会报UnicodeDecodeError。3.3 分析类 CSV 要不要入库那批「pm2.5 与露点之间的关系」之类的 CSV本质是聚合结果数据量小、结构固定。我的建议是如果图表是静态的、不随用户筛选变化直接在views.py里用 pandas 读 CSV 转成 JSON 传给前端就行没必要入库。入库反而多一层同步成本。只有当图表需要按城市、按年份动态筛选时才值得把这些维度也建成表。判断标准很简单——看index.html里的图表有没有筛选控件有就入库没有就读文件。3.4 验证数据是否真的进去了导入完别只看脚本打印的条数进 Django shell 查一下python manage.py shellfrom app01.models import AirQuality print(AirQuality.objects.count()) print(AirQuality.objects.filter(city北京).count()) print(AirQuality.objects.values(city).annotate(totalCount(id)))如果总数对得上、各城市分布合理说明导入没问题。如果某个城市是 0八成是 CSV 路径写错或编码不对回去看脚本的报错信息。4. 图表数据接口views.py 里怎么把查询结果喂给 ECharts4.1 前后端数据流的设计思路这套项目的可视化走的是「Django 返回 JSON → 前端 ECharts 渲染」的路线。views.py里每个图表对应一个视图函数查询数据库或读 CSV把结果整理成 ECharts 要的xAxis和series格式用JsonResponse返回。index.html里用 Ajax 或 fetch 拿数据再setOption。这种设计的好处是数据和视图分离换图表类型不用动后端。坏处是每个图表都要写一个接口接口多了容易乱。常见做法是把同类图表合并成一个接口用参数区分维度。4.2 一个可复用的图表接口写法以「一年中各个月份 PM2.5 均值变化」为例后端要做的是按月份分组求平均。用 Django ORM 的annotate配合TruncMonth可以一次查出来from django.db.models.functions import TruncMonth from django.db.models import Avg from django.http import JsonResponse from .models import AirQuality def monthly_pm25(request): city request.GET.get(city, 北京) # 默认北京前端可传参切换 qs (AirQuality.objects .filter(citycity) .annotate(monthTruncMonth(date)) .values(month) .annotate(avg_pm25Avg(pm25)) .order_by(month)) months [item[month].strftime(%Y-%m) for item in qs] values [round(item[avg_pm25], 2) for item in qs] return JsonResponse({ xAxis: months, series: values, city: city, })TruncMonth把日期截断到月values(month).annotate(Avg(pm25))等价于 SQL 的GROUP BY month。order_by(month)不能省否则分组结果的顺序不保证折线图会乱。返回的 JSON 结构直接对应 ECharts 的xAxis.data和series.data前端拿到就能用。参数上city从request.GET取默认值给一个避免前端不传时报错。4.3 前端 ECharts 的接入要点index.html里引入 ECharts 后初始化一个容器然后请求接口var chart echarts.init(document.getElementById(monthlyChart)); fetch(/api/monthly_pm25/?city北京) .then(res res.json()) .then(data { chart.setOption({ title: { text: data.city 月度 PM2.5 均值 }, tooltip: { trigger: axis }, xAxis: { type: category, data: data.xAxis }, yAxis: { type: value, name: PM2.5 (μg/m³) }, series: [{ type: line, data: data.series, smooth: true }] }); });这里的关键是xAxis.type必须是category因为后端返回的是月份字符串。如果返回的是数值型时间戳才用time类型。smooth: true让折线平滑视觉上好看但不是必须。多个图表共用一个页面时记得每个图表用独立的div和独立的echarts.init否则会互相覆盖。4.4 关系类图表的处理差异「pm2.5 与温度/湿度/露点/气压之间的关系」这类图表本质是散点图横轴是气象因子纵轴是 PM2.5。后端不需要分组直接返回原始点对即可def pm25_vs_temp(request): qs AirQuality.objects.filter(city北京).values_list(temperature, pm25) points [[t, p] for t, p in qs if t is not None and p is not None] return JsonResponse({points: points})注意if t is not None and p is not None这个过滤原始数据里空值是常态不过滤的话 ECharts 会画出断点或报错。散点图的数据量大时几千个点以上前端要开large: true和largeThreshold否则渲染会卡。5. 避坑与排查跑这套源码最容易翻车的五个地方5.1 现象runserver 启动报django.core.exceptions.ImproperlyConfigured原因通常是settings.py里的DATABASES配置指向了不存在的 MySQL 实例或者密码、端口写错。解决方式是先确认 MySQL 服务在跑再用mysql -u root -p手动连一次确认账号密码无误。如果不想折腾 MySQL直接切 SQLite改ENGINE和NAME两行即可这是最快的后悔药。5.2 现象页面能打开但图表空白控制台报 404原因是前端请求的接口路径和urls.py里注册的对不上。Django 的 URL 匹配是精确的/api/monthly_pm25/和/api/monthly_pm25差一个斜杠就是 404。解决方式是打开浏览器开发者工具的 Network 面板看请求的实际 URL 和响应状态再回urls.py核对。另外app01/urls.py如果不存在需要在工程级urls.py里用include引入别漏了。5.3 现象CSV 导入时报UnicodeDecodeError: utf-8 codec cant decode国内气象数据很多是 GBK 或 GB2312 编码pandas 默认按 UTF-8 读就会炸。解决办法是在read_csv里显式指定encodinggbk或者用chardet先探测编码。如果文件里有 BOM 头用encodingutf-8-sig。这个坑几乎每个处理中文 CSV 的人都会踩一次血泪经验是拿到文件先file -i xxx.csv看一眼编码别硬猜。5.4 现象bulk_create导入到一半报Packet too large原因是单批数据量超过了 MySQL 的max_allowed_packet。解决方式有两个一是把batch_size从 500 降到 100 或 200二是改 MySQL 配置max_allowed_packet64M后重启服务。前者改代码就行后者要动数据库配置课设环境建议用前者。另外bulk_create不会触发save()方法如果模型里重写了save()做额外处理批量导入会跳过这点要注意。5.5 现象图表数据对不上明明数据库有数据却显示为空常见原因是查询条件里的城市名和数据库里存的不一致比如数据库存的是「北京市」查询用的是「北京」。解决方式是先用values_list(city, flatTrue).distinct()看一眼实际存了哪些值再对齐。另一个原因是日期字段类型不对TruncMonth要求字段是DateField或DateTimeField如果建表时用了CharField存日期聚合会失败或结果异常。6. 进阶技巧把静态图表改成可切换城市与年份的交互式看板6.1 从「一个图表一个接口」到「参数化接口」原始项目里每个图表基本是写死的城市固定或年份固定。要让它变成能切换的看板核心是把查询条件参数化。下面这个视图把城市、年份、指标三个维度都做成 URL 参数一个接口覆盖多种组合from django.db.models import Avg, Count from django.db.models.functions import TruncMonth from django.http import JsonResponse from .models import AirQuality VALID_METRICS {pm25, temperature, humidity, pressure, dew_point} def trend_api(request): city request.GET.get(city, 北京) year request.GET.get(year) metric request.GET.get(metric, pm25) # 白名单校验防止把非法字段名拼进查询 if metric not in VALID_METRICS: return JsonResponse({error: invalid metric}, status400) qs AirQuality.objects.filter(citycity) if year: qs qs.filter(date__yearyear) qs (qs.annotate(monthTruncMonth(date)) .values(month) .annotate(avgAvg(metric)) .order_by(month)) return JsonResponse({ xAxis: [i[month].strftime(%Y-%m) for i in qs], series: [round(i[avg], 2) for i in qs], meta: {city: city, year: year, metric: metric} })这里最关键的一行是VALID_METRICS白名单。Avg(metric)里的metric如果直接来自用户输入理论上存在字段注入风险虽然 Django ORM 会做一定转义但白名单校验是更稳妥的习惯。date__yearyear是 Django 的字段查找语法会自动翻译成 SQL 的EXTRACT(YEAR FROM date)不用手写。6.2 前端联动下拉框触发重新请求页面上放两个select一个选城市一个选年份change事件里重新 fetch 并setOption。注意 ECharts 重复setOption时默认是合并模式如果新旧数据长度不一致旧数据可能残留。稳妥做法是传{ notMerge: true }function loadTrend() { var city document.getElementById(citySelect).value; var year document.getElementById(yearSelect).value; fetch(/api/trend/?city${city}year${year}metricpm25) .then(res res.json()) .then(data { chart.setOption({ xAxis: { data: data.xAxis }, series: [{ data: data.series }] }, { notMerge: true }); }); }notMerge: true会清掉旧配置再应用新的避免数据错位。代价是每次都要传完整配置不能只传变化的部分。对于课设级别的数据量这点开销可以忽略。6.3 用 admin 后台做数据校验的快捷入口Django 自带的 admin 是个被低估的工具。在app01/admin.py里注册模型后/admin/就能直接浏览和筛选数据不用写任何前端代码。调试阶段用它来确认「某个城市某年的数据到底有没有」比写 SQL 快得多from django.contrib import admin from .models import AirQuality admin.register(AirQuality) class AirQualityAdmin(admin.ModelAdmin): list_display (city, date, pm25, temperature, humidity) list_filter (city, date) search_fields (city,)list_filter会在右侧生成筛选面板search_fields提供搜索框。这套配置五分钟能写完但排查数据问题时能省下大量时间。我一般会在导入数据后先过一遍 admin确认各城市、各年份的记录数分布合理再去看图表。6.4 一个容易被忽略的细节时区与日期截断settings.py里USE_TZ默认为TrueDjango 会把DateTimeField按 UTC 存储。如果 CSV 里的日期是本地时间导入后TruncMonth可能把月初几小时的数据算到上个月。课设场景下最简单的处理是把USE_TZ设为False让 Django 按裸日期处理避免时区换算带来的偏移。这个坑不常遇到但一旦遇到图表数据会「差一点」很难查。从那以后我每次拿到带日期的数据集都强制先确认三件事编码、时区、字段类型。这三样对齐了后面的可视化和分析才谈得上准确。希望帮到你。本文还有配套的精品资源点击获取
返回列表