ARTICLE DETAIL

资讯详情

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

Python+Django城市PM2.5空气质量数据可视化分析完整指南

Python+Django城市PM2.5空气质量数据可视化分析完整指南 简介面向高校毕业设计场景的PythonDjango城市PM2.5空气质量数据可视化分析项目源码适合计算机、数据分析、环境科学等专业学生用于课题设计、课设参考与功能扩展。压缩包共64个文件约12.38MB包含17个Python源码文件、24个CSV数据文件、10个pyc编译文件、4个XML配置、3个HTML页面以及SQL数据库文件、TXT说明和README文档覆盖Django项目配置、数据导入、可视化页面与数据库结构。该项目已有406人浏览学习能够帮助快速理解Django框架下的数据可视化开发流程。项目中内置北京、上海、广州、成都、沈阳五城市六年PM2.5数据并整理出时间序列、不同季度、一天中不同时段等分析维度以及PM2.5与露点、风向、大气压、温度、相对湿度之间关系的统计表同时提供数据导入脚本和MySQL数据库文件整体代码结构清晰便于直接运行、二次开发或作为毕业设计答辩演示素材。1. PythonDjango城市PM2.5空气质量数据可视化分析先弄清楚这套源码能干什么标题里这串名字说白了就是一段用 Python 后端框架 Django 写好的完整网站——上面有登录、有后台、有数据库前端页面用 ECharts 把城市 PM2.5 浓度、AQI 指数、主要污染物画成折线图、柱状图和地图分布。你下载到的那个 zip 里通常装着 Django 工程源码和一个现成的数据库文件SQLite 最常见解压后理论上敲一条命令就能跑起来然后从浏览器里看到图表。它解决的核心问题就一句话把散落各处的空气质量数据做成一套能查询、能看趋势、能打印报告的 Web 系统。这个方向适合两类人一是拿它当毕业设计交付的学生二是刚学完 Django 想找一个“完整链路”练手的初级开发。但说实话我帮人调过太多这种压缩包了真正能一次跑通的不到一半——问题大多不在代码本身而在环境、数据库路径和静态文件这三处这篇笔记就按我的落地顺序从架构讲到部署。2. 架构与数据流从数据采集到图表渲染Django 在这套系统里管了哪几件事2.1 Django MTV 模式与工程模块划分Django 的 MTVModel-Template-View和传统 MVC 最直观的区别是它把“路由控制器”揉进了 View 层而 Template 只负责渲染展示。你在压缩包里会看到典型的工程骨架django_pm25/ ├── manage.py ├── django_pm25/ # 工程配置目录 │ ├── settings.py │ ├── urls.py │ ├── wsgi.py ├── app_air/ # 空气质量业务 App │ ├── models.py │ ├── views.py │ ├── urls.py │ ├── templates/app_air/ │ └── static/app_air/ └── db.sqlite3一个正常的做法是把“城市信息”和“空气质量记录”放在同一个业务 App 里比如app_air这样 models 之间的外键关系写在同一个文件里后续做 ORM 查询时不用跨 App。settings.py 里要确认INSTALLED_APPS已经注册这个 App否则迁移时会出现no such table的报错。这套划分对应到 MTV 上是这样的models.py 负责定义表结构对应 Modeltemplates 目录下放 HTML 模板对应 Templateviews.py 里处理请求、查数据库、给模板传数据对应 View而 urls.py 负责把浏览器地址映射到对应的 view 类或函数。很多新手在这里犯的第一个错是直接在工程级django_pm25/urls.py里写业务路由结果把业务 App 写成了一坨不可复用的代码。我一般会把属于业务的路由全部下沉到app_air/urls.py工程级只是include一下这样后期加新页面只需要在业务 App 里改。2.2 数据流设计采集层、存储层、接口层、展示层这一整套系统我最看重的是数据流因为它直接决定了你后面要写哪些模块。以最常见的毕业设计功能清单来拆这四层跑下来模型是这样公开数据源爬虫/CSV → SQLite/MySQL 数据库 → Django ORM 查询 → JSON API 接口 → ECharts 渲染页面采集层两种做法。一种是写爬虫用 requests 从公开空气质量站点抓当天数据另一种是拿现成的 CSV 数据文件做导入。毕设阶段建议两个都保留CSV 导入保证演示现场断网也能跑爬虫脚本作为“数据更新”功能的加分项。存储层Django 默认的 SQLite 足够支撑几千条记录答辩演示完全够用。如果你的数据库文件名在 settings.py 中被写死成了D:/xxx/db.sqlite3这种绝对路径那换电脑基本必炸后面避坑章节我会专门讲。接口层Django 视图函数从数据库取数序列化成 JSON 返回。这里不涉及 DRFDjango REST Framework也可以毕业设计手工用JsonResponse反而更容易讲清楚。展示层前端模板用 ECharts 的ajax拉取接口数据渲染折线图、柱状图和地图。要注意的是ECharts 官方示例经常直接写成fetch请求而 Django 模板里有csrf_token的语法和 ECharts 本身没有冲突但如果你在模板里插原生script标签注意保持 JavaScript 的变量名和 Python 传过来的 JSON 字段名严格一致。2.3 环境准备Python 版本、虚拟环境与依赖安装先说版本Django 3.2 LTS 及以上都可以跑这套工程Python 用 3.8 到 3.11 之间最稳。别一上来就装 Python 3.13 配最新 Django 5.x某些第三方库比如 MySQL 驱动在最新版本上可能出现预编译问题没必要在这个环节消耗精力。推荐在项目根目录建虚拟环境Windows 下的命令如下python -m venv venv venv\Scripts\activate pip install -r requirements.txtrequirements.txt我一般手动维护最少包含这几项Django3.2,5.0 requests beautifulsoup4 pandas其中 pandas 只在导入 CSV 时用到如果你用纯 Python 标准库csv模块处理可以直接删掉这一行少一个重量级依赖虚拟环境体积会小很多。为什么要用虚拟环境因为 DEBUG 模式下很多包冲突不显山露水等项目部署到服务器pip 装了一堆全局包版本错乱后你根本不知道是哪个库在打架虚拟环境至少把黑匣子缩小到一个 requirements.txt 里。装完依赖后先跑python manage.py migrate把 Django 内置表建出来再跑python manage.py runserver验证工程能起来。注意如果此时直接访问首页报 404不要慌这代表数据库连接没问题只是还没配置路由。3. 数据库建模与数据灌入models 设计、CSV 导入与爬虫两条路径3.1 用 models 建两张核心表城市表与空气质量日报表城市 PM2.5 分析的核心是两张表城市基础信息表和每日空气质量记录表。前者撑起地图分布和城市下拉框后者作为事实表存数值。我在 models.py 里的设计是这样from django.db import models class City(models.Model): name models.CharField(max_length32, uniqueTrue, verbose_name城市名称) province models.CharField(max_length32, blankTrue, verbose_name省份) lon models.FloatField(verbose_name经度) lat models.FloatField(verbose_name纬度) class Meta: ordering [name] def __str__(self): return self.name class AirQuality(models.Model): city models.ForeignKey(City, on_deletemodels.CASCADE, verbose_name所属城市) date models.DateField(verbose_name监测日期) pm25 models.FloatField(verbose_namePM2.5 浓度(μg/m³)) pm10 models.FloatField(verbose_namePM10 浓度(μg/m³)) so2 models.FloatField(verbose_nameSO2 浓度(μg/m³)) no2 models.FloatField(verbose_nameNO2 浓度(μg/m³)) co models.FloatField(verbose_nameCO 浓度(mg/m³)) o3 models.FloatField(verbose_nameO3 浓度(μg/m³)) aqi models.IntegerField(verbose_nameAQI 指数) level models.CharField(max_length16, verbose_name空气质量等级) primary_pollutant models.CharField(max_length32, blankTrue, verbose_name首要污染物) class Meta: unique_together (city, date) ordering [date] def __str__(self): return f{self.city.name} {self.date}设计说明unique_together是这套建模里最关键的约束。同一城市同一天只能有一条记录这条约束既阻止了爬虫重复写入也让后面的update_or_create有了判断依据如果去掉它数据导入只会越导越脏。外键用on_deletemodels.CASCADE城市一旦删除它下面的空气质量记录全部清掉。这对毕设没什么问题但如果你后面想保留历史数据做审计就得改成SET_NULL搭配一个默认的“未知城市”记录。字段类型上pm25、pm10这些浓度用FloatFieldAQI 必须是IntegerField因为 AQI 的计算结果只取整数部分用浮点反而会在前端显示一堆小数点。level和primary_pollutant在爬虫里很好拿但如果你的数据源只有六项污染物没有等级和首要污染物就要在导入时自己推算这个逻辑我放在 3.3 节讲。生成迁移的命令是python manage.py makemigrations app_air python manage.py migrate说到风控提示数据源上不要注册登录、抓取接口不弹性限频学会 sleep。3.2 从 CSV 批量灌数据一个独立的数据导入脚本毕设项目的数据通常是一张 CSV列名一般是城市、日期、PM2.5、PM10、SO2、NO2、CO、O3、AQI、等级。我们要写一个 Django 原生 shell 里的脚本把它灌进数据库。Django 提供了一条最简单的路径在项目根目录写import_csv.py然后在manage.py shell里执行。import csv import django import os os.environ.setdefault(DJANGO_SETTINGS_MODULE, django_pm25.settings) django.setup() from app_air.models import City, AirQuality def run(csv_path): with open(csv_path, r, encodingutf-8) as f: reader csv.DictReader(f) count 0 for row in reader: city_name row[城市].strip() city, _ City.objects.get_or_create( namecity_name, defaults{province: row.get(省份, ), lon: 0, lat: 0} ) AirQuality.objects.update_or_create( citycity, daterow[日期], defaults{ pm25: float(row[PM2.5]), pm10: float(row[PM10]), so2: float(row[SO2]), no2: float(row[NO2]), co: float(row[CO]), o3: float(row[O3]), aqi: int(float(row[AQI])), level: row[等级], primary_pollutant: row.get(首要污染物, ), } ) count 1 print(f成功写入 {count} 条记录) if __name__ __main__: run(data/city_air_2024.csv)逻辑说明脚本开头四行os.environ.setdefault和django.setup()是让 Django ORM 能在独立脚本里运行的固定套路少了它们你会得到django.core.exceptions.ImproperlyConfigured报错。get_or_create负责城市表去重update_or_create负责按(city, date)去重这就是上面unique_together约束的代码侧呼应。第二次导入同一份 CSV 时数据会更新而不是插入重复行这也是血泪经验——很多同学的数据库跑了几次爬虫后多出几百条重复记录图表的趋势直接变成锯齿。城市经纬度在 CSV 里可能没有这里先用 0 占位后续在地图页面显示完后再补真实经纬度。这不是偷懒而是避免 CSV 数据质量太差导致导入中断。执行方式python manage.py shell import_csv.py如果你希望把脚本做成一个可以反复调用的模块可以在app_air下新建management/commands/目录写一个import_csv.py的自定义命令这样就能用python manage.py import_csv data.csv执行答辩演示时也更像样。3.3 爬虫增量更新requests BeautifulSoup 抓公开页面如果说 CSV 导入是“打底数据”爬虫脚本就是“数据保鲜”。常见的做法是抓取公开数据平台页面解析表格后存入模型。为了不影响演示一定要让爬虫脚本断网也能走也就是只作为增量更新工具。import time import requests from bs4 import BeautifulSoup def fetch_realtime(city_list): headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} for city in city_list: url fhttps://example-air-source.com/city?id{city} try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) rows soup.select(table tbody tr) for row in rows[:10]: tds [td.text.strip() for td in row.find_all(td)] # 假设列顺序: 日期 PM2.5 PM10 SO2 NO2 CO O3 AQI 等级 save_air_quality(city, tds) except Exception as e: print(f{city} 抓取失败: {e}) time.sleep(2) def save_air_quality(city_name, tds): # 省略 models 写入逻辑与 3.2 的 update_or_create 一致 pass参数与容错说明User-Agent必须伪装成浏览器很多站点对默认的python-requests是直接拒绝的这是最基础的“反爬”应对。别问我怎么知道第一次写爬虫时没有 UA死活拿不到数据加完 UA 立刻通了。time.sleep(2)是给目标网站的礼貌性限速。两个城市之间必须等待否则几十个城市的请求会在几分钟内把你的 IP 打进封禁名单报错 403 或 503 时先检查这里。抓到的数据要二次校验——tds的长度是否符合预期日期格式是否是YYYY-MM-DDPM2.5 数值是否大于等于 0。脏数据入库的后果比没有数据严重得多图表上一个 -999 的异常值会把整条折线的量程拉飞。更稳妥的增强方案是加一个 3 次重试机制超过 3 次直接放弃这个城市继续下一个。3.4 完整链路跑了导入脚本后用 Django ORM 验证数据是否入库不管数据是从 CSV 还是爬虫进来灌完数据后都要验一验。这里给你一个最直观的查询验证也是面试官最爱问的 ORM 操作from app_air.models import AirQuality from django.db.models import Avg result AirQuality.objects.values(date).annotate(avg_pm25Avg(pm25)) print(list(result[:10]))这个查询的含义是按日期分组计算所有城市当天的 PM2.5 平均值取前 10 天。values(date)生成 GROUP BY 子句annotate(avg_pm25Avg(pm25))生成 AVG 聚合字段得到的result是一个包含字典的 QuerySet。这是做全城趋势图最底层的查询模型后面的可视化接口就是在这个逻辑上加城市过滤和日期区间。如果查询结果为空优先检查AirQuality.objects.count()如果为 0 说明数据导入环节出了问题而不是查询逻辑有问题。把验证写在导入脚本的关键节点上能让你的数据链路上线时少踩一半的坑。4. 可视化与 ECharts后端接口返回 JSON 的姿势与前端渲染的三个坑4.1 用 JsonResponse 写查询接口带日期区间和城市参数可视化页面的数据来源是一个 JSON 接口我们用 Django 原生视图实现核心是查询参数校验和 JsonResponse 使用。下面代码在你下载的源码里应该能找到类似版本如果没有照着写就行。import json from datetime import datetime from django.http import JsonResponse from django.views.decorators.http import require_GET from app_air.models import City, AirQuality require_GET def pollution_api(request): city_name request.GET.get(city, 北京) start_date request.GET.get(start) end_date request.GET.get(end) try: city City.objects.get(namecity_name) except City.DoesNotExist: return JsonResponse({status: 1, msg: 城市不存在}, status404) qs AirQuality.objects.filter(citycity) if start_date: qs qs.filter(date__gtestart_date) if end_date: qs qs.filter(date__lteend_date) qs qs.order_by(date) data { date: [], pm25: [], aqi: [], } for item in qs: data[date].append(item.date.strftime(%Y-%m-%d)) data[pm25].append(item.pm25) data[aqi].append(item.aqi) return JsonResponse({status: 0, data: data})逻辑说明require_GET装饰器限定这个视图只接受 GET 请求前端用 ECharts 的数据请求是标准 GET不需要 POST。date__gte和date__lte是 Django 的日期字段查询语法对应 SQL 中的和支持直接用字符串如2024-01-01Django 会自动解析成日期对象。strftime(%Y-%m-%d)把日期格式化成字符串这样前端 JavaScript 可以直接显示不用再处理 Python 的 datetime 对象序列化问题。这里没有加缓存。如果是展示用的热点页面建议用django.core.cache的cache.set/cache.get把接口返回结果存 5 分钟减轻数据库压力。这个可以在加分项里写但别在最基础的版本里用否则答辩时讲不清楚就没必要。4.2 模板里的 ECharts 渲染折线图加城市下拉框前端页面用fetch或 jQuery 的ajax请求上面的接口把返回数据塞进 ECharts 的 option 里。ECharts 是可视化的事实标准你不需要自己画 canvas核心工作是维护好 option 对象。{% load static %} !DOCTYPE html html langzh-CN head meta charsetUTF-8 titlePM2.5 趋势分析/title script src{% static app_air/echarts.min.js %}/script /head body select idcitySelect option value北京北京/option option value上海上海/option option value广州广州/option /select div idchart stylewidth: 900px; height: 500px;/div script let chart echarts.init(document.getElementById(chart)); function loadData(city) { fetch(/api/pollution/?city${encodeURIComponent(city)}start2024-01-01end2024-12-31) .then(res res.json()) .then(json { if (json.status ! 0) return; chart.setOption({ title: { text: ${city} PM2.5 日均浓度 }, tooltip: { trigger: axis }, xAxis: { type: category, data: json.data.date }, yAxis: { type: value, name: μg/m³ }, series: [{ name: PM2.5, type: line, data: json.data.pm25 }] }); }); } document.getElementById(citySelect).addEventListener(change, function() { loadData(this.value); }); loadData(北京); /script /body /html这里的核心坑有三个第一echarts.min.js必须放在 static 目录里并用{% static %}标签引用而不是直接写/static/app_air/echarts.min.js。Django 的静态文件查找机制在 DEBUG 模式下会按STATICFILES_DIRS和每个 App 的static/目录展开写死相对路径在路径变化时会直接 404。第二fetch中拼 URL 时要用encodeURIComponent(city)否则城市名里的中文会变成不可读的编码特别是当你的城市名含“重庆”这样的多音字极容易在 URL 解析时出隐性 bug。第三chart.setOption在切换城市时要先chart.clear()否则旧数据残留在画布上。ECharts 的 setOption 默认是合并模式不 clear 的话新旧 series 数据会重叠折线会变成一坨毛线。这是可视化最常见的翻车现场。4.3 视图与模板函数城市列表注入页面的方法上面的select下拉框是写死的三个城市。更好的做法是从数据库读出所有城市动态填充。Django 模板里可以用for循环视图代码from django.shortcuts import render def chart_view(request): cities City.objects.values(id, name) active_city request.GET.get(city, 北京) return render(request, app_air/chart.html, { cities: cities, active_city: active_city, })模板中select idcitySelect {% for c in cities %} option value{{ c.name }} {% if c.name active_city %}selected{% endif %}{{ c.name }}/option {% endfor %} /select这里的values(id, name)比City.objects.all()更轻只查询需要的字段模板里也不需要访问c.id之外的属性。选中态用{% if %}条件渲染切页时能保留选择状态体验提升一个档次而且代码量不增加。urls.py 里的配置from django.urls import path from app_air import views urlpatterns [ path(chart/, views.chart_view, namechart_view), path(api/pollution/, views.pollution_api, namepollution_api), ]4.4 页面里加一张城市地图分布图适可而止的加分项如果只做折线图和柱状图这个项目的完成度已经能支撑毕业设计答辩。加分项是加一张中国地图显示各省 PM2.5 浓度颜色深浅代表污染程度。ECharts 5 已经内置了地图组件的registerMap但地图 JSON 数据默认放在 CDN答辩现场断网就白屏。常用的做法是下载一份中国地图 GeoJSON 文件存到static/app_air/china.json然后在代码里fetch(/static/app_air/china.json) .then(res res.json()) .then(geoJson { echarts.registerMap(china, geoJson); chart.setOption({ series: [{ type: map, map: china }] }); });地图 JSON 文件可以是 100% 离线的但要注意它的坐标系版本和省份名称必须是“北京”“上海”这种简写而不是“北京市”。如果注册地图报错 “GeoLite2 database not found” 或 “Invalid geoJson”先检查 JSON 里省份名称是否与你的城市表外键对齐。我的建议是地图作为“最后一页”其他数据图表全部正常后再加不要一上来就啃坐标系。5. 避坑毕业设计常踩的 8 个经典疑难杂症5.1 现象解压源码后 runserver 起不来提示 “no such table: app_air_airquality”原因这是最常见的新手问题。源码里附带的是数据库文件但没有执行迁移Django 工程不知道表结构。数据库文件为空或不存在Migrate可以建表但数据不会自动生成。解决先跑python manage.py migrate创建表结构再把压缩包里的数据导入脚本执行一遍。如果数据库文件已经在压缩包里确认 settings.py 的DATABASES配置指向的是BASE_DIR / db.sqlite3而不是某个写死的绝对路径DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } }若下载的源码里 database 目录下有.sqlite3文件直接改这个路径即可。这里要特别提一句如果压缩包里的数据库文件名和 settings 里不一致比如pm25_data.sqlite3和db.sqlite3你会看到一个奇怪的现象——程序不报错但页面上一直没有任何数据。先ls database/看文件名对不对。5.2 现象ECharts 图表一直空白控制台报 “geojson is null”原因地图注册失败最常见的原因是 GeoJSON 文件没有下载到本地。很多教程给出的是https://cdn.jsdelivr.net/...的在线地址学校网络环境和演示环境常常无法访问。解决把 GeoJSON 下载到static/app_air/china.json页面用相对路径引用同时确认STATIC_URL配置正确。一个降级方案是如果地图实在搞不定就只用折线图展示数据——毕业答辩并不强制要求地图模块把趋势分析做扎实比堆图表更重要。5.3 现象切换城市后图表两条折线重叠数值错乱原因EChartssetOption的合并机制导致旧数据没清掉。具体表现是第一次加载北京的正常切到上海后北京的数据还在图上。解决在chart.setOption前调用chart.clear()或者给 series 的name做整体覆盖。推荐 clear() 方案逻辑简单chart.clear(); chart.setOption({ /* 完整配置 */ });5.4 现象查询接口返回正常但页面请求显示 403原因Django 默认开启了 CSRF 中间件对 POST 请求会校验 token。虽然这个接口是 GET但如果你在某个页面里用了 POST 提交筛选条件就会触发 CSRF 验证。解决视图上加csrf_exempt装饰器不推荐或者在前端页面里加上 Django 模板提供的{% csrf_token %}并把 token 带给后端。对毕业设计来说简单方案是把所有查询都改成 GET避免 CSRF 的复杂性。5.5 现象数据库连接 MySQL 报错 “Unknown collation utf8mb4_0900_ai_ci”原因这是 MySQL 8.0 默认的排序规则而你的服务器或同学的 SQLite 不支持某些特性或 MySQL 5.7 不兼容。解决建库时手动指定CREATE DATABASE pm25_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;数据库连接配置里加上OPTIONS: {charset: utf8mb4, sql_mode: STRICT_TRANS_TABLES}这个问题在 Windows MySQL 5.7 的环境下玄学频发换成 utf8mb4_general_ci 之后通常就通了。5.6 现象SQLite 版本的日期筛选永远查不到数据原因SQLite 对日期比较是文本比较如果你的date字段里存的是2024-01-01这种标准格式不会有问题但如果存的是2024/01/01或者1/1/2024date__gte就会变成字典排序结果完全不符合预期。解决在导入数据时统一datetime.strptime(row[日期], %Y-%m-%d).date()格式化入库前把格式锁死别存字符串。另如果你的数据库迁移后又改过 models 字段建议先删掉数据库文件重新migrate。毕业设计不需要考虑数据积累删了重建成本最低。5.7 现象Windows 上python manage.py runserver启动成功但局域网访问不了原因Django 默认监听 127.0.0.1同事的手机和会议室电脑访问不到你的开发服务器。解决python manage.py runserver 0.0.0.0:8000同时确认 Windows 防火墙放行了 8000 端口这是答辩演示前必须提前测试的一步。5.8 现象页面样式全部丢失控制台一堆静态资源 404原因settings.py 里的DEBUG False时Django 默认不再提供静态文件服务。解决为演示页面临时把DEBUG改回True或者用whitenoise托管静态文件。更简单的命令行方案是在工程级 urls.py 里加一条from django.conf import settings from django.conf.urls.static import static urlpatterns static(settings.STATIC_URL, document_rootsettings.STATIC_ROOT)6. 从开发到可部署用 waitress 跑起来再给项目加一张“日报卡片”6.1 用 waitress 在 Windows 上替代 runserverrunserver 是开发服务器性能差、不支持并发答辩演示时如果同时刷新页面较多会出现明显的响应变慢。Windows 下最省事的部署方案是 waitress纯 Python、无需编译pip install waitress waitress-serve --listen0.0.0.0:8000 django_pm25.wsgi:application如果服务器是 Linux习惯上用 gunicornWindows 专属就选 waitress。启动后用http://127.0.0.1:8000验证然后该写关机脚本就写关机脚本演示前把这行命令放到启动 bat 文件里双击执行能减少很多现场尴尬。6.2 配一张 nginx 反向代理并托管 static 文件线上环境最常见的组合是 waitress 处理 Python 应用nginx 处理静态文件和反向代理。Django 这边先把静态文件收集到一个目录python manage.py collectstaticnginx 配置核心片段server { listen 80; server_name your_server_ip; location /static/ { alias /var/www/django_pm25/static/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }配置完记得nginx -t校验语法再nginx -s reload。需要注意的细节location /static/和alias的路径结尾都要有斜杠少一个斜杠就 404这是 nginx 配置里最经典的翻车点。6.3 增量价值给系统加一张每日空气质量等级统计卡片当你把链路跑通之后我想再分享一个能显著提升完成度的功能做法是写一个定时计算任务把最近一天各城市的空气质量等级统计成一个文本卡片落实到系统的首页或单独接口上。from django.db.models import Count from app_air.models import AirQuality def daily_summary(date): rows AirQuality.objects.filter(datedate).values(level).annotate( totalCount(level) ).order_by(-total) if not rows: return 今日暂无数据 summary [f{row[level]}: {row[total]}城 for row in rows] return 、.join(summary)这段代码按空气质量等级分组统计某天每个等级的城市数量返回一串比如“良: 12城、优: 8城、轻度污染: 3城”的文本。配合 Django 的crontab或者 Windows 计划任务每天跑一次就能把整个系统从“能看趋势”升级到“会自动汇报”这在答辩现场提出来是很加分的点。我的习惯是拿到任何一套新环境先手工跑一次python manage.py shell import_csv.py确认数据链路通再用测试用例保护update_or_create的幂等性最后才写定时任务。定时任务刚写完一两天不出问题不代表没问题跨月的日期边界最容错记得加try-except捕获并打印异常否则半夜定时任务崩了第二天早上你对着一个空数据库懵掉。说起来我最早自己写这套系统时也栽过不少跟头尤其 SQLite 文件被 IDE 锁死导致数据库操作直接报错那回折腾到凌晨两点才发现是 Pycharm 的数据库插件占用了文件。后来用回命令行和显式的数据库配置这些坑基本就绕开了。希望这篇笔记里的设计思路和排错经验能帮到你至少让你解压那个 zip 后不会再对着 runserver 的报错发呆。本文还有配套的精品资源点击获取
返回列表