☰
Django高考志愿推荐系统实战:从数据模型到冲稳保算法
2026/10/4 4:37:54 网站建设 项目流程

简介:这份资源是基于Django与智能算法构建的高考志愿填报推荐系统源码包,面向具备Python Web基础、希望研究推荐算法落地或搭建志愿填报工具的学习者与开发者。系统以Django承担后台数据存储、处理与查询,前端通过Templates模板与static静态资源实现页面渲染,算法层则融合数据挖掘、排序、预测与优化思路,结合历年录取数据、考生分数与兴趣偏好生成个性化志愿建议,并覆盖K-12阶段的政策、专业与就业信息。压缩包共631个文件,约85.91MB,包含93个py源码、34个html模板、90个js脚本、14个css样式,以及csv、xlsx、json等数据文件与docx、pdf说明文档,另附ipynb实验笔记和sqlite3数据库,便于还原完整项目结构。目前已有41人学习下载,适合作为课程设计、毕业设计或算法实践参考,可从中梳理Django项目分层、模板与静态资源组织方式及推荐流程的实现思路。

1. 从一张志愿表说起:这套 Django 推荐系统到底能帮你省下多少手工比对

每年六月,总有亲戚朋友甩过来一张 Excel 志愿表,几十个院校专业组,每个后面跟着往年最低分、位次、招生计划,然后问「你帮我看看能上哪个」。手工比对不是不能做,问题是数据一多,人脑就开始犯迷糊——冲稳保的梯度怎么分、专业级差怎么算、今年位次波动要不要修正,全靠经验拍脑袋。这套基于 Django 和智能算法的高考志愿填报推荐系统,干的就是把这件事流程化:后端用 Django 管院校专业数据、考生分数位次、历年录取线,前端给考生一个输入分数和位次的入口,算法层根据「冲稳保」策略和推荐算法算出匹配的院校专业列表,按录取概率排序输出。

它适合两类人:一类是想拿一个完整 Django 项目练手的新手,因为志愿填报这个场景天然带数据表设计、权限、表单、算法调用,麻雀虽小五脏俱全;另一类是真的想搭一个志愿辅助工具的开发者,代码结构可以直接改成自己省份的数据。核心不是算法多高深,而是把「数据怎么存、分数怎么匹配、推荐怎么排序」这条链路跑通。下面我按拆包顺序,从环境到算法到避坑,一步步说清楚。

2. 环境搭建与 Django 项目骨架:从零把跑起来的底座搭稳

2.1 为什么是 Django 而不是 Flask

志愿填报系统的数据模型不算简单:院校、专业、专业组、历年录取线、考生信息、推荐结果,表之间有多层外键关系,还要带后台管理来录入数据。Django 自带 ORM、Admin、表单验证和用户体系,这几样正好对上。用 Flask 不是不行,但你得自己拼 SQLAlchemy、Flask-Admin、WTForms,拼完发现工作量比 Django 还大。常见做法是:数据关系复杂、需要后台录入、需要快速出管理界面,选 Django;纯接口、轻量级,才考虑 Flask。这套代码选 Django 是合理的,别在这上面纠结。

2.2 环境准备与依赖安装

先确认 Python 版本。Django 3.x 系列对 Python 3.6 到 3.9 友好,Django 4.x 要求 Python 3.8 以上。如果你拿到的代码里requirements.txt写的是Django==3.2,那就别硬上 Python 3.11,容易在ugettext这类老 API 上报错。我一般会先建虚拟环境,把版本锁死。

# 建虚拟环境,避免污染系统 Python python -m venv venv # 激活:Linux/macOS source venv/bin/activate # 激活:Windows venv\Scripts\activate # 安装依赖,建议先看 requirements.txt 里的 Django 版本 pip install -r requirements.txt # 如果 requirements.txt 缺失或不全,手动装核心依赖 pip install django==3.2 mysqlclient pandas numpy

这里mysqlclient是 MySQL 驱动,如果代码用的是 SQLite 就不用装。pandas和numpy一般是算法层处理历年录取线数据用的,比如算位次波动、做归一化。装完先别急着runserver,先确认 Django 版本和代码匹配。

python -c "import django; print(django.get_version())"

如果打印出来的版本和requirements.txt差一个大版本,后面大概率会遇到ImportError,这时候要么降级 Django,要么改代码里的导入,别硬扛。

