首页 / 资讯中心 / 文章详情

Django+Vue.js考研分数线预测与院校推荐系统毕设全解析

Django+Vue.js考研分数线预测与院校推荐系统毕设全解析 ★ FEATURED ARTICLE
带过几届毕业设计之后我越来越觉得计算机类的毕设选题不能只看技术新不新更要看它能不能讲清楚一个问题、能不能完整走通一条数据链路。今天想拆的这个题目——DjangoVue.js考研分数线预测系统、考研院校推荐系统就是很典型的一个技术栈成熟、业务场景真实、数据链路完整而且不管是做预测还是做推荐都有明确的用户痛点和可验证的结果输出。换句话说它像一个麻雀虽小五脏俱全的项目非常适合作为大数据方向的毕业设计。这篇博文我会从需求拆解、架构设计、数据库建模、预测模型落地、推荐算法实现、前端页面开发到最后的常见坑点排查完整走一遍。目标读者是那些选了类似题目但还不清楚从哪下手、或者想把这个项目做扎实的同学。我会尽量把每个环节里当时我也纠结过的决策过程写出来而不是只给结论。1. 项目拆解与核心需求解析1.1 分数线预测从一个很现实的焦虑说起考研择校这件事信息不对称是最大的问题。很多同学在三四月份决定要考某校某专业时手头能参考的往往只是去年的录取分数线、一两个经验帖、以及学长学姐的描述。但分数线本身受报名人数、试卷难度、招生计划变化、当年国家线波动等因素影响只看单一年份的数据几乎等于猜。所以这个系统的第一个核心需求就是分数线预测。预测对象分三层国家线、院校线、专业复试线。国家线是全国统一的基准线比较容易预测院校线是各校根据报考情况划的线波动更大专业复试线则是真正决定你能不能进复试的那条线也最有价值。预测的本质是一个回归问题——基于历史年份的分数序列以及影响分数的外部特征预测未来一年的分数线区间。注意我说的是区间不是一个精确值。因为分数线受当年试卷难度这种不可控因素影响很大精确到个位数的预测既做不到也不该承诺。好的毕业设计应该有这个分寸感把预测结果以区间或概率的形式呈现并且给出置信度的概念。1.2 院校推荐不只是分数够不够有了预测分数之后用户下一步自然要问我这个条件能冲哪些学校、稳哪些学校、保哪些学校这就是院校推荐系统的需求来源。但推荐不能只看预测分数线。一个完整的推荐决策涉及的因素至少包括本科院校层次双一流/普通一本/二本涉及复试歧视风险、目标地区东部中部西部涉及判卷宽松度和就业地缘、专业门类工科、理科、经管、文科的分数结构差异很大、学硕还是专硕、英语和政治难度预期、是否接受调剂以及最重要的——近几年报考热度和报录比。所以我把推荐系统定位成规则过滤 多维评分排序两段式结构。先通过硬性规则把完全不可能的和用户明确排除的院校过滤掉再对剩下的院校做综合评分按冲、稳、保三档输出。这个设计比单纯做一个分数填报查询要更有说服力答辩时也更容易讲清楚。1.3 技术选型为什么是DjangoVue.js这套组合先说结论这个组合非常适合做毕业设计而且是那种不会翻车的选择。Django的优势在于自带ORM、Admin后台、用户认证体系开发效率极高。尤其Admin后台录入历年分数线数据、维护院校信息直接就能用省掉大量后台管理页面的开发时间。另一个关键点是——Python生态。分数线预测要写特征工程、模型训练用Python做这件事再顺手不过了。训练好的模型用joblib或pickle存下来Django view里直接加载调用技术链路非常顺畅。这一点是SpringBoot那套Java技术栈很难比的。前端选Vue.js则看中的是组件化开发和数据可视化生态。ECharts配Vue 3画近几年分数线趋势图、报录比柱状图、分数分布热力图都是现成的组件。再加上Element Plus提供表单、表格、卡片、对话框等一整套UI组件你不需要自己从零写样式能把精力放在业务逻辑上。如果之前只接触过PHP或者纯模板渲染的开发方式这套前后端分离的架构可能需要一点时间适应但一旦理解了后端只管数据接口、前端只管页面交互的边界划分开发协调上反而更清晰——前端同学跟后端同学各改各的联调时对齐接口就行。单人完成毕设时这种边界也让代码结构更可控、更不容易写到一半乱七八糟。2. 系统架构与数据库建模2.1 前后端分离的目录结构设计整个项目的目录我建议分成前后端两个独立工程不要混在一个目录里。这样部署时思路清晰答辩讲架构时也更容易说清楚。GraduateAdmissionSystem/ ├── backend/ # Django工程 │ ├── manage.py │ ├── requirements.txt │ ├── configs/ # 全局配置 │ │ ├── settings.py │ │ ├── urls.py │ │ └── ... │ ├── apps/ │ │ ├── users/ # 用户模块 │ │ ├── schools/ # 院校与分数线模块 │ │ ├── predict/ # 预测模块 │ │ └── recommend/ # 推荐模块 │ ├── ml_models/ # 模型文件与训练脚本 │ │ ├── train_score_model.py │ │ ├── model_store/ │ │ └── feature_utils.py │ └── scripts/ # 数据导入清洗脚本 ├── frontend/ # Vue3工程 │ ├── package.json │ ├── vite.config.js │ ├── src/ │ │ ├── api/ # axios封装 │ │ ├── views/ # 页面组件 │ │ ├── router/ │ │ └── components/ └── documents/ # 说明书、PPT、SQL脚本这里有个细节把训练脚本和模型存放目录独立出来不要跟Django的业务代码混在一起。因为模型不是每次请求都要重新训练它应该在离线环境训练好、保存成文件在线环境只负责加载和预测。这个离线训练、在线预测的架构是机器学习工程里非常基础也非常重要的思想写进文档里会是个加分项。接口风格用RESTful。典型接口包括POST /api/users/register注册、POST /api/users/login登录GET /api/schools院校列表支持校区、地区、类型筛选GET /api/schools/{id}/score_lines某院校历年分数线POST /api/predict/score提交特征返回预测分数区间POST /api/recommend/schools提交用户画像返回推荐院校列表GET /api/recommend/options获取筛选条件的枚举值Django端需要处理跨域直接在settings里装django-cors-headers# settings.py INSTALLED_APPS [ # ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, # Vite开发服务器 http://127.0.0.1:5173, ]注意中间件顺序CorsMiddleware最好放在其他中间件之前否则某些情况下响应头会加不上。我早期在这个地方踩过坑接口数据都正常返回了但前端就是读不到排查半天最后发现是中间件顺序问题。2.2 核心数据表设计数据库我用MySQL原因很朴素毕设答辩环境跑这种系统MySQL比PostgreSQL更常见同学自己在本机装也方便。下面这几张表是核心中的核心设计时用点心能省后面很多事。第一张是院校信息表。字段除了基础名称、地区、院校层次我建议预留院校特色标签字段比如理工类、师范类、财经类、综合类这是推荐系统做画像匹配时的重要维度。# apps/schools/models.py from django.db import models class School(models.Model): 院校基础信息表 name models.CharField(院校名称, max_length128, uniqueTrue) province models.CharField(所在省份, max_length32) # 冗余省份名称 city models.CharField(所在城市, max_length32) region models.CharField(所属大区, max_length16) # 华东/华北/华南/西部等 level models.CharField(院校层次, max_length32) # 双一流A/B、普通一本、普通二本 category models.CharField(院校类型, max_length32) # 理工/师范/财经/综合等 is_985 models.BooleanField(是否985院校, defaultFalse) # 避免直接用985作为枚举值用布尔位便于筛选 is_211 models.BooleanField(是否211院校, defaultFalse) is_double_first_class models.BooleanField(是否双一流, defaultFalse) num_graduates models.IntegerField(每年毕业人数, nullTrue, blankTrue) tags models.CharField(院校特色标签, max_length255, blankTrue) created_at models.DateTimeField(创建时间, auto_now_addTrue) class Meta: db_table school ordering [id] def __str__(self): return self.name生产环境当然不推荐用is_985这种布尔字段去描述五位一体式的复杂层次结构那应该用关联表或者多值枚举。但对于毕设的体量布尔位直接、好查、好展示我认为是合理的取舍。答辩时如果能主动说出这里我做了简化取舍如果未来扩展可以改成标尺表反而显得你思考过。第二张是专业信息表。注意专业不能只存一个名称因为不同院校的同一专业名称可能不同但学科门类是一致的。class Major(models.Model): 专业信息表 code models.CharField(专业代码, max_length16, uniqueTrue) name models.CharField(专业名称, max_length64) category models.CharField(学科门类, max_length32) # 哲学/经济学/法学/教育学/文学/历史学/理学/工学/农学/医学/管理学/艺术学 is_master_degree models.BooleanField(是否学术型硕士点, defaultTrue) is_doctor_degree models.BooleanField(是否博士点, defaultFalse) description models.TextField(专业描述, blankTrue) class Meta: db_table major第三张是历年分数线表这是整个预测功能的数据底座。字段设计上有一条经验值得记住把分数线的科目构成拆开存。考研分数线不是单一数字它包含总分线和各科单科线。如果只存一个总分预测模型的输入维度就少了一大块更关键的是某些专业总分涨了但单科线降了这种结构性变化只有拆开存才能捕捉到。class ScoreLine(models.Model): 某院校某专业某年分数线记录 school models.ForeignKey(School, on_deletemodels.CASCADE, related_namescore_lines) major models.ForeignKey(Major, on_deletemodels.CASCADE, related_namescore_lines) year models.IntegerField(年份) category models.CharField(硕士类型, max_length16) # 学硕/专硕 total_score models.IntegerField(总分线) politics_score models.IntegerField(政治单科线) foreign_language_score models.IntegerField(外语单科线) business_course_one_score models.IntegerField(业务课一单科线) business_course_two_score models.IntegerField(业务课二单科线) enroll_count models.IntegerField(招生人数, nullTrue, blankTrue) applicants_count models.IntegerField(报考人数, nullTrue, blankTrue) admission_rate models.FloatField(报录比, nullTrue, blankTrue) source models.CharField(数据来源, max_length255, blankTrue) class Meta: db_table score_line unique_together (school, major, year, category)说几个建模时的关键判断。冗余地区和大区字段虽然可以通过省份坐标换算但接口筛选时频繁做坐标换算会让查询变复杂冗余存储直接查就行。报录比字段允许为空因为早年数据确实采集不到为空的记录预测时要单独处理。联合唯一约束school, major, year, category保证了同一条数据不会重复导入这个约束在数据清洗脚本里是兜底防线。用户相关表我反而建议做得简单一点用户表存账号密码和基本画像目标专业门类、意向地区、本科层次再加一张收藏表存用户收藏的院校。很多同学在用户模块上花太多时间做积分、做关注、做评论这些功能对毕设核心卖点的提升很有限不如把时间留给预测模型和推荐策略。2.3 字段设计里的经验教训我见过很多半途而废的毕业设计数据库设计缺陷是主要翻车原因之一。最常见的问题有两个。第一个是把年份相关的字段设计成属性列。有些同学图省事设计成score_2023、score_2024结果后续要加年份时就得改表结构写指标查询时也要写一堆动态列拼接的脏代码。正确做法就是上面的行式存储一条记录对应某校某专业某年某类型这样不管是预测训练集的构造按年排序还是前端画趋势图按年遍历都是最自然的方式。第二个问题是不区分数据属性。例如把是否接受调剂这种每年都会变化的政策信息设计成一个固定枚举这会导致年份变化后推荐结果失真。没错学费、宿舍条件、导师资源这些每年都可能变。分数线表和院校表里的数据时效性完全不同院校的基础属性层次、类型几年不变而分数线和招生计划每年都在变。建模时要把稳定性信息和时效性信息分表存放查询时关联即可。3. 分数线预测模型落地3.1 数据准备与合规处理预测模型的成败六成在数据三成在特征模型算法本身只占一成。对毕设项目来说你需要先把数据攒起来。分数线数据获取途径我建议分两步。第一步用爬虫去研究生招生信息网和各院校官网的历年公示页面爬公开数据。这里必须强调只爬公开的、可合法访问的数据控制并发频率不要爬任何需要登录或者明确禁止采集的信息。某高校公示的复试名单和拟录取名单包含分数、专业、初试复试成绩是很好的数据来源。第二步公开数据覆盖不足的部分用人工录入补全Django Admin直接可以操作。数据清洗这一步很多人会跳过但预测结果不准往往就是栽在这。常见的脏数据有年份字段格式不统一有的年份是四位数字、有的带年字专业名称不一致计算机技术和计算机科学与技术被当成两个专业同一学校临时改了校名导致的历史数据断层某些年份分数线缺失但前后年份都有值。清洗策略分两类。缺失值如果某缺失年份前后两年都有数据用前后均值填充如果连续缺失三年以上建议直接丢弃该序列因为填充的可靠性太低。异常值如果某年分数线突然比前后两年高出三四十分先核实原始数据确认无误后保留——这种异常往往是报考人数暴增造成的对预测模型来说反而是有信息量的样本不应该当脏数据处理。3.2 特征工程分数线到底被什么影响建模之前得先想明白一个问题分数线是果它的因是什么我整理过一轮把特征分成三组。第一组是历史趋势特征。前一年的总分线、前两年的总分线、近五年分数线的均值和方差。这组特征捕捉的是分数线的惯性——通常分数线不会一年内暴涨暴跌连续变化比例在20%以内是常态。第二组是报考热度特征。当年的报名人数、招生人数、报录比以及这三者与前三年的比值。注意报名人数和招生人数的增长率往往比绝对值更有预测力。比如某专业招生从50人缩到30人即使报考人数不变分数线也大概率上移。第三组是外部环境特征。当年的国家线总分、国家线单科线、当年公共课平均难度可以用样本考生平均分近似、目标院校所处地区的公共课判卷宽松系数。这个判卷宽松系数是个很微妙的东西——同样是英语二某省份平均分可能比另一省份高七八分这会直接影响总分线的实际含义。网上一直有考生把它叫作水区旱区虽然是非官方说法但作为特征输入模型是合理的。特征并不是越多越好。我最初做过一个版本把四十多个特征一股脑塞进去结果模型在训练集上表现很好测试集上完全失控——典型的过拟合。后来砍到十一个特征用特征重要性排序保证每个特征都有独立信息量效果反而稳定了。3.3 模型选型与训练脚本想在毕业设计里用LSTM做时序预测这个念头多半是看了某篇论文。但我的建议是先用简单模型跑通再按需升级。简单模型首选XGBoost或者随机森林理由是考研分数线数据量级就几百到几千条样本深度学习模型在这种小数据量上很难发挥优势反而容易过拟合。梯度提升树对特征的单位和分布不敏感不需要做复杂的归一化工程上省事。模型可解释性强能输出特征重要性答辩时可以清晰讲出影响分数线最大的因素是往年分数和招生人数变化这是一个非常有力的展示点。我当时做的第一版基线模型反而是线性回归用来做下限对比——即验证一个极其简单的模型能达到什么效果。答辩时能把简单模型 vs 集成模型的对比数据放出来比单纯吹自己模型多精确更有说服力。训练脚本独立放在ml_models目录下# ml_models/train_score_model.py import pandas as pd import joblib from sklearn.ensemble import GradientBoostingRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error # 1. 从数据库/CSV读取特征样本 data pd.read_csv(score_dataset.csv) # 关键点去掉泄漏信息的时间字段年份不能作为回归目标 feature_cols [ last_year_total_score, avg_score_5y, std_score_5y, applicants_growth_ratio, enroll_change_ratio, national_line_total, national_line_single, exam_difficulty_ratio, region_mark_coefficient, school_level_code, is_master_degree, ] X data[feature_cols] y data[total_score] # 2. 划分训练集与测试集 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.15, random_state42 ) # 3. 训练回归模型 model GradientBoostingRegressor( n_estimators200, max_depth4, learning_rate0.08, random_state42, ) model.fit(X_train, y_train) # 4. 评估 y_pred model.predict(X_test) print(MAE: %.2f % mean_absolute_error(y_test, y_pred)) # 5. 保存模型文件 joblib.dump(model, model_store/score_model_xgb.joblib) # 6. 输出特征重要性 importance sorted( zip(feature_cols, model.feature_importances_), keylambda x: x[1], reverseTrue, ) for name, value in importance: print(%s: %.4f % (name, value))一个非常重要的细节切分训练集和测试集时不能随机切分而要按时间顺序切分。比如用2018到2022的数据训练2023和2024的数据做测试。为什么因为随机切分会让模型偷看到未来数据——某一年份的样本既出现在训练集又可能出现在测试集的同一年在真实预测场景中这是不可能发生的测试结果会虚高。这叫时间序列数据的时间切分原则很多初学者在这里栽了跟头还误以为模型很厉害。模型评估用平均绝对误差MAE而不是准确率。分数线的预测误差在5到10分以内说明模型已经具备实用参考价值。你想考研复试线很多专业分分相差能达几十分预测误差在10分以内意味着能把用户的目标院校锁定在一个相对靠谱的范围内。3.4 预测接口的封装模型训练好以后Django端要做的工作就是接收前端传来的特征加载模型文件输出预测区间。# apps/predict/views.py import joblib import numpy as np from django.conf import settings from rest_framework.views import APIView from rest_framework.response import Response model joblib.load(settings.BASE_DIR / ml_models/model_store/score_model_xgb.joblib) class ScorePredictView(APIView): def post(self, request): # 前端传来的特征 feature_dict request.data # 构造特征向量顺序必须与训练时一致 feature_cols [ last_year_total_score, avg_score_5y, std_score_5y, applicants_growth_ratio, enroll_change_ratio, national_line_total, national_line_single, exam_difficulty_ratio, region_mark_coefficient, school_level_code, is_master_degree, ] X np.array([[feature_dict.get(col, 0) for col in feature_cols]]) pred model.predict(X)[0] # 输出区间点预测值 ± 模型误差的1.5倍 lower max(0, int(pred - 8)) upper int(pred 8) return Response({ predicted_score: round(pred, 1), interval: [lower, upper], level: 高风险 if feature_dict.get(school_level_code, 0) 2 else 中等风险 })模型文件每学期重新训练一次就够了不需要在线更新所以启动时加载一次到内存完全没问题。我这里输出的高风险/中等风险是一个简化的风险标签实际生产会用分位数来算概率但毕设做到这个程度已经完全能展示。4. 院校推荐系统实现4.1 推荐流程设计推荐系统不需要上协同过滤、矩阵分解这种高级算法。原因很简单这个场景没有用户行为数据——用户最多收藏几所学校没有足够的点击、浏览、报考行为来支撑协同过滤。强行套用协同过滤是典型的为了用算法而用算法。实际可行的方案是内容过滤 规则打分。先构建用户画像再计算用户画像与每个院校特征的匹配度。匹配度由四个维度的得分加权求和得到分数匹配度权重0.4用户预估分数 / 目标院校近三年平均复试线。比值在1.1以上算稳1.0到1.1算适中0.9到1.0算需要冲刺。层次匹配度权重0.25用户本科层次与目标院校层次的关系。双一流冲击双一流加满分普通二本冲击顶尖院校加分低甚至为零避免推荐完全可望不可即的学校。地区偏好匹配度权重0.2用户意向地区命中加分同时考虑院校所在地区的就业承载力。专业热度匹配度权重0.15目标专业的报录比热度与用户风险承受能力是否匹配。用户选择求稳时高热度专业降权。这个权重不是拍脑袋定的我做了一个简单的问卷把十组不同的推荐结果给同届考研的学生选统计他们更认可哪种排序方式用排名数据反推权重。虽然样本量只有几十人但它至少说明权重是从用户反馈中来的而不是我对着空气编的。排序完成后再打档位综合得分前20%标为冲刺院校中间50%标为稳妥院校后30%标为保底院校。同时每一个推荐的院校卡片都要给出推荐理由的文本解释例如该校近三年复试线稳定在355至365分与你预估分数匹配度较高且该专业历年招生人数稳定在22人左右风险较低。在毕业设计答辩里可解释的推荐理由比黑盒排序结果更能打动人。4.2 过滤规则与边界情况过滤阶段要处理几个边界情况。第一用户预估分数为空的冷启动问题。允许用户不做分数预测、直接按地区专业浏览推荐结果。此时评分模型跳过分数匹配度维度其余三个维度按比例重新分配权重。这个设计让系统不至于因为没有预测结果就整个不可用。第二不接受调剂的用户要过滤掉往年显示有大量调剂名额的院校。反过来接受调剂的用户可以单独给一个调剂友好院校列表这个列表的数据来源是往年拟录取名单里调剂标注的记录比例。第三地区偏好的粒度。如果用户只选了浙江但该省院校资源少系统会自动扩展到同一大区的院校并提示浙江地区院校较少已为你扩展至华东区域推荐。这种扩展策略处理了数据稀疏问题也有实际意义——考研本来就有很强的跨省流动性没必要把范围锁死在一个省。4.3 推荐接口的代码实现推荐接口的伪代码逻辑可以这样组织# apps/recommend/services.py def recommend_schools(user_profile): # 1. 硬性条件过滤 schools School.objects.filter( province__inuser_profile.provinces, major__categoryuser_profile.major_category, ) # 2. 软性条件过滤可配置 if not user_profile.accept_adjust: schools [s for s in schools if s.adjustment_ratio 0.2] # 3. 综合评分 scored [] for school in schools: score ( 0.4 * match_score(user_profile.predicted_score, school.avg_score) 0.25 * level_match(user_profile.bachelor_level, school.level) 0.2 * region_match(user_profile.target_region, school.region) 0.15 * heat_score(user_profile.risk_attitude, school.applicants_growth) ) scored.append((school, score)) scored.sort(keylambda x: x[1], reverseTrue) # 4. 切三档 total len(scored) return { rush: scored[:int(total * 0.2)], steady: scored[int(total * 0.2):int(total * 0.7)], safe: scored[int(total * 0.7):], }这段代码里有个容易忽略的细节school.avg_score不是院校总分线的简单平均而是该院校在用户目标专业上近三年的平均复试线。因为同一个学校不同专业的分数线可以相差五六十分用全校统一平均分去匹配没有意义。所以这个字段的查询要关联到专业维度而不是简单的School模型字段。还有一个建议评分过程放在Django service层而不是view层view层只做参数校验和Response包装。这点在做代码审查时会很加分——职责单一view没有业务逻辑。4.4 可视化分析的可加分方向推荐系统做完基础功能以后我建议加一个考研大数据分析页面这是大数据毕设的题目内涵所在。不用做得很复杂三个表就够了近十年国家线趋势折线图按学科门类分色展示。这个图能让用户一眼看出自己专业的分数趋势是上行还是平稳。各省份学子报考热力地图用省份报考人次数据着色的中国地图。数据量不大但视觉冲击感强。院校分数线变动Top 10排行榜列出近三年涨幅最大的院校和专业。ECharts做这些图也就十几行配置但对大数据毕业设计的包装帮助很大。答辩时页面上有图、有数、有结论比纯表格要直观得多。5. Vue.js前端核心实现5.1 项目初始化与路由设计前端用Vite创建Vue 3工程比Vue CLI快很多。创建时选择vue-router和pinia作为依赖UI组件库单独安装Element Plus。npm create vitelatest frontend -- --template vue cd frontend npm install element-plus axios vue-router4 pinia echarts路由设计上我希望页面结构清晰不冗余/首页项目简介 快捷入口/predict分数线预测页输入特征展示预测结果和趋势图/recommend院校推荐页填写画像展示冲稳保卡片列表/schools院校库筛选浏览院校与分数线明细/analysis数据分析页几个可视化大图/login/register登录注册页其中/recommend和/analysis是核心页面演示时打开就是完整功能不用绕路这个细节在导师演示当天很重要——如果还要先登录、再点好几个菜单才能看到核心页面体验就很差了。5.2 预测页面的交互与可视化预测页的表单字段不多大概七个大学代号用于查院校层级、目标专业门类、预估公共课水平、目标地区、意向硕士类型学硕/专硕、风险偏好、是否接受调剂。提交后调用后端的/api/predict/score接口拿到预测区间然后在ECharts上把近五年历史分数线折线和预测区间带叠加展示。// src/api/predict.js import request from ../utils/request export function predictScore(data) { return request({ url: /api/predict/score, method: post, data, }) }预测结果区域我建议放三个东西大号的预测分数数字、预测区间区间带用半透明色带表示浮动区间、和一句话解释(你的预估分数与该专业近三年平均线基本持平处于中等风险区间)。这个解释后端要返回前端直接展示即可。人话解释是产品感和技术感最直接的差异。画趋势图时有一个小坑要注意ECharts横轴如果是年份数据量少只有五六年默认的类目轴是均匀分布的这是对的。不要因为看到X轴年份间隔不均匀就试图改成时间轴那会导致年份间隔被拉伸成等距反而不符合真实时间逻辑。5.3 推荐页面的卡片设计与筛选逻辑推荐页是系统里最复杂的页面因为它的交互链路长选择筛选条件 - 提交 - 返回三组卡片 - 点击卡片查看详情 - 详情里展示理由和分数线明细。筛选条件用Element Plus的el-form布局条件包括目标省份多选、专业门类、预估分数或预测结果、学硕专硕、风险偏好、是否接受调剂。提交按钮调用/api/recommend/schools接口。结果区用三块Tab或三个分组展示冲刺、稳妥、保底。每个院校卡片包含校名和院校层次标签Element Plus的el-tag近三年复试平均分和报录比预估分数与平均线的差值用8分或-5分这种直观表达推荐理由后端生成的文本卡片底部放两个操作按钮查看详情打开分数线明细抽屉收藏调收藏接口。收藏功能虽然是小功能却能让演示时有一个连贯的操作流预测 - 推荐 - 收藏 - 在个人中心看到收藏列表。5.4 前端工程化细节接口地址统一放src/utils/request.js里封装axios设置baseURL为/api这样本地开发时配置Vite代理到Django端口// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true, }, }, }, })用代理而不是在前端写死http://127.0.0.1:8000最关键的好处是之后部署到生产环境时不用改前端代码——Nginx统一反向代理后前端请求的同源地址自动转发到Django服务。这个习惯如果从一开始就养成部署debug的时间可以省出一整天。打包构建时还要注意history路由的问题。Vue Router如果使用createWebHistory生产环境必须让服务器把所有非静态文件请求都转发到index.html否则在/recommend直接刷新页面会404。Nginx配置里加一行try_files $uri $uri/ /index.html;就能解决。如果你不想折腾服务器重写规则最简单的方案是改用createWebHashHistoryURL里带个#号但美观性差一点。我建议毕设直接用history模式加Nginx一行重写规则讲解部署时还能顺便聊两句。6. 常见问题与排查指南6.1 十个高频坑点实录这批问题是我在这个项目类型的开发过程中真实遇到过、也帮学生排查过的按出现频率排序做成一个速查表。现象根本原因解决办法前端请求后端报CORS错误忘记配置django-cors-headers或中间件顺序不对检查settings.py把CorsMiddleware放到中间件最前axios请求返回403CSRF token校验失败前后端分离后用JWT认证或对API视图标注csrf_exempt用JWT替代session模型预测结果始终是同一数值前端传来的特征字段名和训练时不一致取默认值0在接口返回调试字段打印实际接收的特征字典训练集R²很高但测试集一塌糊涂随机切分了时间序列数据改成按年份切分保证训练集年份全部早于测试集推荐接口报列表为空过滤条件过严比如省份只选了一个且没有匹配数据增加扩展策略同大区扩展并提示同时过滤条件前端做非空校验前端打包后刷新404history路由没有配置重写规则Nginx加try_files规则或改用hash模式ECharts图表显示空白容器div高度为0或图表初始化在异步数据返回前给容器设置固定高度在nextTick里初始化图表Element Plus组件样式不对全量引入了Element Plus但没正确注册确保app.use(ElementPlus)且引入样式element-plus/dist/index.css登录后刷新页面就掉登录态JWT存在localStorage但没有在请求拦截器里带tokenaxios拦截器里从localStorage取出token加进Header数据库导入中文乱码连接串没指定utf8mb4字符集Django配置中OPTIONS加charset: utf8mb4MySQL表也设为utf8mb46.2 两个需要专门说说的深坑第一个是时间序列的数据泄露问题。我见过有同学的预测模型在测试集上MAE只有3分我可高兴了但后来发现他的特征里包含了当年实际分数线自己——他把目标字段total_score当成特征输进去了。自查方法很简单模型做完后把特征的列名列表打印出来人工确认目标字段不在其中。这种低级错误一旦自查出是很好的答辩素材因为它说明你理解数据语义而不是只会调库。第二个坑是预测目标的定义要统一。有的同学预测总分线有的预测复试最低录取分这两个概念完全不同——复试线是官方划的进复试的门槛而录取最低分是实际录取的最低成绩两者之间往往有差值。如果你的历史数据混用了这两种口径模型学出来的就是一个四不像。我做项目时在数据导入脚本里增加了一条强校验同一院校同一专业同一年份只能有一条分数线记录且total_score字段的值必须筛选在350到420之间的正常区间超出区间的直接进异常报告等待人工确认。这个校验逻辑很简单却守护了整条模型的底。6.3 调试技巧与效率心得分享三个提升调试效率的心得。第一Django的print大法并不过时。很多同学一遇到问题就去查日志框架但对于毕设规模的项目在view里加几行print(request.data)比配置loguru快得多。真正上线之前再把print清理掉就行。不要有心理负担工具是为效率服务的。第二接口自测用Postman或Apifox不要每次都开着前端页面点。后端开发阶段我就用Apifox保存了所有接口的请求模板参数改一改就能重新测试不用等前端把页面搭好。前后端并行开发本来就是分离架构的优势你得会利用它。第三数据库备份用Django的dumpdata。模型改来改去很容易把数据弄乱我会在每次做完重要修改后执行一次python manage.py dumpdata backup.json。数据丢了重新爬、重新录入的代价太高备份成本却低到可以忽略。7. 一些实际的扩展想法预测和推荐主流程做完后如果还有精力我建议挑下面两三个方向做扩展不用全做。方向一是加入复试与调剂信息模块。复试名单、拟录取名单、调剂缺额、调剂成功率这些数据在考研场景里的价值甚至比初试分数线更高。可以让用户按院校查看历年复试比对近三年复试线与录取最低分的差距这个差距能直观反映学硕复试刷人是否严重。方向二是把预测做成对比模式。用户可以选择两到三所目标院校并排展示预测区间、近五年走势、报录比变化帮助用户在纠结到底报A还是B的场景下做更理性的决策。A和B在这个场景下用某院校A、某院校B代替即可。方向三是导出报告。把用户的基础画像、预测结果、推荐院校列表、推荐理由等汇总生成一份PDF报告用户可以下载保存。这个功能用Python的reportlab或者前端用html2canvas转PDF都能实现工作量不大但演示效果非常完整会让人觉得整个系统的闭环是思考过的。最后说一句我自己带毕设时经常跟学生讲的话做这个题目目标不是做出一个无比精确的分数线预测而是用一套清晰的技术方案把数据流动起来——数据从爬虫到数据库、从特征工程到模型、从接口到前端图表每一步都有逻辑、有验证这本身就是一篇合格的毕业设计论文的骨架。而预测精度上的不完美恰恰是你展示误差分析、优化思路的空间。所以如果你正卡在某个环节别慌把链路拆细一步一步来。我在实际做这类项目时还有个体会推荐列表里永远要放几个意料之外、情理之中的院校。完全按规则打分结果容易过于保守在做推荐策略时我会故意给某些特征加上一点随机探索系数让结果里出现一两个用户可能没听过但匹配度其实很高的学校。这种惊喜感在演示时效果很好也给了用户探索新选项的理由。你也可以在项目里设计一个类似的探索参数然后主动向导师解释这个设计背后的意图——这是答辩加分项。祝你把题目做成一个能真正讲出故事的作品。
阅读完成 · 觉得有帮助?
咨询建站