又到了一年一度计算机专业学生跟毕业设计“死磕”的季节。说实话,我每年都能收到一堆类似的私信:“博主,毕设做什么题好?”“Django和爬虫怎么结合?”“机器学习到底要学到什么程度才够用?”与其一个个回,不如干脆写一篇完整的技术拆解。今天要说的是我今年在“智能新能源汽车数据洞察与可视化系统”这个题目上的完整设计思路和落地过程——一个把Django框架、Scrapy爬虫、可视化、数据分析、大数据处理、机器学习和大模型能力全部串起来的毕业设计级项目。这篇文章不仅讲“怎么搭出来”,更会讲“为什么这样做”,以及我把整个项目从0写到1的过程中踩过的坑和填平的坎。不管你是正在构思选题、还是代码卡在半路,这篇文章都能给你一张可以直接照着走的路线图。
1. 选题逻辑拆解:一个毕设怎么兼顾“工作量”和“含金量”
1.1 为什么是新能源汽车数据,而不是传统的电商、电影数据
很多同学的第一个问题是:数据类毕设,选什么题材最稳?我的回答一直是:不要选“满大街都是”的题材,也不要选“完全没数据”的题材。 电商评论、豆瓣电影这类数据集,从大一到毕业设计,被无数人做过,评委老师基本一眼就能看出来你的数据是网上扒的套路集,同质化太严重。反过来,如果你选一个特别冷门的领域,比如某类工业设备振动信号,虽然新鲜,但公开数据源少,爬虫阶段就直接劝退。
新能源汽车是这两年的天然“流量池”。首先,它有大量公开、结构相对规整的数据源:销量榜单、车型参数、用户评价、充电桩分布、免购置税目录,随便一列就是几个方向。其次,它的数据维度非常丰富,有数值型变量(价格、续航、销量)、有文本型变量(用户评价、新闻标题)、有时间序列变量(月度销量、季度市占率),这为后续做数据分析和机器学习提供了天然的“实验场”。最后也是最重要的一点,这个领域大家有感知、有讨论度,答辩的时候你能用数据讲故事,评委老师也听得进去。
从技术角度讲,这个选题能自然覆盖一个完整的大数据应用链路:Scrapy爬虫做数据采集,Django做后端服务和数据管理,ECharts做可视化大屏,机器学习做销量预测和情感分析,再配合大模型API做自动化的数据洞察报告生成。 这样一个闭环,既不是“堆技术名词”,也不是“只做一个CRUD网站”,它有真实的数据流动逻辑,也有看得见的展示效果。
1.2 技术栈选型:为什么这组搭配“能打”且“好说”
毕设技术栈的选择逻辑只有一个:评审老师能不能在十分钟内理解你的架构,以及你能不能把每个组件的作用讲清楚。 我见过有同学用微服务做毕设,六个服务五个数据库,结果答辩的时候自己都讲不利索。这不是炫技,这是给自己挖坑。
我们来拆一下这套技术栈的选型理由:
| 技术组件 | 在这个项目里扮演的角色 | 为什么不换成别的 |
|---|---|---|
| Django框架 | 后端主框架,负责数据管理、业务逻辑、API接口、页面渲染 | 比Flask更适合“有管理后台需求”的项目,自带Admin和ORM,减少大量重复代码 |
| Scrapy爬虫 | 数据采集层,负责定时抓取公开数据 | Requests+BeautifulSoup写起来简单,但遇到多页面、并发抓取、断点续爬就很吃力 |
| MySQL | 数据持久化存储 | 数据结构固定,关系查询多,最适合传统关系型数据库 |
| ECharts | 可视化图表渲染 | 中文文档全,图表类型丰富,地图热力图支持成熟,对答辩演示友好 |
| scikit-learn / Prophet | 机器学习建模 | 不追求高精度,重点是“流程完整”:训练、评估、预测、可视化一条线 |
| 大模型API | 自动生成数据洞察结论、自然语言问答 | 本地部署大模型成本高、环境依赖多,毕设阶段调用成熟API更稳妥 |
这套组合最大的优势是每一层都有明确的教育意义:爬虫让学生理解网络数据是怎么获取的,Django让学生理解Web后端是怎么组织的,可视化让学生理解数据是怎么被呈现的,机器学习和大模型让学生理解“数据→模型→结论”这条智能化链路。三分钟把架构图画完,每一层都有话可讲,这是毕设答辩的理想状态。
1.3 功能模块地图:系统到底需要做哪些页面
很多同学一上来就写代码,写到一半发现页面东一块西一块,缺这个少那个。我的习惯是先画功能地图,再动键盘。这个系统的核心功能模块可以分成六块:
- 数据采集模块:Scrapy爬虫定时抓取新能源汽车的车型参数、月度销量、用户评价、充电桩分布等公开数据。
- 数据管理模块:Django Admin后台,对爬取到的数据进行查看、筛选、修正、导出,管理员可以手动干预数据质量。
- 数据分析模块:对销量、价格、续航、地区分布等维度做统计分析和交叉分析,计算环比、同比、市占率等指标。
- 智能洞察模块:机器学习销量预测 + 用户评价情感分析 + 大模型自动生成月度数据洞察报告。
- 可视化大屏模块:通过ECharts展示总览大盘、品牌对比、车型分布、地图热力、趋势预测等图表。
- 用户交互模块:登录、权限控制、收藏、筛选、数据下载。
每个模块不需要做得特别复杂,但必须完整。评审老师看毕设,最反感的是“演示的时候只能展示一个孤零零的图表页面,后面的功能全是线框图”。功能地图的作用,就是让你从第一天就知道整个系统长什么样,而不是边写边想。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据采集层设计:怎么把公开数据变成“干净”的数据
2.1 Scrapy爬虫的架构设计:从单页抓取到全站爬取
爬虫是整个项目的数据源头,但很多同学在这里犯一个致命错误——把爬虫写成一次性脚本。跑完一次,数据拿到手,就不再管了。这会导致后面做可视化的时候,数据是“死”的,没有任何更新能力。而一个数据洞察系统,数据更新应该是常态。
我建议把爬虫设计成可以定期增量执行的Scrapy项目。项目里至少包含这样几个组件:
- spiders/新能源汽车数据爬虫.py:定义爬虫规则,指定起始URL、解析函数、跟进链接规则。
- items.py:定义数据字段结构,比如品牌、车型、能源类型、指导价、续航里程、月度销量等。
- pipelines.py:定义数据清洗和入库逻辑,包括去重、类型转换、缺失值填充。
- middlewares.py:配置下载中间件,处理User-Agent伪装、请求频率控制、代理切换。
以抓取新能源汽车销量数据为例,爬虫核心逻辑大致如下:
python复制import scrapy
from new_energy_crawler.items import SalesItem
class SalesSpider(scrapy.Spider):
name = "sales_spider"
def start_requests(self):
# 指定要抓取的月度销量榜单URL
url = "https://example-public-data.com/monthly_sales?month=202506"
yield scrapy.Request(url=url, callback=self.parse_list, meta={"month": "202506"})
def parse_list(self, response):
month = response.meta["month"]
# 解析表格数据
for row in response.css("table tbody tr"):
item = SalesItem()
item["month"] = month
item["brand"] = row.css("td.brand::text").get()
item["model"] = row.css("td.model::text").get()
item["sales_volume"] = int(row.css("td.volume::text").get().replace(",", ""))
yield item
这里有几个细节需要注意:
第一,不要一上来就全站爬取。 先爬一个月的数据,把解析规则跑通,再通过跟进链接扩展。Scrapy的Rule和LinkExtractor可以做自动翻页和链接提取,但建议显式控制爬取范围,避免爬到无关页面。
第二,动态加载页面的处理。 很多数据网站是Ajax异步加载的,直接用response.css()拿不到数据。我的做法是:优先检查网页源码里是否有__NEXT_DATA__或window.__INITIAL_STATE__这类内嵌JSON,如果有,直接用正则或JSON解析提取数据,效率比加载浏览器高得多。只有在内嵌数据不存在的情况下,再考虑用Scrapy Playwright或Selenium渲染动态页面。
第三,设置合理的下载延迟。 不要用默认的并发设置跑爬虫,尤其是目标站点没有提供API的情况下。DOWNLOAD_DELAY = 3会在每个请求之间间隔3秒,配合CONCURRENT_REQUESTS = 8,既能保证抓取速度,也不会给目标服务器造成压力。
2.2 数据清洗与标准化:爬下来的数据不能直接用
爬虫完成的那一刻,你手里拿到的是一堆“脏数据”。别想着直接把数据塞进数据库——数据清洗才是真正见功夫的地方。
我总结了最容易出现的四类问题:
- 缺失值:车型的续航里程、快充时间、电池容量等字段经常为空。处理策略要按字段区分:核心字段(品牌、车型、价格)如果缺失,直接丢弃该记录;非核心字段(电池类型、百公里电耗)缺失时,可以用同类车型的中位数填充,或者标记为“未知”。
- 格式不一致:同一个价格,有的源写成“15.98万”,有的源写成“159800”,还有的写成“15.98万元”。你需要写一个统一的标准化函数,把所有价格字段转成浮点数(单位:万元),把所有续航字段转成整数(单位:公里)。
- 重复数据:不同数据源可能覆盖同一车型,导致重复记录。解决方法是给
model + config_version设置唯一索引,入库前先做查重。 - 异常值:销量数据可能出现极端值,比如某个月某车型销量是其他月份的100倍,很可能是数据源录入错误。我的做法是引入一个简单的规则:如果某条记录的数值超过该车型历史均值的5倍标准差,就标记为异常,交由人工判断。
数据清洗的每个步骤,都要有日志输出。我在Pipeline里加了一个简单的统计器:
python复制class DataCleaningPipeline:
def process_item(self, item, spider):
if not item.get("price") or item["price"] <= 0:
spider.crawler.stats.inc_value("cleaned/price_missing")
raise DropItem("价格字段无效")
# 标准化处理
item["price"] = float(str(item["price"]).replace("万", "").replace("元", ""))
item["battery_range"] = int(float(item["battery_range"]))
return item
这样跑完一次爬虫,直接看cleaned/price_missing的计数,就能知道有多少数据被清洗掉了,方便评估数据源的可靠性,也方便答辩的时候说实话、讲细节。
2.3 数据存储设计:MySQL表结构怎么建
数据存储使用的是MySQL。数据库设计上,我建立了6张核心表,这里重点说三张:
- car_brand(品牌表):id、品牌名称、品牌所属国家、成立时间、logo图片URL。
- car_model(车型表):id、品牌ID(外键)、车型名称、车辆类型(轿车/SUV/MPV)、能源类型(纯电/插混/增程)、指导价、上市时间、续航里程、电池容量、电机功率。
- sales_record(销量表):id、车型ID(外键)、统计月份、销量数据、环比增长率、同比增长率。
这样的表结构设计的核心逻辑是:把静态属性和动态属性分开。车型的品牌、价格、续航是静态属性,变化频率低;月度销量是动态属性,每个月都会新增记录。如果全都塞在一张大宽表里,后续做筛选、聚合、时间序列分析都会很别扭,查询性能也不理想。
建完之后,别忘了给经常用于查询的字段加索引。比如sales_record表里month和car_model_id就是高频查询字段,加联合索引后,按月份筛选和按车型聚合的速度会有明显提升。这个细节虽然小,但Django的ORM在执行复杂查询时,如果索引缺失,慢得会让你怀疑人生。
3. Django后端与“数据洞察”的真正实现
3.1 Django项目骨架搭建:爬虫和Web必须物理分离
这是一个很多人忽略的架构决策。我在项目里把代码组织成两个目录:
code复制new_energy_project/
├── backend/ # Django Web 项目
│ ├── manage.py
│ ├── config/ # 项目配置
│ └── apps/
│ ├── data_manage/ # 数据管理模块
│ ├── analysis/ # 数据分析模块
│ ├── insight/ # 智能洞察模块
│ └── dashboard/ # 可视化大屏模块
└── crawler/ # Scrapy 爬虫项目
├── scrapy.cfg
└── new_energy_crawler/
├── spiders/
├── items.py
└── pipelines.py
为什么要分成两个目录?因为Django和Scrapy的生命周期不同,强耦合会互相拖累。Django是在线服务,需要稳定运行;Scrapy是离线任务,定期跑一次。如果Scrapy的依赖(比如Playwright的浏览器内核、Torch等重型库)直接装进Django的环境,服务启动会变慢,出问题的概率也会增加。
但两者之间不能完全隔离,数据需要流通。我的做法是:Scrapy的Pipeline直接使用Django的ORM模型,只需要在Scrapy的settings.py里配置Django环境变量:
python复制import os
import django
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django.setup()
这样在Pipeline里就可以直接写:
python复制from apps.data_manage.models import CarModel, SalesRecord
def process_item(self, item, spider):
car_model = CarModel.objects.filter(model_name=item["model"]).first()
SalesRecord.objects.update_or_create(
car_model=car_model,
month=item["month"],
defaults={"sales_volume": item["sales_volume"]}
)
用update_or_create的好处是天然幂等,反复执行同一批数据不会产生重复记录。
3.2 数据模型设计:Django ORM怎么建模
Django的模型层是整个后端业务逻辑的基石。我定义了三个核心模型来支撑整个系统的业务逻辑:
python复制from django.db import models
class Brand(models.Model):
name = models.CharField(max_length=50, unique=True, verbose_name="品牌名称")
country = models.CharField(max_length=20, blank=True, verbose_name="所属国家")
class Meta:
db_table = "car_brand"
verbose_name = "品牌"
verbose_name_plural = "品牌"
def __str__(self):
return self.name
class CarModel(models.Model):
ENERGY_TYPE_CHOICES = (
("BEV", "纯电动"),
("PHEV", "插电混动"),
("EREV", "增程式"),
)
brand = models.ForeignKey(Brand, on_delete=models.CASCADE, verbose_name="所属品牌")
model_name = models.CharField(max_length=100, verbose_name="车型名称")
energy_type = models.CharField(max_length=10, choices=ENERGY_TYPE_CHOICES, verbose_name="能源类型")
price = models.FloatField(verbose_name="指导价(万元)")
battery_range = models.IntegerField(verbose_name="续航里程(km)")
launch_date = models.DateField(null=True, blank=True, verbose_name="上市日期")
class Meta:
db_table = "car_model"
verbose_name = "车型"
verbose_name_plural = "车型"
def __str__(self):
return f"{self.brand.name} {self.model_name}"
class SalesRecord(models.Model):
car_model = models.ForeignKey(CarModel, on_delete=models.CASCADE, verbose_name="车型")
month = models.CharField(max_length=6, verbose_name="统计月份(YYYYMM)")
sales_volume = models.IntegerField(verbose_name="销量(辆)")
class Meta:
db_table = "sales_record"
unique_together = ("car_model", "month")
verbose_name = "销量记录"
verbose_name_plural = "销量记录"
def __str__(self):
return f"{self.car_model.model_name} - {self.month} - {self.sales_volume}"
这个模型设计考虑了三层因素:
- 外键关系:车型通过外键关联品牌,销量通过外键关联车型。Django的
select_related和prefetch_related可以完美处理这类关联查询,避免N+1问题。 - 唯一约束:
unique_together确保同一车型同一月份只有一条销量记录,从数据库层面防止重复。 - 可读性:每个字段都加了
verbose_name,Django Admin后台会直接显示中文名称,管理体验好很多。
3.3 机器学习在这里到底干什么活
很多同学对“机器学习”这个关键词有误解,以为一定要训练一个多么精深的深度学习模型。其实毕设阶段的机器学习,核心是走通流程:数据预处理→特征工程→模型训练→评估→预测→可视化。
在这个项目里,我把机器学习任务拆成了两个小而完整的子任务:
任务一:月度销量预测。 用过去24个月的销量数据,预测未来3个月的销量趋势。这一步我用了两种方法对比:一种是Prophet,它对时间序列数据非常友好,处理节假日、周期性效应都很方便,而且代码量极少;另一种是LinearRegression加滞后特征,简单但可控性强,适合在答辩中讲解原理。预测结果会存回数据库,在前端用折线图展示“历史实际值 + 未来预测值”的对比。
python复制from prophet import Prophet
def train_sales_forecast(car_model_id):
sales_df = get_sales_dataframe(car_model_id)
model = Prophet(yearly_seasonality=True, weekly_seasonality=False)
model.fit(sales_df[["ds", "y"]])
future = model.make_future_dataframe(periods=3, freq="MS")
forecast = model.predict(future)
return forecast.tail(3)[["ds", "yhat", "yhat_lower", "yhat_upper"]]
任务二:用户评价情感分析。 对爬取的用户评价进行中文分词,用TF-IDF提取特征,然后用朴素贝叶斯或逻辑回归做情感二分类(正向/负向)。这里不追求用BERT或者大模型去刷准确率,为什么?因为数据集规模和标注成本都不支持,而且逻辑回归的参数含义更直观,答辩时解释起来不费劲。把情感分析结果聚合成“车型好评率”指标,在前端用仪表盘或者进度条展示,视觉冲击力很强。
3.4 大模型API接入:自动生成“数据洞察结论”怎么做
大模型是本项目最具亮点的部分,也是最容易翻车的部分。翻车的原因往往不是技术难度,而是没有想清楚大模型在系统里到底解决什么问题。
我的设计思路是:大模型不负责算数据,只负责把算好的数据“翻译成人话”。 具体的统计指标、环比计算、排名变化,全部由Django后端用代码完成,然后再把这些结构化结果组织成提示词,交给大模型生成连贯的洞察报告。
示例提示词模板如下:
python复制def generate_insight_prompt(month_stats):
return f"""
你是一位新能源汽车行业的数据分析师。请根据以下统计数据,生成一份200字左右的市场洞察简报,要求语言简洁、结论明确。
本月整体销量:{month_stats["total_sales"]} 辆,环比增长 {month_stats["mom_growth"]}%。
销量最高的品牌:{month_stats["top_brand"]},市占率 {month_stats["top_share"]}%。
增长最快的车型:{month_stats["fastest_model"]},环比增长 {month_stats["fastest_growth"]}%。
用户评价最集中的关键词:{month_stats["top_keywords"]}。
请输出:
1. 对本月新能源汽车市场的整体判断
2. 值得关注的一个亮点和一个问题
"""
需要注意的是,实际调用大模型API时涉及接口地址、鉴权方式、模型名称等配置,这些都要放到Django的settings.py或环境变量中,不要把密钥硬编码在代码里。调用结果可以缓存,比如一个月度报告生成一次后存到数据库,避免重复调用产生费用。
注意:这里强调一下,毕设中使用大模型API做文本生成,完全合理合规,跟训练数据无关。但任何涉及数据合规的问题,建议只使用公开可获取的数据源,并在论文中明确注明数据来源。
另外,大模型生成的内容未必100%准确,所以系统在展示洞察报告时,需要同时展示“数据依据”。我在页面上做了一个双栏布局:左边是大模型生成的文字结论,右边是对应的统计图表。这样评委老师既能被智能化能力吸引,又能通过右侧的数据验证结论的可靠性。
4. 可视化方案:数据怎么既“好看”又“有用”
4.1 可视化选型:为什么用ECharts而不是其他图表库
可视化是毕设的“门面”,也是答辩时分数的关键。选ECharts的原因很简单:图表类型全、中文文档好、配置灵活、社区方案多。 对比一下:Chart.js轻量但图表类型有限,D3.js灵活但学习曲线陡峭、开发效率低,AntV虽然好看但生态相对复杂。ECharts在中国地图热力图、关系图、仪表盘这几类图表上都有成熟方案,非常适合大数据展示。
前端渲染方式我推荐Django模板渲染 + ECharts CDN的组合,而不是前后端分离。理由很务实:毕业设计不需要前端工程化那套复杂度,也不需要Vue/React那样单独的构建流程。Django把数据通过View函数传给模板,模板在<script>标签里把JSON数据塞给ECharts,开发效率极高,代码量也少。如果你想做得更正式一点,可以用Django REST Framework提供API + Vue前端,但说实话,不到4000行的项目,维护两套前端工程是在给自己找事。
4.2 核心图表拆解:每一张图都要有“存在的理由”
我把可视化大屏拆成四个区域,每张图都有明确的分析目的:
第一区:总览指标卡片。 顶部用4张数字卡片展示本月总销量、环比增长率、在售新能源车型数、平均续航里程。这些指标是全屏信息的“锚点”,用户进入系统第一眼就能知道整体大盘情况。
第二区:销量趋势折线图。 近24个月的总销量走势,配合3个月预测线。这里有个用户体验细节:预测部分用虚线,实际部分用实线,中间用一条垂直的参考线隔开,这样用户一眼就能看到“哪里是实际,哪里是预测”。
第三区:品牌市占率柱状图和地图热力图。 柱状图展示Top10品牌的月销量,地图热力图展示不同省份的销量分布。地图选用中国地图,ECharts需要单独引入中国地图GeoJSON数据,不熟悉的话很容易漏掉这一步。
第四区:价格-续航散点图和用户评价词云。 散点图可以直观展示“价格”和“续航”两个维度的相关性,标注出“高性价比”车型区域;词云展示用户评价中最常出现的关键词。这两张图非常出效果,尤其是词云,几乎每个评委老师都会多看两眼。
4.3 数据交互:不要只做一张“死”大屏
大屏不是一张静态截图,必须有交互筛选能力。我在页面顶部加了一排筛选条件:时间范围、品牌、能源类型。用户选择不同条件时,通过Ajax请求后端API,动态更新图表数据。
Django侧对应的API逻辑大致是这样的:
python复制import json
from django.http import JsonResponse
from django.db.models import Sum
def get_sales_trend_data(request):
start_month = request.GET.get("start_month", "202301")
end_month = request.GET.get("end_month", "202506")
brand_id = request.GET.get("brand_id")
queryset = SalesRecord.objects.filter(
month__gte=start_month,
month__lte=end_month,
)
if brand_id:
queryset = queryset.filter(car_model__brand_id=brand_id)
monthly_data = (
queryset.values("month")
.annotate(total=Sum("sales_volume"))
.order_by("month")
)
return JsonResponse({
"months": [item["month"] for item in monthly_data],
"totals": [item["total"] for item in monthly_data],
})
这里有一个小坑:Django的ORM聚合结果默认不能直接JSON序列化,需要手动转成列表。另外,筛选条件要全部在Django层做,而不是把全量数据拉到前端再筛。这不仅是性能问题,更是数据安全的边界问题——前端只应该拿到它需要的聚合结果。
交互的另一个维度是下钻。比如柱状图点击某个品牌,下方会联动展示该品牌的车型销量排名。下钻逻辑不复杂,但能让用户感觉到“这个系统是真的在做分析,而不是画了几张静态图”。
5. 毕设开发中容易踩的坑和答辩准备
5.1 爬虫数据拿了不到/数据量太少的应对方案
这是最常卡住学生的坑。公开网站的页面结构会变,反爬策略会升级,今天还能抓的数据明天可能就抓不到了。我在开发时经历过目标网站改版,辛辛苦苦写好的解析规则全部失效,当时整个人是崩溃的。
我的应对策略是采用“多源备份 + 手动补充”模式:
- 至少准备2个数据源。数据源A失效时,切换到数据源B,保证数据链路不断。
- 对关键月份数据做手动校准。如果发现某个月份的销量数据和公开报道中的趋势明显不符,用Django Admin后台手动修改。
- 如果实在拿不到某个字段,不要硬编造,允许它为空。在可视化时对空值做降级展示(比如显示“暂无数据”),反而显得系统处理规范。
- 数据量不是越大越好。一款车型12个月的销量记录,已经有足够的时间序列分析意义。不要陷入“必须抓10万条数据”的执念。
另外,考虑到爬虫的不确定性,我在项目中嵌入了一个“数据模拟器”。当真实数据不足时,可以基于已有数据的分布特征生成模拟数据,确保系统演示不中断。这一点我会在论文里如实说明,不会去糊弄评委。
5.2 Django与机器学习/大模型联调的“版本地狱”
另一个高频坑是版本兼容问题。Django新版本和MySQL驱动的兼容性、Prophet依赖的cmdstanpy安装、Python版本和机器学习库的适配……每一样都能折腾半天。我的建议是在项目一开始就用虚拟环境锁版本:
bash复制python -m venv venv
source venv/bin/activate
pip install django==4.2.* djangorestframework scrapy mysqlclient prophet scikit-learn pandas
pip freeze > requirements.txt
其中一个特别值得提醒的是mysqlclient的安装。在Windows上它经常需要提前安装MySQL C客户端库,否则编译直接报错;在Mac上则相对顺畅。如果编译遇到问题,可以改用pymysql:
python复制import pymysql
pymysql.install_as_MySQLdb()
至于大模型API调用,我在Django里做了一个单独的LLMService类,封装所有调用逻辑。这样即使切换模型厂商,只需要改这一个类,不用动其他业务代码。
5.3 答辩中的展示顺序和常见追问
答辩是整个毕设的“临门一脚”。我的建议是不要先讲技术细节,先把“一个问题从数据到结论的完整链路”讲清楚。比如:
- 我通过Scrapy爬虫拿到了新能源汽车销量数据(展示爬虫代码和数据表)
- 数据经过清洗进入MySQL数据库(展示Django Admin后台)
- 后端对销量做时间序列分析,用Prophet预测未来趋势(展示预测曲线)
- 机器学习模型对用户评价做情感分析(展示好评率数据)
- 大模型自动生成月度洞察报告(展示报告的生成过程和结果)
- 前端大屏把所有结果可视化展示(展示各种图表)
这套顺序本身就是完整的“数据流故事”,评委老师跟着你的节奏走,自然会认可项目的完整性。
常见追问无非这几种:
- “你的预测模型准确率多少?” 不要乱报数字。要诚实回答模型评估指标,比如MAPE(平均绝对百分比误差),并说明数据集规模和局限性。
- “如果数据源反爬怎么办?” 把我前面讲的“多源备份 + 手动校准”方案说一遍即可。
- “大模型生成的内容有没有可能出错?” 承认有这种可能性,并说明你的系统设计上有“数据依据对照区”,用户可以用图表数据核验结论,这本身就是一种纠错机制。
还有一个小技巧:答辩前把所有演示页面前后端都启动好,并准备一个“降级备胎”。 如果现场网络波动导致大模型API不可用,日志里要有报错提示,页面上也能正常显示所有统计数据,只是结论部分显示“生成中,请稍后”。这一点极其重要,我见过太多因为现场断网或API限流导致当场翻车的案例了。
五月初我把整个系统跑通、写完论文的时候,已经是凌晨四点。那段时间反复在爬虫、Django、前端图表、模型训练之间横跳,每次调试都要重新翻一遍报错日志。但说句实话,整个项目做下来的收获,比大学四年里任何一门课都多——因为你必须把所有知识捏在一起,让它们在一个真实系统里协同工作。最后分享一个我个人的体会:毕业设计的核心不在于“技术多新”或者“模型多深”,而在于你能不能把自己的设计决策、实现路径、踩坑过程和最终效果完整地讲成一个逻辑通畅的故事。如果你正在做这个题目,或者准备选类似的方向,希望这篇拆解能帮你少走几个弯路。