2.3 数据库配置与迁移

打开settings.py,找到DATABASES配置。SQLite 最省事,适合先跑通;MySQL 适合数据量大、要多人访问的场景。志愿填报的历年录取线数据,一个省动辄几万条,SQLite 也能扛,但如果你要频繁按位次范围查询,MySQL 加索引会更稳。

# settings.py 数据库配置示例(MySQL) DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'volunteer_db', # 数据库名,提前建好 'USER': 'root', 'PASSWORD': 'your_password', # 换成自己的 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'charset': 'utf8mb4'}, # 支持中文和特殊字符 } }

配置完执行迁移。迁移的本质是把模型类翻译成建表 SQL,Django 帮你管版本。

# 生成迁移文件,检测 models.py 的变化 python manage.py makemigrations # 执行迁移,真正建表 python manage.py migrate # 创建后台超级用户,用来录入院校数据 python manage.py createsuperuser

makemigrations只生成描述文件,不改数据库;migrate才落库。很多人第一次跑只执行了前者,然后发现表不存在,就是漏了第二步。如果迁移报「No changes detected」,说明模型没被注册到INSTALLED_APPS,检查你的 app 有没有加进去。

2.4 创建 app 与项目结构梳理

Django 项目实战新手最容易迷路的地方,是分不清 project 和 app。project 是配置容器,app 是功能模块。这套系统一般会拆成几个 app:users管考生账号,colleges管院校专业数据,recommend管推荐算法和结果。

# 创建功能 app python manage.py startapp colleges python manage.py startapp recommend python manage.py startapp users

创建完,每个 app 都要在settings.py的INSTALLED_APPS里注册,否则模型不生效、Admin 不显示。

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'colleges', # 院校专业数据 'recommend', # 推荐算法 'users', # 用户模块 ]

注册完再跑一次makemigrations,你会看到新 app 的迁移文件被生成。这一步顺序别搞反:先建 app、再注册、再迁移。反过来做,模型表建不出来,后台也看不到。

3. 数据模型与智能算法落地:院校专业表怎么设计、推荐怎么算

3.1 院校专业数据模型设计

志愿填报系统的数据模型,核心是三张表:院校表、专业表、录取线表。录取线表要按年份、省份、科类(物理/历史)区分,否则同一专业不同年份的分数会串。常见做法是把「院校+专业+年份+省份+科类」做成联合唯一约束,避免重复录入。

# colleges/models.py from django.db import models class College(models.Model): name = models.CharField('院校名称', max_length=100, unique=True) province = models.CharField('所在省份', max_length=20) level = models.CharField('院校层次', max_length=20, blank=True) # 985/211/双一流 def __str__(self): return self.name class Major(models.Model): college = models.ForeignKey(College, on_delete=models.CASCADE, verbose_name='所属院校') name = models.CharField('专业名称', max_length=100) category = models.CharField('专业类别', max_length=50, blank=True) def __str__(self): return f"{self.college.name}-{self.name}" class AdmissionScore(models.Model): major = models.ForeignKey(Major, on_delete=models.CASCADE, verbose_name='专业') year = models.IntegerField('年份') province = models.CharField('省份', max_length=20) subject_type = models.CharField('科类', max_length=10) # 物理/历史 min_score = models.IntegerField('最低分') min_rank = models.IntegerField('最低位次') plan_count = models.IntegerField('招生计划', default=0) class Meta: unique_together = ('major', 'year', 'province', 'subject_type') # 防重复

on_delete=models.CASCADE表示院校删了,下面的专业和录取线一起删,避免脏数据。unique_together是血泪经验:没有它,同一专业同一年份录两次,推荐时算出来的概率会翻倍偏差。min_rank位次字段比分数更关键,因为每年试卷难度不同,分数会漂,位次相对稳定,算法里应该以位次为主、分数为辅。

3.2 推荐算法的核心逻辑:位次匹配与冲稳保分档

智能算法这块,别被「智能」两个字唬住。志愿填报推荐的本质是:拿考生位次去和历年录取位次比,算出录取概率,再按概率分冲、稳、保三档。常见做法是用位次差做归一化,或者用简单的概率映射。下面是一个可复现的算法实现。

