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

Scrapy爬虫+SpringBoot+Vue构建高考志愿智能推荐系统

Scrapy爬虫+SpringBoot+Vue构建高考志愿智能推荐系统 ★ FEATURED ARTICLE
简介基于爬虫的高考志愿智能推荐系统是一套面向高校计算机类专业毕业生与开发者的完整 Java 毕设项目采用 SpringBoot Vue 前后端分离架构并集成 MySQL 数据库。系统覆盖考生端与后台管理员端考生可进行院校/专业/位次查询、MBTI 职业性格测试、模拟志愿填报与志愿管理管理员则负责专业、院校、招生计划、位次及咨询内容管理。项目还通过爬虫抓取中国教育在线的高考录取数据结合知识图谱与智能算法为考生提供个性化院校推荐功能体系完整、业务场景贴近真实需求。压缩包共 671 个文件以 Java、Vue、JavaScript 源码为主辅以 159 个 SVG 图标、数据库 SQL 脚本、说明文档、PPT 及启动运行脚本整体大小约 34.56MB配套资源较为齐全。目前已有 87 人学习下载。资料内含前后端完整工程、爬虫模块、数据库文件及演示文稿目录结构清晰部署说明与脚本齐备适合直接参考或二次开发可帮助快速完成毕业设计并理解智能推荐与爬虫技术的工程化落地。1. 高考志愿智能推荐系统爬虫数据先于算法成型做高考志愿智能推荐系统真正难的不是推荐算法而是先拿到往年每个学校、每个专业在各省的最低录取位次。这套基于爬虫的高考志愿智能推荐系统就是用 Scrapy 把这些公开数据抓下来、洗成结构化数据再交给 SpringBootVue 的前后端分离架构去承接业务考生登录后可以查专业、查院校、查位次做 MBTI 性格测试模拟填报志愿后台管理员负责维护专业、院校、招生计划和用户数据。市面上志愿卡价格不低信息却是去年的纸质的报考指南翻起来慢数据还是静态的。这套系统的价值就在于把“数据采集—数据管理—个性推荐—模拟填报”串成一条完整链路推荐结果里既有位次匹配的硬条件也有兴趣性格的软条件。适合正在做 Java 前后端分离毕设、又不想只写增删改查的同学也适合想展示爬虫落地和推荐逻辑的课设项目。跟着这套方案你拿到的是一份能直接交差、能讲清楚“为什么这么设计”的完整项目。2. 总体架构设计SpringBootVue 前后端分离与五大功能域划分2.1 技术选型为什么不用传统 JSP 单体应用答辩时评委最容易问的第一个问题就是为什么选前后端分离传统 JSP 单体项目把页面渲染和数据访问写在一起开发速度快但对这个项目不合适。原因很直接爬虫入库的数据要独立运行、独立维护不能和业务代码抢进程考生端和管理员端交互完全不同混在同一个工程里越写越乱后端只需要对外暴露 JSON 接口将来接小程序或者换前端都不用动服务端。这套项目实际是三个部分并行spider 目录是独立的 Python Scrapy 爬虫工程springboot2a1qfv22 里同时放了 admin 管理端和 front 考生端两个前端工程后端统一用 SpringBoot 提供接口数据库用 MySQL 5.7。JDK 1.8 配 Tomcat 7开发工具用 IDEA 或 Eclipse 都行Maven 负责后端依赖管理。前端通过 Axios 调后端接口管理员和考生共用同一套后端服务靠角色字段区分权限。2.2 模块划分管理员端与考生端的权限边界从业务角色看系统天然分成两大块。考生端面向最终用户功能有登录注册、专业查询、院校查询、位次查询、高考咨询浏览、历史录取数据查看、MBTI 职业性格测试、院校识别、专业院校收藏、模拟志愿填报和志愿管理。管理员端面向后台运营负责专业管理、院校管理、高考咨询管理、招生计划管理、位次管理和 MBTI 测试题目管理。这两个端不要做成独立的两个系统而是共用同一套用户表和角色字段。考生注册时默认 role1管理员账号在初始化 SQL 里预置 role0。后端接口统一以 /api/ 开头管理员相关接口加 /api/admin/ 前缀由拦截器校验角色。前端 admin 工程和 front 工程各自打包部署时 Nginx 按路径转发本地开发时直接两个端口并行跑避免跨域麻烦。2.3 数据库表设计核心数据表与字段约定数据库是整个系统的地基。我建议至少要落六张核心表院校表、专业表、录取数据表、位次表、用户表、志愿表再加上 MBTI 题目表和考生答题记录表。录取数据表是推荐算法的直接数据源字段设计一定要一次性想清楚后面爬虫抓回来的数据要能原样映射进去。CREATE TABLE t_admission ( id INT PRIMARY KEY AUTO_INCREMENT, school_name VARCHAR(100) NOT NULL COMMENT 院校名称, major_name VARCHAR(100) NOT NULL COMMENT 专业名称, province VARCHAR(20) NOT NULL COMMENT 招生省份, year INT NOT NULL COMMENT 录取年份, batch VARCHAR(20) DEFAULT NULL COMMENT 录取批次, subject_type VARCHAR(10) DEFAULT NULL COMMENT 科类 理/文/综合, min_score INT DEFAULT NULL COMMENT 最低录取分数, min_rank INT DEFAULT NULL COMMENT 最低录取位次, plan_count INT DEFAULT NULL COMMENT 计划招生人数, UNIQUE KEY uk_school_major_year (school_name, major_name, province, year) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT录取数据表;录取数据表一定要把 (school_name, major_name, province, year) 做成联合唯一索引因为爬虫重复跑的时候同一批数据可能被插入两次。推荐查询时无论按分数段过滤还是按位次区间过滤都依赖 min_score 和 min_rank 这两个字段建议单独再加索引。位次表结构类似核心字段是 score、rank、province、year一分一段的数据按年份分表存储也行数据量不大单表够用。2.4 项目启动流程四个 bat 脚本背后的部署逻辑拿到源码后不要急着看代码先把运行链路摸清楚。spider 目录下的安装.bat 负责创建 Python 虚拟环境并安装 Scrapy 依赖运行.bat 负责启动爬虫任务并写入数据库。springboot 工程里admin 和 front 目录下各有 1-install.bat、2-run.bat、3-build.bat分别是安装 npm 依赖、启动开发服务器、打包生产构建。后端工程直接用 Maven 的 spring-boot:run 启动或者打包成 jar 放到 Tomcat 7 的 webapps 下。这个设计其实很聪明所有环境命令都固化成脚本答辩演示时双击就能跑不需要现场敲一堆可能出错的命令。我自己带项目时发现学生最容易翻车的不是代码逻辑而是现场环境不对导致服务起不来。把安装、运行、构建三个阶段拆成独立脚本出问题时能快速定位是依赖没装好还是端口被占用。3. 数据采集层实现Scrapy 定向抓取录取数据与入库清洗3.1 爬虫工程结构独立 spider 模块与后端解耦爬虫工程放在整个项目的 spider 目录下和 SpringBoot 后端完全解耦这是刻意的。爬虫的依赖是 Python 生态后端的依赖是 Maven 生态硬塞到一起只会让环境配置变成灾难。Scrapy 的工程结构很清晰scrapy.cfg 是工程配置文件items.py 定义抓取字段pipelines.py 做数据清洗和入库settings.py 控制请求频率和爬取策略spiders 目录放具体的爬虫代码。spider/ ├── scrapy.cfg ├── requirements.txt ├── items.py ├── pipelines.py ├── settings.py └── spiders/ └── admission_spider.py安装.bat 做的事情就是在 spider 目录下执行 pip install -r requirements.txt把 Scrapy 和 pymysql 装好。运行.bat 执行 scrapy crawl admission 启动爬虫。爬虫跑完后数据直接进入 MySQL后端 SpringBoot 不需要关心数据从哪来只管查询和展示。这个解耦带来的好处是爬虫挂了不影响系统使用考试院数据更新了重跑一遍爬虫就能刷新数据。3.2 页面解析抓取院校录取分数线的核心代码爬虫的目标是公开的高考资讯网站上各高校历年录取数据。页面通常是一个列表页每行包含院校名称、专业名称、年份、科类、最低分、最低位次、计划数。用 Scrapy 的 XPath 选择器定位表格行逐一抽取字段。这里有一个血泪经验页面结构经常微调选择器不要写得太死先抓整行再做字段切割宁可多抓再清洗也不要因为一个字段解析失败丢掉整条数据。import scrapy from spider.items import AdmissionItem class AdmissionSpider(scrapy.Spider): name admission allowed_domains [目标站点域名] def start_requests(self): # 按年份和省份组合列表页每年志愿填报季前重跑一次 base_url https://目标站点/历年录取?year{}province{} for year in range(2020, 2024): for province in [北京, 山东, 河南]: yield scrapy.Request( urlbase_url.format(year, province), callbackself.parse, meta{year: year, province: province} ) def parse(self, response): rows response.xpath(//table/tbody/tr) for row in rows: item AdmissionItem() item[year] response.meta[year] item[province] response.meta[province] item[school_name] row.xpath(./td[1]/text()).get() item[major_name] row.xpath(./td[2]/text()).get() item[min_score] row.xpath(./td[5]/text()).get() item[min_rank] row.xpath(./td[6]/text()).get() item[plan_count] row.xpath(./td[7]/text()).get() # 某列为空时整条丢弃避免半截数据污染推荐结果 if item[school_name] and item[min_score]: yield itemstart_requests 里的年份和省份列表要根据实际需要调整爬全量数据会很慢毕设场景下抓三到五个省份、近四年的数据足够支撑演示。parse 里对空值做了第一层过滤院校名称和最低分必须存在否则直接丢弃。位次字段如果页面没公布先留空后续在管道里处理不要在这里就把记录删掉。3.3 数据清洗与入库管道类型转换、去重与空值兜底抓到的原始数据不能直接入库页面上的文本常有换行符、全角空格、“-”占位符这些脏数据。pipelines.py 负责统一的清洗逻辑字符串字段去掉首尾空白数值字段先判断是否是纯数字不是就转成 None。清洗完后再做去重用第 2 章建的唯一索引做 INSERT IGNORE重复数据自然被丢弃不会报错中断。import pymysql class MysqlPipeline: def open_spider(self, spider): self.conn pymysql.connect( host127.0.0.1, userroot, password123456, databasegaokao, charsetutf8mb4 ) def process_item(self, item, spider): data dict(item) # 数值字段兜底页面可能返回 -- 或空字符串 data[min_score] self._to_int(data.get(min_score)) data[min_rank] self._to_int(data.get(min_rank)) data[plan_count] self._to_int(data.get(plan_count)) sql INSERT IGNORE INTO t_admission (school_name, major_name, province, year, min_score, min_rank, plan_count) VALUES (%s, %s, %s, %s, %s, %s, %s) cursor self.conn.cursor() cursor.execute(sql, ( data[school_name], data[major_name], data[province], data[year], data[min_score], data[min_rank], data[plan_count] )) self.conn.commit() return item_to_int 是我自定义的转换函数核心逻辑是尝试 int(value)捕获到 ValueError 就返回 None字符串里的逗号要先去掉。设置里 DOWNLOAD_DELAY 2.0 控制请求间隔避免对目标站点造成压力这是基本的数据采集素养。建议在 settings.py 里把 ROBOTSTXT_OBEY 设为 True尊重目标站点的抓取规则。3.4 增量更新策略按批次覆盖不如按年份重跑这个项目的数据更新逻辑不复杂每年高考出分后重跑一次爬虫。运行.bat 里用 scrapy crawl admission 重新跑全量INSERT IGNORE 保证已有数据不会重复新增数据自然补进来。如果只想更新某一年可以在启动命令后面加 -a year2024 参数在 start_requests 里读取这个参数做过滤。注意爬虫抓到的数据只用于教学项目和非商业场景入库后要在系统页面显著位置标注“数据来源公开高考录取信息”不要拿它做任何付费服务。4. 推荐引擎核心实现位次换算、MBTI 测评与知识图谱关联推荐4.1 位次换算为什么用位次代替分数线推荐系统最容易犯的错误是直接用当年分数对比往年分数这完全是玄学。每年试题难度不同、批次线不同同样的 560 分在不同年份含金量完全不同。位次就稳定得多它代表考生在全省的排名是相对量而不是绝对量。系统里位次换算的逻辑是考生输入分数和省份后先查该省当年的一分一段表把分数映射成位次再用这个位次去匹配往年录取数据。核心代码在 SpringBoot 的 Service 层逻辑很直白根据分数和省份查 t_rank 表拿到位次然后调用推荐查询。位次查不到的情况也要处理比如考生分数超过一本线很多一分一段表可能没有覆盖此时取该分数段的最大位次作为兜底。public Integer getRankByScore(Integer score, String province, String year) { RankRecord record rankMapper.selectByScore(province, year, score); if (record null) { // 分数高于表内最高分段时取当年该省份累计人数作为位次 return rankMapper.selectMaxRank(province, year); } return record.getRank(); }4.2 冲稳保梯度推荐位次比例区间与 SQL 筛选推荐的核心不是算出一个学校而是给出一组梯度志愿。冲稳保的划分依据是历史录取位次与考生位次的比例关系如果某校某专业近三年最低位次都明显高于考生位次说明录取概率高放在“保”档如果学校录取位次略低于考生位次有冲刺空间放在“冲”档。梯度位次区间录取概率参考冲考生位次 × 0.85 ~ 1.040% ~ 60%稳考生位次 × 1.0 ~ 1.1560% ~ 85%保考生位次 × 1.15 ~ 1.485% 以上位次数值越小代表排名越靠前所以冲的区间是乘以小于 1 的系数稳和保是乘以大于 1 的系数。SQL 层直接用 BETWEEN 做区间过滤配合第 2 章的联合索引查询效率完全够用。SELECT s.school_name, m.major_name, a.min_score, a.min_rank, a.year FROM t_admission a JOIN t_school s ON a.school_id s.id JOIN t_major m ON a.major_id m.id WHERE a.province #{province} AND a.year #{year} AND a.min_rank BETWEEN #{rank * 1.0} AND #{rank * 1.15} ORDER BY a.min_rank DESC LIMIT 20三段查询分别对应冲稳保每个梯度取前 20 条前端按梯度分组展示。系数可以根据省份录取规则微调比如高考人数特别多的省份同分人数多区间适当放大反之收紧。这里不需要复杂的机器学习模型规则清晰、可解释性强才是毕设答辩时能讲明白的东西。4.3 MBTI 职业性格测试量表计分与专业方向映射MBTI 测试是兴趣维度的数据来源。系统里把它拆成四个维度精力倾向 E/I、信息获取 S/N、决策方式 T/F、生活方式 J/P每个维度准备几道选择题考生答题后按选项累加得分每个维度得分高的字母作为该维度结果最后拼成一个四字母类型比如 INTJ、ESFP。题目表结构很简单question_id、dimension、option_a、option_b 四个字段。# 简化计分逻辑每题两个选项分别对应一个维度字母 dimension_scores {E: 0, I: 0, S: 0, N: 0, T: 0, F: 0, J: 0, P: 0} for answer in user_answers: dimension_scores[answer] 1 result for pair in [(E, I), (S, N), (T, F), (J, P)]: high pair[0] if dimension_scores[pair[0]] dimension_scores[pair[1]] else pair[1] result high得到四字母类型后查专业方向映射表推荐感兴趣的专业类别。这个映射表是静态配置INTJ 建议计算机类、数据科学方向ENFP 建议市场营销、新闻传播方向ISFJ 建议教育、护理方向ESTJ 建议工商管理、金融方向。映射关系建议放在数据库表而不是写死在代码里管理员端可以动态调整。推荐结果展示时把 MBTI 匹配的专业排在前面并标注“兴趣推荐”标签与位次推荐的“分数梯度”标签区分开。4.4 知识图谱与推荐结果融合实体关系的表化实现项目描述里反复提到知识图谱这里要诚实说在这个系统里知识图谱不是 Neo4j 那种图数据库而是把高校、专业、就业前景、智能推荐设置这些实体之间的关系用关系型表表达。院校表关联专业表专业表关联就业方向表就业方向表关联 MBTI 类型表一层一层通过外键串起来。查询时用 JOIN 把这些关系重新拼装成图结构供前端关系图组件展示。推荐引擎的最终输出不是单一规则而是加权融合位次匹配的硬条件占决定权MBTI 兴趣方向作为加分项考生收藏记录和就业前景标签做最后微调。通常的权重设计是位次匹配占 0.6兴趣方向占 0.3收藏与就业占 0.1。这个权重不要写死在 Java 里做成系统配置项放到数据库表里管理员可以调整答辩时能说清楚这个参数的意义和调整依据。5. 高频故障排查数据抓取为空、中文乱码与部署启动的常见翻车点5.1 爬虫抓回的数据全是空值现象爬虫正常运行控制台没有报错日志显示抓了几百条数据但 MySQL 里查出来 school_name 和 min_score 全是 NULL。原因目标页面改版了。最常见的是表格结构调整td 的列数变了原来的 ./td[1] 从院校名称变成了序号。还有一种坑是请求返回了 200但内容其实是反爬拦截页面页面上根本没有表格XPath 自然什么也匹配不到。解决先在 Scrapy shell 里单独请求一个目标 URL打印 response.text 的前 500 个字符肉眼看页面结构是否正常。如果返回的是验证页面就需要调整请求头里的 User-Agent、Cookie并增加下载延迟如果页面结构变了重新核对 XPath 路径尽量用 class 选择器而不是数字下标定位列抗改版能力强很多。5.2 中文乱码与数字格式异常现象数据库里中文变成“???”或者类似乱码min_score 字段存进去的是字符串排序和区间查询结果完全不对。原因MySQL 5.7 建库时默认字符集可能是 latin1插入中文直接乱码另一个原因Scrapy 抓到的页面如果是 GBK 编码item 里的字符串没做统一转换写库时编码不一致。数值格式异常则是管道里没有做类型转换页面上的“570.0”“5,780”直接当字符串入库。解决建库建表时显式指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci数据库连接串加上useUnicodetruecharacterEncodingutf8。Scrapy 侧对页面编码做强制处理response.encoding 按实际页面编码手动指定。数值字段在 pipeline 里统一走 _to_int 转换先把逗号去掉再转 int转失败就存 NULL。这一步最容易翻车当时我在这上面卡了整整一下午。5.3 npm install 卡死与 node-sass 编译失败现象前端 admin 或 front 目录执行 1-install.bat依赖装到一半卡住或者直接报 node-sass 编译 error后面怎么重试都没用。原因node-sass 是 C 扩展需要本地编译Node 版本太高时和旧版 node-sass 不兼容经常直接在 install 阶段挂掉。另一个常见原因是 npm 官方源在国内网络环境下不稳定安装到一半连接超时。解决先用 nvm 把 Node 切到 LTS 版本再执行 install.bat如果 node-sass 还是报错把它替换成 sass 或者 dart-sass代码里的引入语句兼容。npm 源换成国内镜像在项目根目录建 .npmrc 文件写 registry。记住install、run、build 三个脚本要按顺序执行不要跳过 install 直接 run。5.4 MySQL 连接失败导致后端启动报错现象SpringBoot 启动时报Access denied for user或Communications link failure前端登录页一直转圈。原因数据库连接串里的账号密码和本地 MySQL 不一致是大多数原因。另一个隐蔽的坑是驱动版本与数据库版本不匹配项目环境是 MySQL 5.7如果用新版连接驱动加密协议认证失败报错信息非常难懂。解决确认 application.yml 里的 spring.datasource 配置与本地数据库一致密码不要有特殊字符。驱动版本固定用与 MySQL 5.7 匹配的 5.1.4x 系列连接串里加serverTimezoneAsia/Shanghai避免时区导致连接报错。本地数据库先建好库和用户再用 Navicat 测通连接再启动后端不要凭感觉跳过环境检查。5.5 端口占用导致服务起不来现象双击 2-run.bat 后控制台报Port 8080 was already in use前端页面访问白屏。原因上次启动的 Java 进程没有完全关闭或者电脑上其他服务占了 8080 端口。这个排查起来最容易但也是最容易忽视的我每次带项目都要提醒学生先查端口再重启。解决Windows 下执行netstat -ano | findstr 8080看最后一列的 PID再用taskkill /PID 对应PID /F杀进程。如果 8080 被系统服务占用不想动直接改 application.yml 里的 server.port 换一个端口前端工程的代理配置同步改掉。启动完 SpringBoot 后用curl http://localhost:8080/api/health确认接口可用再打开前端页面不要直接开浏览器看白屏就以为代码错了。6. 方案验收与答辩演示从测试用例到演示路径设计6.1 功能验收清单答辩前按模块逐项自测每一项都过一遍再进答辩现场。模块验收动作预期结果爬虫采集运行 spider/运行.bat查看采集日志和数据库新增记录含院校、专业、分数、位次、年份无乱码MBTI 测试按一套固定答案答题提交生成四字母类型推荐专业方向匹配映射表位次推荐输入分数 570、省份、年份返回冲稳保三档每档按录取概率排序模拟填报添加 3 个志愿并提交生成志愿表状态为“已填报”支持修改删除管理员端用管理员账号进入后台编辑一条院校数据保存成功考生端立即可见6.2 答辩演示路径演示要按“数据从哪来—数据怎么用—结果怎么展示”的顺序走。第一步打开 Navicat 展示 t_admission 表按年份和省份过滤证明数据量大且结构化。第二步现场跑一次 MBTI 测试展示答题到出结果的闭环。第三步输入一个预置好的艺考分数展示冲稳保三档推荐列表点开一个院校能看到历年位次趋势。第四步进入模拟填报把推荐结果加入志愿表生成最终志愿清单。最后回到管理员端展示后台对院校和专业的维护操作。演示前把数据预置好一个测试账号、一套 MBTI 答案、一个能出三档结果的分数。现场不要现敲分数万一选了一个推荐结果为空的分段整个演示就冷场了。6.3 评委高频提问与应答方向推荐系统的推荐依据是什么答先说位次换算的可靠性再说 MBTI 加权的逻辑强调规则可解释。爬虫会不会有风险答只爬公开录取数据、遵守 robots、限制频率、注明来源用于教学非商业场景。知识图谱体现在哪答实体关系通过表结构建模前端关系图可视化展示推荐时做多维度关联。冲稳保系数怎么定的答参考志愿填报经验值做成配置项可调答辩时补充历史数据验证逻辑。答辩前我习惯强制走一遍完整流程清端口、重启 MySQL、按顺序跑安装和运行脚本、开着抓包工具看接口返回。这套流程看着笨但它救过我至少三次现场翻车。从那以后我每次做演示都只信脚本不信感觉环境问题提前暴露比现场暴露好一万倍。希望帮到你顺着这个方案把系统跑起来再把自己的理解加进去答辩就不会虚。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站