Java爬虫题库自动生成系统:从抓取到组卷导出完整实战 简介这是一套面向高校计算机相关专业毕业设计场景的完整项目源码主题为利用爬虫技术实现题库自动生成系统适合正在准备毕设、需要参考前后端分离架构与数据采集实现思路的本科或专科学生。资源包共122个文件约474KB以Java源码为主71个构成后端业务与爬虫处理逻辑另有22个Vue组件与7个JavaScript文件负责前端页面交互并辅以XML、JSON、YAML、properties等配置及少量图片、样式与说明文档整体结构清晰、便于按模块阅读。项目围绕题库采集、解析与生成流程展开包含服务实现、文件控制等核心类可帮助读者理解爬虫调度、数据清洗与前后端联调的完整链路。目前已有286人学习下载适合作为毕设选题参考或二次开发的基础工程。1. 题库自动生成系统从爬虫抓取到组卷导出的完整链路带毕设的同学最近问得最多的一个资源就是这套利用爬虫的题库自动生成系统。它的定位很明确用 Java 写一套后台把散落在网页上的题目抓下来清洗成结构化数据再按题型、难度、知识点自动组卷最后导出成可打印的文档。适合正在做教育类、考试类毕设的本科生也适合想找一个「爬虫 业务系统」完整练手项目的中级开发者。很多人做毕设卡在「数据从哪来」这一步手动录几百道题既费时又容易出错这套东西解决的正是这个痛点——抓取、入库、组卷、导出四段链路一次跑通。下面按我实际拆包的顺序把每个环节的参数和坑讲清楚。2. 环境搭建与项目结构先跑通再改代码2.1 依赖清单与版本对齐拿到压缩包后别急着双击运行先看依赖。这类 Java 项目通常基于 Spring Boot数据库用 MySQL爬虫部分常见的是 Jsoup 或 WebMagic。我一般会先确认三件事JDK 版本、Maven 依赖、数据库脚本。JDK 建议 1.8 或 11Spring Boot 2.x 对这两个版本兼容性最好用 17 以上容易在反射和字节码上翻车。核心依赖大致是这几类依赖作用常见版本spring-boot-starter-web提供 REST 接口2.5.xmybatis-plus简化 CRUD3.4.xjsoupHTML 解析1.14.xmysql-connector-java数据库驱动8.0.xpoi / easypoi导出 Word/Excel4.1.x版本不用追新毕设项目稳定优先。如果 pom 里某个依赖拉不下来先换阿里云镜像别怀疑代码。2.2 数据库初始化与配置修改导入 SQL 脚本是第一步。脚本里一般会建三到五张表题目表、知识点表、试卷表、试卷题目关联表。执行前先建库字符集用 utf8mb4否则题干里的特殊符号会变问号。CREATE DATABASE question_bank DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE question_bank; -- 再执行项目自带的 schema.sql然后改application.yml重点是数据库连接和爬虫目标地址spring: datasource: url: jdbc:mysql://localhost:3306/question_bank?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver crawler: target-url: https://example-edu-site.com/question # 替换成你要抓的页面 page-size: 20 max-page: 50serverTimezone必须写不写会报时区错误这是 MySQL 8 的经典坑。max-page控制抓取深度第一次跑建议设成 3确认流程通了再放大。2.3 启动与首次验证配置改完直接mvn spring-boot:run。启动成功后先访问首页或 Swagger 文档确认接口活着。然后手动触发一次抓取接口观察控制台日志和数据库表。如果题目表有数据进来说明爬虫链路通了如果为空先看日志里有没有 403、超时或解析异常。这一步别跳过很多人直接去点组卷结果组出来是空卷回头查半天。3. 爬虫抓取与题目解析选择器写不对后面全白费3.1 抓取策略与请求头伪装爬虫部分的核心是「怎么把网页上的题目抠出来」。常见做法是用 Jsoup 发 GET 请求拿到 HTML 后用 CSS 选择器定位题干、选项、答案。目标站点一般会有基础反爬所以请求头要带全Connection conn Jsoup.connect(url) .userAgent(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36) .header(Referer, https://example-edu-site.com/) .timeout(10000) .ignoreContentType(true); Document doc conn.get();userAgent不写会被直接拒Referer有些站点会校验带上更稳timeout设 10 秒太短容易误判失败太长会拖慢整体。ignoreContentType在返回非标准 HTML 时有用。3.2 选择器定位与字段映射假设题目列表页每道题在一个div.question-item里题干在.title选项在.option答案在.answer。解析代码大致这样Elements items doc.select(div.question-item); for (Element item : items) { String title item.select(.title).text(); String type item.select(.type).text(); // 单选/多选/判断 ListString options item.select(.option).eachText(); String answer item.select(.answer).text(); // 封装成实体后入库 Question q new Question(title, type, String.join(|, options), answer); questionMapper.insert(q); }选择器是这套系统里最脆弱的一环。目标页面结构一改选择器就失效所以建议把选择器抽到配置文件里别硬编码在 Java 里。String.join(|, options)是把选项拼成一个字段存简单但够用如果要做选项级分析就单独建选项表。3.3 分页与去重分页抓取要处理两个问题页码递增和重复数据。页码一般拼在 URL 上比如?page1。去重我一般用题干哈希对题干做 MD5入库前查一下哈希是否存在存在就跳过。这样即使重复抓同一页也不会产生脏数据。String hash DigestUtils.md5Hex(title.trim()); if (questionMapper.existsByHash(hash)) continue; q.setHash(hash);trim()不能省前后空格不同会导致哈希不同去重就失效了。抓取频率也要控制每抓一页Thread.sleep(500)别把人家站点打挂这也是基本的工程素养。4. 组卷算法与导出从题库到一张能用的卷子4.1 组卷规则设计组卷的本质是「按约束条件从题库里抽题」。约束通常包括总分、题型分布、难度分布、知识点覆盖。最简单的实现是按题型分组每组随机抽 N 道。稍微讲究一点就加难度权重。// 按题型和难度抽题 ListQuestion singles questionMapper.randomByType(单选, 10); ListQuestion multiples questionMapper.randomByType(多选, 5); ListQuestion judges questionMapper.randomByType(判断, 5);randomByType底层用ORDER BY RAND() LIMIT n。数据量小的时候没问题上万条以后RAND()会明显变慢这时候可以改成先查 ID 列表再随机取或者用按 ID 区间抽样的方式。毕设数据量一般不大先用简单方案跑通。4.2 难度与知识点平衡如果题库里每道题都标了难度易/中/难和知识点组卷时可以按比例抽。常见配比是易 40%、中 40%、难 20%。实现上就是分别按难度查再合并。知识点覆盖则是在抽题后做一次校验看覆盖了几个知识点不够就补抽。这块不用做太复杂毕设能体现「有约束地抽题」就够了。4.3 导出 Word 与 PDF导出用 POI 或 EasyPoi 生成 Word格式一般是「题目区 答案区」。题目区只显示题干和选项答案区统一列答案和解析。XWPFDocument doc new XWPFDocument(); for (Question q : paper.getQuestions()) { XWPFParagraph p doc.createParagraph(); p.createRun().setText(q.getIndex() . q.getTitle()); for (String opt : q.getOptions().split(\\|)) { doc.createParagraph().createRun().setText(opt); } } FileOutputStream out new FileOutputStream(paper.docx); doc.write(out); out.close();选项用|分割后逐行写入注意转义。导出路径建议做成可配置别写死在代码里。PDF 导出可以先用 Word 生成再转或者直接上 iText但 iText 中文需要额外字体配置容易踩坑毕设用 Word 就够。5. 避坑与常见问题排查这些坑我替你踩过了5.1 抓取返回空或 403现象请求发出去了但doc是空的或者直接抛异常。原因通常是请求头不全被拦截或者目标页面是 JS 动态渲染的Jsoup 拿不到内容。解决先补全userAgent和Referer如果是动态渲染就得换方案用浏览器内核或者找页面背后的数据接口。先确认页面是服务端渲染还是客户端渲染再决定工具。5.2 中文乱码现象数据库里题干显示成问号或乱码。原因一般是数据库字符集不是 utf8mb4或者连接 URL 没带characterEncodingutf8。解决建库时指定 utf8mb4连接串补上编码参数两边都对齐。已经入库的乱码数据清掉重抓。5.3 组卷抽不出题现象点组卷后生成的卷子是空的或者题目数量不对。原因多半是题型字段对不上——爬虫存进去的是「单选题」组卷查的是「单选」字符串不匹配就查不到。解决统一题型枚举入库前做一次标准化映射别让原始文本直接进库。5.4 导出文件打不开现象导出的 docx 用 Word 打开提示损坏。原因通常是流没关闭或者写入过程中异常中断。解决用 try-with-resources 管理流确保doc.write(out)之后out.close()一定执行。另外别在循环里反复创建同名文件会覆盖。5.5 重复抓取导致数据膨胀现象跑了几次爬虫后题库里同一道题出现好几遍。原因是没有做去重或者去重用的字段不稳定。解决用题干哈希做唯一约束数据库层面加唯一索引双保险。哈希前记得trim()和统一大小写。6. 进阶技巧把爬虫配置化和组卷模板化跑通基础流程后真正让这套系统好用的一步是「配置化」。我一般会把爬虫的选择器、分页规则、字段映射全部抽到一个 JSON 配置里换一个目标站点只改配置不改代码。这样即使毕设答辩时老师让你现场换个页面抓你也能从容应对。{ listSelector: div.question-item, fields: { title: .title, type: .type, options: .option, answer: .answer }, pagination: { urlPattern: https://example-edu-site.com/question?page{page}, startPage: 1, maxPage: 50 } }解析时读这个 JSON用反射或 Map 把字段填进实体。urlPattern里的{page}是占位符循环替换即可。这样一套代码能适配多个结构相似的题库站点扩展性直接上一个台阶。组卷这边可以把试卷结构也模板化一份模板定义题型、数量、分值、难度比例组卷时按模板执行。模板存数据库或 JSON 都行。验证方法很简单——同一份模板连续组十次卷检查每次的题型数量和总分是否一致难度分布是否在允许范围内波动。如果某次抽出来的题明显偏难说明随机逻辑有问题回去看抽样是不是真随机。还有一个实用技巧是给题目加「使用次数」字段。每次组卷被抽中的题次数加一。下次组卷时优先抽次数少的题避免同一道题反复出现。这个逻辑不复杂但能让生成的卷子看起来更专业。从那以后我每次拿到这类爬虫加业务系统的项目都会先把选择器和组卷规则抽成配置再动手写业务代码。因为页面会变、需求会变唯一不变的是「别把易变的东西写死」。希望帮到你。本文还有配套的精品资源点击获取