# recommend/algorithm.py import numpy as np def calc_probability(student_rank, history_ranks): """ 根据考生位次和历年录取位次,估算录取概率 student_rank: 考生位次(越小越靠前) history_ranks: 该专业近三年最低录取位次列表 """ if not history_ranks: return 0.0 # 用近三年位次的加权平均,越近的年份权重越高 weights = np.array([0.2, 0.3, 0.5][-len(history_ranks):]) weights = weights / weights.sum() avg_rank = np.average(history_ranks, weights=weights) # 位次差归一化:考生位次优于平均位次越多,概率越高 diff = (avg_rank - student_rank) / avg_rank # 用 sigmoid 把差值映射到 0~1 prob = 1 / (1 + np.exp(-diff * 8)) return round(float(prob), 4) def classify(prob): """按概率分冲稳保三档""" if prob >= 0.75: return '保' elif prob >= 0.45: return '稳' else: return '冲'

weights给近三年分别 0.2、0.3、0.5 的权重,是因为越近的年份参考价值越大,但也不能完全丢掉早年数据。sigmoid里的系数 8 是调节曲线陡峭程度的,调大一点,概率对位次差更敏感;调小一点,输出更平缓。这个系数没有标准答案,我一般会拿本省往年数据回测,看冲稳保的比例是否接近 3:4:3,再微调。classify的阈值 0.75 和 0.45 也是经验值,不同省份竞争激烈程度不同,可以改。

3.3 推荐接口与结果排序

算法算完,要通过视图暴露出去。考生提交分数和位次,后端查出候选专业,逐个算概率,按概率降序返回。

# recommend/views.py from django.http import JsonResponse from colleges.models import AdmissionScore from .algorithm import calc_probability, classify def recommend_view(request): rank = int(request.GET.get('rank', 0)) # 考生位次 province = request.GET.get('province', '') # 省份 subject = request.GET.get('subject', '') # 科类 results = [] # 查出该省该科类的所有录取记录 scores = AdmissionScore.objects.filter( province=province, subject_type=subject ).select_related('major', 'major__college') # 按专业聚合历年位次 major_map = {} for s in scores: key = s.major_id major_map.setdefault(key, {'major': s.major, 'ranks': []}) major_map[key]['ranks'].append(s.min_rank) for item in major_map.values(): prob = calc_probability(rank, item['ranks']) results.append({ 'college': item['major'].college.name, 'major': item['major'].name, 'probability': prob, 'level': classify(prob), }) # 按概率降序,冲稳保自然分层 results.sort(key=lambda x: x['probability'], reverse=True) return JsonResponse({'data': results[:50]}) # 只返回前50条

select_related是关键优化,它把关联的院校和专业一次性查出来,避免循环里反复查库(N+1 问题)。results[:50]限制返回条数,因为考生看不了几百条,前 50 条足够覆盖冲稳保。如果你不加这个限制,数据量大时接口会明显变慢。major_map按专业聚合历年位次,是因为一个专业有多年的录取记录,要合并后再算概率。

3.4 后台数据录入与 Admin 配置

算法再准,数据是空的也白搭。Django Admin 是录入院校专业数据最快的方式,把模型注册进去就行。

# colleges/admin.py from django.contrib import admin from .models import College, Major, AdmissionScore @admin.register(College) class CollegeAdmin(admin.ModelAdmin): list_display = ('name', 'province', 'level') search_fields = ('name',) # 按名称搜索 list_filter = ('province', 'level') # 按省份和层次筛选 @admin.register(AdmissionScore) class AdmissionScoreAdmin(admin.ModelAdmin): list_display = ('major', 'year', 'province', 'subject_type', 'min_score', 'min_rank') list_filter = ('year', 'province', 'subject_type') search_fields = ('major__name', 'major__college__name')

search_fields里用双下划线跨表搜索,major__college__name表示按专业所属院校名称搜。list_filter让你在后台按年份、省份快速筛,录入几万条数据时这个筛选器能省大量时间。数据录入建议用批量导入而不是一条条点,常见做法是写个管理命令读 Excel,这里不展开,但你要知道 Admin 适合校对,不适合大批量录入。

4. 避坑与排查:这套代码跑不起来时先看这几条

4.1 迁移报错「Table already exists」

