资讯动态

Django实战:PM2.5空气质量数据可视化项目搭建与避坑指南

发布时间:2026/9/28 13:12:39 来源:尧图企业网站定制
简介这份资源是一份基于Python与Django框架的城市PM2.5空气质量数据可视化分析完整项目面向计算机相关专业学生的毕业设计需求。项目采用Django MTV架构包含前端页面、后端逻辑、数据脚本与MySQL数据库内置北京、上海、广州、成都、沈阳五座城市六年PM2.5数据。数据部分覆盖一年中各月变化、不同季度逐年数据、时间序列趋势以及与温度、湿度、露点、气压、风向等因素的关联关系可支撑多维度空气质量分析与页面可视化展示帮助学习者完整掌握从数据采集、清洗、入库到图表呈现的流程。压缩包共64个文件大小约12.38MB主要包含17个Python源码、24个CSV数据文件、3个HTML模板另有Django环境配置文件、SQL数据库脚本、requirements依赖清单和README说明目录层级清晰便于直接导入运行与二次扩展。资源目前已有406人学习下载适合课程设计、毕业设计或Django加可视化实战入门参考。1. 一份毕设源码包背后Django PM2.5 可视化的真实价值从网上下载的源码 zip解压即报错的比例远比你想象的高。这不是玩笑。标题里写了 Python、Django、PM2.5、可视化、数据库看起来是一条标准的“数据采集 Web 展示”链路但真正把整套项目跑通的人靠的不是运气而是把 Python 版本、Django 依赖、数据库连接、时区、字符集、前端图表接口这六件事一次配齐。这类项目在毕业设计里属于典型的数据可视化方向后端用 Django 框架对外提供页面和接口数据库存放城市空气质量监测数据前端用 ECharts 绘制趋势图。它适合正在准备毕设的本科生、第一次接触 Django 项目实战的新手以及想快速搭一个数据展示 Demo 的开发者。读完这篇文章你能判断这份源码值不值得用也能在半小时内把它跑起来。2. 技术架构拆解Django 在 PM2.5 可视化项目里到底承担了什么2.1 为什么毕设级别的数据可视化几乎都用 Django而不是 Flask看到标题里带 Django先别急着换框架。常见做法是淘宝上大量空气质量可视化毕设源码基于 Django原因不是 Django 比 Flask 更高级而是它自带三件套——ORM、Admin 后台、模板系统。对于 PM2.5 这种典型的数据管理项目你需要写站点的增删改查页面Django 的 ModelForm 加 Admin 十几行代码就出来了Flask 得自己拼 SQL 和表单时间和代码量都翻倍。我做技术选型时会拿一张表对比两个框架能力DjangoFlaskORM内置Model 层即数据库表结构无内置需接 SQLAlchemy后台管理自带 Admin可管理监测站点与数据记录需安装 flask-admin模板渲染自带模板语言适合服务端渲染图表页面用 Jinja2需手动配置学习曲线偏重但上手后有完整的项目骨架轻量适合单文件接口结论是如果项目只做接口不做管理界面Flask 更轻但毕设题目里带“数据可视化”三个字评委一定会看后台管理、数据录入、用户登录这些完整度指标Django 是更稳的选择。标题里既然写了 Django说明源码作者已经替你做了这个决定。2.2 数据模型设计从一张监测数据表拆出三张关联表跑通之前先看懂数据长什么样。空气质量可视化项目里最常见的数据模型不是一张大宽表而是拆成站点、指标、记录三张关联表。站点表存城市名称和坐标指标表存 PM2.5、PM10、AQI 这些检测项记录表才是真正每小时增长的事实数据。# core/models.py —— 毕设项目里最常见的三类模型 from django.db import models class Station(models.Model): name models.CharField(max_length50, uniqueTrue) # 城市或站点名 longitude models.FloatField(default0.0) # 经度用于地图展示 latitude models.FloatField(default0.0) # 纬度 class AirQuality(models.Model): station models.ForeignKey(Station, on_deletemodels.CASCADE) pm25 models.FloatField(nullTrue) # 注意允许空值 pm10 models.FloatField(nullTrue) aqi models.IntegerField(nullTrue) recorded_at models.DateTimeField(db_indexTrue) # 索引字段按时间查询这三个模型的字段设计有讲究。recorded_at必须加db_indexTrue因为图表页面的查询条件是WHERE recorded_at BETWEEN ? AND ?没有索引的话数据量过 5 万条接口就会明显变慢。pm25用FloatField而不是IntegerField因为不少公开数据源给的值带一位小数。主键不写也行的Django 会自动加自增 id。这张表结构足够支撑一个城市 200 多天的每小时数据大概 5000 条记录查起来毫秒级。2.3 可视化选型ECharts 是默认答案但别忽略 AntV标题里的“可视化”最终要落到前端图表库上。ECharts 在这类项目里的普及率高到离谱原因是折线图、柱状图、饼图、地图四类常用图形都只需要配置项不需要写 Canvas 代码。二手房项目用 ECharts 大屏、企业级数据可视化用 ECharts毕设源码里十有八九也是它。ECharts 有三个必调参数答辩论据充足xAxis和yAxis的type一个设category一个设value这是折线图的基础series.data直接对接接口返回的数组注意字段名要和后端一致tooltip.trigger设成axis才能在鼠标悬停时显示十字辅助线。如果源码里用的是 AntV也别慌AntV 的 G2Plot 同样支持折线图只是配置项风格偏函数式。对毕设来说选哪个不重要重要的是图表能跟着数据自动更新。相比之下Highcharts 在这个场景里的存在感已经越来越弱了除非源码里已经写好了否则不建议新项目选它。3. 数据通道从采集、清洗到入库PM2.5 数据的完整链路3.1 数据采集requests 抓公开接口 vs 直接调数据 API打开源码包后第一件事是看数据从哪来。常见做法有三类爬虫抓取公开网页、调用免费天气数据 API、直接导入 CSV 历史数据。多数毕设源码选第一种因为它能体现爬虫能力是答辩加分项。# collector/fetch.py —— 采集某个公开天气站点的 PM2.5 数据 import requests from bs4 import BeautifulSoup def fetch_pm25(city: str) - float: url fhttps://example.com/api/air?city{city} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://example.com/ } resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) value soup.select_one(.pm25-value) # 页面结构会变这条选择器要自己确认 return float(value.text.strip())这段代码里的关键点是headers。很多新手爬虫翻车就是没带 User-Agent被对方网站直接拒绝响应。timeout10不能省否则网络波动时脚本会卡死。select_one的选择器取决于目标页面源码包里大概率已经写好了。如果你重新写爬虫先用浏览器开发者工具看数据在哪个标签里再改选择器最忌讳盲猜。如果你不想碰爬虫更省事的方案是调用和风天气这类开放 API注册就有免费额度返回 JSON 直接解析。两种方式对项目的影响不大因为最终落库的数据结构是一样的。区别在于爬虫代码更“好看”但每周都可能因为页面改版而失效。3.2 清洗入库时间对齐、缺失值处理与批量写入采集到的原始数据不能直接入库至少要做两步清洗。第一步是时间格式统一有的接口返回 “2024-05-01 08:00”有的是带时区的时间戳全都转成 Django 的DateTimeField能认的格式。第二步是缺失值处理PM2.5 传感器故障时不返回值None可以直接存库不需要删除记录图表里会形成断点语义上是真实的。# collector/load.py —— 批量写入清洗后的数据 from datetime import datetime from core.models import AirQuality, Station def save_records(rows: list[dict]) - int: station Station.objects.get(namerows[0][city]) # 先拿外键 objs [ AirQuality( stationstation, pm25row[pm25], pm10row[pm10], aqirow[aqi], recorded_atdatetime.fromisoformat(row[time]), ) for row in rows ] return AirQuality.objects.bulk_create(objs)这里用bulk_create批量写入比循环里save()快一个数量级。注意bulk_create有两个边界触发不了模型的save()方法所以如果你在save()里改过数据批量写入会跳过另外它不会自动填充外键必须先把station对象取出来。还有一个隐蔽的坑bulk_create对数据量大时容易撞唯一约束如果你在模型上加了unique_together去重重复数据会直接抛异常需要在入库前做一次去重判断。3.3 把数据库变成接口视图、序列化与 JsonResponse 的取舍数据进了库前端怎么拿两种风格在毕设源码里都常见一种是 Django 模板在服务端渲染数据另一种是前端页面单独通过接口拿 JSON。新一点的源码几乎都用第二种因为 ECharts 的setOption方法直接吃 JSON不用拼模板字符串。# core/views.py —— 返回最近 24 小时 PM2.5 数据的接口 from django.http import JsonResponse from core.models import AirQuality def recent_pm25(request, city: str): rows list( AirQuality.objects .filter(station__namecity) .order_by(-recorded_at)[:24] # 取最新 24 条 ) rows.reverse() # 转回正序给图表用 data [ {time: row.recorded_at.strftime(%H:%M), pm25: row.pm25} for row in rows ] return JsonResponse( {city: city, data: data}, json_dumps_params{ensure_ascii: False} # 关键参数防中文转义 )这段代码有三个细节值得讲。第一order_by(-recorded_at)[:24]先倒序切片再反转这是取最新 N 条的标准写法直接正序切片会拿到最旧的数据。第二station__name是跨表查询Django ORM 的双下划线语法新手容易漏写导致 FieldError。第三ensure_asciiFalse是血泪经验来的——不开它接口返回的城市名会变成\u5317\u4eac前端能正常解析但你在浏览器看接口时完全无法核对排查问题时会多绕十分钟弯路。到这里数据链路已经从爬虫走到了接口。接下来要做的是验证这套链路在自己的机器上能不能整个转起来。4. 环境搭建与跑通拿到源码后按什么顺序起步4.1 解压后先读这三个文件再决定用哪个 Python 版本下载下来的 zip 不要急着双击解压先看压缩包内部结构。最常见的翻车点是源码包解压后出现两层嵌套目录xxx/xxx/manage.py路径不对导致 Django 项目认不到配置。直接解压后第一件事是找这三个文件requirements.txt管 Python 依赖版本manage.py是项目的入口settings.py决定数据库类型。你选择的 Python 版本必须和requirements.txt里锁定的 Django 版本兼容。# 查看源码包内 Django 版本约束 cat requirements.txt # 常见输出: Django2.2.28 或 Django3.2.25 或 Django4.2.7 # 如果是 Django 2.xPython 3.8/3.9 最稳4.x 建议 Python 3.10 以上新手容易在这翻车。Django 2.2 在 Python 3.10 上会报AttributeErrorDjango 4.1 在 Python 3.7 上直接装不上。我的建议是先看requirements.txt里 Django 版本号再按对应关系创建虚拟环境。如果源码里没有requirements.txt就用pip freeze生成一份但要在干净环境里生成别把全局装的包带进去。4.2 Python 环境与 Django 项目创建一次说清 startproject 和 startapp 的对应环境准备好后用虚拟环境隔离依赖。Windows 和 Linux 下的命令略有差别但流程一致。先在项目根目录创建虚拟环境激活后再装依赖。# 在源码包根目录执行 python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple这里解释两个概念因为很多人会在答辩时被问到。django-admin startproject生成的是整个项目的容器包含settings.py、urls.py和根manage.pypython manage.py startapp生成的是功能模块比如core、monitor、visual。两者是包含关系不深究但只有在同一级目录下settings.py里的INSTALLED_APPS才能注册到子应用。源码包里如果已经分好了apps目录或者平级的多个模块说明作者已经做了这一步。如果是源码里已经带了全部代码不需要重新创建项目直接从 4.1 的pip install之后跳到 4.3。如果你是从这个标题出发打算自己写一遍先把项目和应用建好再来回填core/models.py那段代码。4.3 数据库初始化MySQL/SQLite 切换与 migrate 顺序跑通前的最后一关是数据库。源码包的默认配置可能是 SQLite也可能是 MySQL。打开settings.py的DATABASES配置看一眼就能判断。SQLite 不需要任何额外服务MySQL 需要你先在数据库里建一个空库再把连接信息填进配置。# settings.py —— 两种常见的 DATABASES 配置 # 如果源码包用的是 SQLite这段就不用改 DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } } # 如果源码包用的是 MySQL按下面这段改并先在 MySQL 里建好数据库 DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: air_quality, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, # 关键参数不然后续中文数据全是问号 OPTIONS: {charset: utf8mb4}, } }MySQL 配置里的charset: utf8mb4是让中文不乱码的前提很多毕设源码的数据库连接串里并没有这个参数导致从后台录入中文站点名后变成问号。改了配置之后按顺序执行两条命令初始化数据库。python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000makemigrations根据模型生成迁移文件migrate把迁移应用到数据库。顺序不能反否则建表会失败。注意如果源码包里自带一个db.sqlite3文件直接运行runserver就能看到数据不需要重新 migrate如果改用了 MySQL就要先跑迁移建表再通过写好的init_data.py或爬虫脚本导入数据。访问http://127.0.0.1:8000/admin登录后台能看到站点和数据记录表说明整套环境已经通了。5. 避坑跑通 PM2.5 可视化项目最常见的五个翻车点5.1 Django 版本与 Python 版本不匹配migrate 直接报错现象python manage.py migrate执行到一半抛出django.core.exceptions.ImproperlyConfigured或AttributeError: module django.db.models has no attribute JSONField。原因源码包锁定的 Django 版本与你当前 Python 环境不兼容或者 Python 版本太高导致第三方依赖编译失败。Django 2.2 不支持 Python 3.10 以上而 Django 4.x 在 Python 3.7 以下无法运行。解决先用python --version确认版本再对照requirements.txt里的约束选择解释器。没有约束时直接建一个新的 Python 3.9 或 3.10 虚拟环境重新安装依赖。不要强行在当前环境里降级会污染全局包。5.2 MySQL 字符集不对中文全部变成问号现象后台新增一个站点名“北京”保存后再看变成“”。前端地图上的城市名也全是问号。原因数据库连接时没有指定字符集MySQL 默认的拉丁字符集存不了中文。另一个原因是在 MySQL 里建库时库的默认字符集就是 latin1。解决两步走。建库时执行CREATE DATABASE air_quality DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci同时把settings.py里加上OPTIONS: {charset: utf8mb4}。改完重启 Django 服务重新跑迁移然后清掉旧表数据再试。5.3 时间差 8 小时图表横轴对不上现象数据库里recorded_at存的明明是 08:00页面上图表显示 16:00每天的数据错位 8 小时。原因settings.py的TIME_ZONE和USE_TZ配置与本地时区不一致。Django 默认TIME_ZONE UTC时存进数据库的时间是 UTC 时间前端显示没转回东八区就会差 8 小时。解决TIME_ZONE Asia/ShanghaiUSE_TZ False改完后重新采集或重新导入数据。注意只改TIME_ZONE不关USE_TZDjango 仍会做 UTC 换算。这个坑排查起来最耗时间数据明明对就是图不对。5.4 前端 ECharts 空白接口却能看到 JSON现象浏览器打开可视化页面图表区域空白或者报xAxis data is empty但直接访问接口 URLJSON 数据是正常的。原因代码里的fetch请求地址写错了或data字段结构与前端解析逻辑不一致。比如接口返回{city: 北京, data: []}前端代码里取的是res.list永远取不到。解决打开浏览器开发者工具的 Network 面板刷新页面看请求有没有发出返回的状态码是多少JSON 结构长什么样再对照前端代码里的res.data.data取数路径。还有一个常见原因是静态文件没加载ECharts 库在static目录下检查settings.py的STATICFILES_DIRS配了没有。5.5 采集脚本跑几天后重复数据堆积图出现锯齿现象定时采集跑了一周数据库里同一时刻出现多条记录图表折线变成密集毛刺查询接口越来越慢。原因采集脚本没有做幂等去重。每次执行都往表里插入没有检查同样站点、同样时间的数据是否已存在。解决在模型上加唯一约束再把插入逻辑改成get_or_create。# 在模型上声明唯一键迁移一次 class Meta: unique_together (station, recorded_at) # 采集入库时改用 get_or_create替代 bulk_create AirQuality.objects.get_or_create( stationstation, recorded_atrecorded_at, defaults{pm25: row[pm25], pm10: row[pm10], aqi: row[aqi]}, )加了唯一约束后重复执行同一个时间点的采集不会新增记录只有值发生变化才会更新。注意get_or_create的第三个参数是defaults不能用pm25...直接放进去否则去重逻辑失效。6. 进阶与验证把单机演示做成可上线的空气质量监测系统验证指标系统是否可信我的习惯是写好一个巡检 SQL 每周跑一遍看数据连续性。SELECT DATE(recorded_at) AS d, COUNT(*) AS records, ROUND(AVG(pm25), 1) AS avg_pm25 FROM core_airquality GROUP BY DATE(recorded_at) ORDER BY d DESC LIMIT 7;表名取决于你建的 app 名core_airquality是 Django 自动拼出来的。连续七天都有记录每天条数和预期一致说明采集、清洗、入库整个链路是健康的。那前端图表要不要开 WebSocket 推送我的结论是毕设和中小型系统用现有的轮询就够了。前端每 60 秒用setInterval请求一次接口流量开销完全可接受。WebSocket 的优势在秒级推送但引入 Django Channels 之后部署方式从runserver变成 ASGI 服务复杂度上升一个量级。别为了热词里的“websocket 推送”去做不值得的性能升级。真正值得升级的是数据量。如果系统要接入全省多个城市每天新增几条记录按小时粒度存五年也才 4 万条左右SQLite 都扛得住。想做成大屏可视化直接用 ECharts 的dataZoom组件做时间轴缩放配合Media Query做响应式布局企业级数据可视化的常见套路也就这些。最后说一个我自己的习惯把接口返回的字段名写进一个常量字典前端模板统一引用同一份字段映射比如FIELD_MAP {pm25: PM2.5, pm10: PM10, aqi: AQI}。这样后面加指标、改单位只需要改一处不至于改一个字段把图表、表格、导出三处全翻一遍。这套项目跑通后最大的收获不是代码是这条数据链路上每个环节的容错点在哪知道哪里会断、为什么断、怎么接上。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