现象:执行migrate时提示某张表已存在,迁移中断。原因通常是之前手动建过表,或者迁移记录和实际表结构不一致。解决:先看django_migrations表里对应 app 的记录,如果记录缺失但表在,可以python manage.py migrate --fake把记录补上;如果表结构确实冲突,进数据库DROP TABLE再重新迁移。别直接删库,先确认数据能不能丢。

4.2 推荐结果为空或全是同一档

现象:接口返回空列表,或者所有专业都是「冲」。原因一般是位次字段单位不统一——有的数据存的是「位次」,有的存的是「分数」,混在一起算,概率全乱。解决:检查AdmissionScore里min_rank存的是不是位次,考生传进来的rank是不是位次。另外确认province和subject_type筛选条件有没有传对,传错省份自然查不到数据。

4.3 中文乱码或 Admin 显示问号

现象:后台录入的中文院校名显示成问号。原因:数据库字符集不是utf8mb4,或者连接时没指定字符集。解决:建库时用CREATE DATABASE volunteer_db CHARACTER SET utf8mb4;,settings.py的OPTIONS里加'charset': 'utf8mb4'。SQLite 一般没这问题,MySQL 必查。

4.4 算法概率全是 0.5 附近

现象:不管考生位次高低,算出来的概率都挤在 0.5 左右。原因:sigmoid的系数太小,或者位次差diff被算成了接近 0。解决:检查avg_rank和student_rank的量级是否一致,如果一个是几万、一个是几百,diff会失真。另外把系数从 8 调到 12 试试,曲线更陡,区分度更高。回测时拿几个已知录取结果验证,别凭感觉调。

4.5 接口响应慢,几百条数据卡住

现象:推荐接口要好几秒才返回。原因:循环里查库导致 N+1,或者没加索引。解决:视图里用select_related预取关联数据;给AdmissionScore的province、subject_type、min_rank加数据库索引。在模型Meta里加indexes,或者迁移时手动建。数据量上万条后,索引的差别非常明显。

5. 进阶技巧:用管理命令批量导入数据并回测算法阈值

跑通之后,真正花时间的是数据准备和参数调优。我一般会写一个 Django 管理命令,把 Excel 里的历年录取线批量灌进数据库,比 Admin 一条条点快得多。

# colleges/management/commands/import_scores.py import pandas as pd from django.core.management.base import BaseCommand from colleges.models import College, Major, AdmissionScore class Command(BaseCommand): help = '从 Excel 批量导入录取线数据' def handle(self, *args, **kwargs): df = pd.read_excel('scores.xlsx') # 列:院校,专业,年份,省份,科类,最低分,最低位次 for _, row in df.iterrows(): college, _ = College.objects.get_or_create( name=row['院校'], defaults={'province': row['省份']} ) major, _ = Major.objects.get_or_create( college=college, name=row['专业'] ) AdmissionScore.objects.update_or_create( major=major, year=row['年份'], province=row['省份'], subject_type=row['科类'], defaults={'min_score': row['最低分'], 'min_rank': row['最低位次']} ) self.stdout.write(self.style.SUCCESS('导入完成'))

get_or_create保证院校和专业不重复建,update_or_create保证同一专业同年份的数据是更新而不是新增,配合模型里的unique_together双保险。执行时用python manage.py import_scores,把 Excel 放项目根目录。注意 Excel 列名要和代码里对上,列名不一致会直接KeyError。

数据进去后,回测阈值。拿本省去年的真实录取结果,跑一遍推荐,看「保」档里实际录取比例有多少,「冲」档里有多少真的冲上了。如果「保」档录取率低于 90%,说明阈值 0.75 定低了,往上调;如果「冲」档几乎没戏,说明 0.45 定高了,往下调。这个回测过程没有捷径,就是反复试。

还有个容易忽略的点:位次波动。有些专业大小年明显,一年高一年低,用三年加权平均会平滑掉波动,导致概率偏乐观。我一般会额外算一个位次标准差,标准差大的专业,在概率上打个折,提示考生「该专业波动较大」。这个折扣系数我习惯设 0.9,具体看数据。

从那以后我每次拿到这类推荐系统代码,都强制先跑一遍数据导入和回测,确认阈值和位次口径对得上,再去看前端。因为算法参数这东西,换个省份、换套数据就得重调,没有一套放之四海皆准的配置。希望这套拆解能帮你少走点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询