从爬虫到推荐引擎:热门图书推荐系统构建与实践

发布时间:2026/10/18 9:06:09
从爬虫到推荐引擎:热门图书推荐系统构建与实践 1. 毕设选题为什么“爬虫推荐系统”是性价比最高的组合1.1 这个项目到底解决什么问题每年到了毕设季最纠结的事就是选什么题。游戏开发门槛高、纯管理系统太水、人工智能方向论文难写——很多同学在选题阶段就卡住了。“基于网络爬虫的热门图书推荐系统”这个题目属于一眼看上去就知道“工作量够、方向热门、技术含量足”的类型。它把大数据方向最常见的两个能力点——数据采集和推荐算法——完整地串在了一起爬虫负责从公开网络渠道抓取热门图书信息推荐系统负责把抓回来的数据变成“猜你喜欢”的个性化结果。对评委来说数据采集有实时性、推荐逻辑有算法性、系统实现有完整性是一个“能讲出东西”的题目。从学习角度看这个题目覆盖了一套完整Java全栈项目必需的所有环节网络请求、数据解析、清洗入库、Spring Boot后端接口、前端展示、算法实现、自动化部署。无论你将来是做后端开发、数据工程师还是算法工程师简历上都能从这个项目里挖出对应的技能点。1.2 技术栈选型背后的逻辑先说结论这个题目用Spring Boot做后端是标配但搭配的选择上可以做文章。很多同学纠结要不要用爬虫框架比如爬虫框架Scrapy对应Python路线但题目明确是用Spring Boot体系那就用Java生态。Java生态做爬虫有几个选择——基于异步事件驱动的网络请求框架、老牌多线程网络爬虫框架、或者直接上无头浏览器。对毕设项目来说我建议把核心抓取逻辑写成独立的实时抓取服务而不是集成重型框架。原因很简单毕设答辩时你会被问到“每一行核心代码在做什么”自己手写的一套请求加解析逻辑比一键生成的框架脚本更容易讲清楚也更能体现代码能力。推荐算法部分很多同学一听“推荐系统”就觉得要上机器学习。但实际上一个基于“内容相似度 用户行为协同过滤”的混合推荐已经完全满足毕设要求而且效果肉眼可见。你不需要引入庞大的深度学习框架用余弦相似度计算图书标签向量之间的相似度再用用户行为数据做协同过滤热度榜作为兜底策略三个通道结合起来演示效果非常直观。整个项目数据链路大概是定时任务触发爬虫抓取图书信息数据清洗后写入数据库用户浏览和点击行为通过后端接口记录推荐引擎定时离线计算比对结果后写入缓存前端打开推荐页时直接读取。链路清晰符合业界“采集—存储—计算—应用”的四层结构。2. 系统架构与功能模块拆解2.1 整体架构从数据抓取到推荐展示先把整套系统的数据流画出一个基本印象定时任务框架负责按周期调度爬虫任务 → 抓取热门图书列表页和详情页 → 解析品名、作者、出版社、评分、简介、封面图、分类标签等字段 → 通过双重重置策略去重 → 写入主数据库 → 推荐服务读取图书数据和用户行为数据 → 离线任务计算相似度矩阵 → 结果写入缓存 → 用户访问推荐接口时返回个性化结果。有一个点必须提前想清楚数据库的读写压力。图书数据量不算大几千本书足够演示所以不需要上重型分布式存储用MySQL单库就够。但为了体现“大数据”的味道可以把爬虫采集接口、数据处理接口、推荐结果接口拆分加一个消息队列未必更实际的做法是加一个缓存层把推荐结果和热门榜单缓存起来。缓存命中后接口响应时间压缩到几十毫秒以内演示性能时是个加分项。2.2 功能模块清单后台管理、用户端、推荐引擎按角色划分整个系统拆成三类功能模块普通用户端注册登录、图书浏览、图书详情、搜索、收藏、评分、个人推荐页。管理员端图书管理、用户管理、推荐位配置、爬虫任务状态监控、数据统计看板。后台服务端爬虫调度模块、数据清洗模块、推荐计算模块、日志与监控模块。用户端的核心页面我建议做成三个首页热门榜、图书详情页、推荐页。首页热门榜直接调推荐服务里的“热度排序”接口可以没有任何个性化逻辑就是榜单。图书详情页展示爬虫抓回来的完整字段同时记录当前用户的浏览行为并上报。推荐页是重头戏上面分区域展示“根据你的浏览记录推荐”“和你口味相似的人也在看”“编辑精选热门书单”三个板块分别对应不同的算法通道。管理员端除基本信息管理外亮点功能是爬虫监控页。用定时任务框架自带的调度监控能力把最近一次爬虫执行时间、抓取条数、失败条数和耗时展示出来。这个功能做出来之后答辩时候评委问“运行状态如何跟踪”直接打开页面就能讲。2.3 数据库设计要点数据库设计直接决定后续代码能写到什么程度。核心表我建议规划七张用户表包含账号、昵称、密码加密存储、角色类型。图书表包含标题、作者、出版社、出版日期、ISBN、评分、评分人数、简介、封面URL、分类ID、抓取来源、抓取时间。分类表图书分类例如文学、计算机、经济管理等。用户行为表包含用户ID、图书ID、行为类型浏览/收藏/评分、行为时间。收藏表用户收藏关系。评分表用户对图书的评分记录。推荐结果表或缓存为用户ID和推荐图书ID列表的映射。需要注意的一个坑图书表的唯一索引不能只建在书名上要建在“书名作者”的联合唯一索引上否则不同出版社的同名书会被误判为重复数据。另外爬虫抓下来的简介可能包含大量HTML标签和特殊字符入库前必须清洗掉一方面保证页面展示效果另一方面避免XSS之类的安全隐患。3. 热门图书数据采集实战爬虫设计与反爬应对3.1 数据源选择与页面结构分析爬虫的第一步选数据源。对图书推荐系统来说数据源要有几个特点图书信息公开可见、数据结构规整、访问频率限制合理。适合做毕设演示的数据源有两类一类是电子图书平台的热门榜单页另一类是读书社区的分类图书列表。不建议选择需要登录才能看到内容的站点也不建议选择页面结构过于动态的站点。页面是服务端渲染的最好因为直接用HTTP请求就能拿到完整HTML如果是前端渲染的复杂页面就得动用无头浏览器调试成本会突然上升。确定目标站点后先用浏览器开发者工具分析页面结构。按F12打开检查工具找到书名、作者、出版社、评分、简介这些元素对应的HTML结构。实际操作中一个常见做法是先用浏览器自带的“复制为XPath”功能抓取元素路径再在代码里基于XPath或CSS选择器进行二次精调。会正则表达式就会发现清洗抓取结果时这部分简直是必需品比如把“作者张三”清洗成“张三”把评分“8.7分”清洗成“8.7”。3.2 爬虫核心代码实现以Java为基础做一个结构清晰的定时爬虫程序。抓取的思路是分两层先抓列表页从列表页解析出每本书的详情页URL再遍历详情页URL抓取完整字段。这样设计的好处是解耦列表页结构发生变化时只需要调整列表页解析器详情页变化时同理。下面是一段基于轻量级HTTP组件的抓取骨架重点是请求和解析分离便于后续扩展public class BookCrawler { private static final String USER_AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36; private static final int TIMEOUT 5000; public String fetchPage(String url) { CloseableHttpClient client HttpClients.createDefault(); HttpGet request new HttpGet(url); request.setHeader(User-Agent, USER_AGENT); request.setHeader(Accept-Language, zh-CN,zh;q0.9); try (CloseableHttpResponse response client.execute(request)) { return EntityUtils.toString(response.getEntity(), UTF-8); } catch (Exception e) { System.err.println(抓取失败: url - e.getMessage()); return null; } } }请求头里必须带上模拟浏览器的浏览器标识User-Agent这是最基础的反反爬手段。其次考虑到某些站点会校验Referer可以在请求头中加上目标网站首页地址。把两者都配好之后大部分基础场景的抓取就没有问题了。拿到HTML之后解析阶段用Jsoup最顺手。它的选择器语法和前端CSS选择器几乎一致和代码的适配度非常高。例如解析图书详情页时可以这样写Document doc Jsoup.parse(html); String title doc.select(h1.book-title).first().text(); String author doc.select(p.book-author).first().text().replace(作者, ); String rating doc.select(span.rating-score).first().text();每抓完一批数据后不要急着入库先统一放到内存的待清洗列表里批量处理完再做校验与持久化。批量操作能显著减少数据库连接开销也能在爬虫部分之外展示你对数据库性能意识的把握。3.3 数据去重与清洗这里专门展开说一下数据清洗它是爬虫里最容易被低估的部分。直接把页面文本塞进数据库会让后续推荐算法的效果大打折扣。实际清洗流程我按优先级分成四步去除HTML标签与转义字符把字符串里的换行、空格压缩为单空格。字段格式化把评分、评分人数、价格等字符串转为统一类型。缺失字段处理封面图确实抓不到的给一个默认封面路径不把脏数据带进推荐逻辑。合法性校验把内容为空的记录、URL明显异常的记录直接丢弃。以JPA为例做批量保存时可以实现基于实体的哈希值比较进行增量更新Override Transactional public void saveBooks(ListBook books) { for (Book book : books) { String fingerprint generateFingerprint(book); if (bookRepository.existsByFingerprint(fingerprint)) { continue; } book.setFingerprint(fingerprint); bookRepository.save(book); } } private String generateFingerprint(Book book) { return DigestUtils.md5Hex(book.getTitle() - book.getAuthor()); }上面这段是双保险数据库层建立“书名作者”唯一索引业务层再做指纹去重双保险下几乎不会有重复数据。3.4 反爬策略与合规提醒爬虫部分一定要掌握分寸。做毕设的爬虫和商业爬虫不同目标是采集足够演示的数据量比如一两千本图书而不是把整站数据搬走。因此代码里必须主动控制抓取频率每次请求之间至少间隔一到两秒并且做好异常退出后的断点保护。合规这块必须明确三点第一只抓取公开信息不碰账号体系数据第二遵守目标网站的robots协议不抓取明确禁止的内容第三采集的数据仅用于学习研究不用于商业用途。答辩时如果评委问到数据来源和合规性如实说明这三条原则比含糊其辞要好得多。再补充一个监控技巧把每次抓取的入参、耗时、成功条数和失败原因输出成结构化日志。在毕设项目里日志就是老师抽查“你是否真的跑通过整个流程”的最强证据。4. 推荐引擎从零搭建不止是“热门榜”4.1 推荐系统的基本框架推荐引擎部分是整个项目的灵魂。如果只是把图书按评分排个序展示出来那不叫推荐系统那叫榜单。真正的推荐引擎要回答的问题有三个这个用户过去对什么感兴趣、有哪些图书和他感兴趣的相似、和他口味相似的用户还喜欢什么。因此我采用了“三通道混合推荐”的策略框架内容推荐通道基于图书分类、标签、简介文本的相似度计算。协同过滤通道基于用户行为的相似用户推荐。热度兜底通道评分人数加权后的热门榜解决新用户无行为数据的问题。三个通道的结果按权重合并最终生成每个用户的推荐列表。这个框架层次分明每一层都有独立的算法逻辑答辩时能讲的内容就多了。4.2 基于内容的推荐实现基于内容的推荐核心是把每一本书的“画像”算出来。画像包括两类特征一类是离散特征包括分类ID和标签列表另一类是文本特征包括标题和简介的关键词。先看分类匹配。如果用户看过三本书分别属于计算机、文学、文学那用户的分类偏好权重就是计算机三分之一、文学三分之二。候选图书按分类权重得分同分类的优先推荐public double calculateCategoryScore(UserProfile profile, Book book) { Double weight profile.getCategoryWeights().get(book.getCategoryId()); return weight null ? 0.0 : weight; }再看文本相似度。把图书简介做中文分词用词频向量代表一本书再用余弦相似度计算两本书的距离。这一步引入分词工具可以把“分布式系统”切成“分布式/系统”比按单字切分要准确得多。候选集不能太大否则计算耗时不可控实际项目里可以先按分类筛选出候选再对候选集逐一计算相似度把计算量限制在百级别。4.3 基于用户的协同过滤实现协同过滤的思路更像“人以群分”。实现分两步先找相似用户再集合相似用户喜欢的图书排序推荐。找相似用户的经典方式是“基于物品的协同过滤”的一种简化。实际操作中先从行为数据里取当前用户最近一段时间内的行为记录再找到与当前用户有至少一条共同行为交集的其他用户。为了方便这两步都可以通过SQL实现并不需要引入额外的大数据引擎。相似度计算仍然用余弦公式把用户行为看成向量。我给出一个基础实现public double cosineSimilarity(MapLong, Double userVector1, MapLong, Double userVector2) { SetLong commonItems new HashSet(userVector1.keySet()); commonItems.retainAll(userVector2.keySet()); if (commonItems.isEmpty()) { return 0.0; } double dotProduct 0.0, norm1 0.0, norm2 0.0; for (Long itemId : commonItems) { dotProduct userVector1.get(itemId) * userVector2.get(itemId); } for (Double value : userVector1.values()) { norm1 value * value; } for (Double value : userVector2.values()) { norm2 value * value; } return norm1 0.0 || norm2 0.0 ? 0.0 : dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); }这里用户向量的每一项可以把行为类型做一个映射浏览记0.5分、收藏记1.5分、评分记2分。权重不同相似度的区分度就出来了。如果所有行为都是1分最后算下来容易变成“看过同一本书就是双胞胎兄弟”的低区分度结果。找到TopN相似用户后把相似用户们有过正向行为的图书收集起来剔除当前用户已经看过的再按“相似度 × 行为权重”累加得分取前20本就是协同过滤推荐结果。4.4 冷启动问题的处理每个推荐系统都绕不开冷启动。新注册用户没有任何行为数据协同过滤和内容推荐都失效这时候热度榜就是唯一的推荐通道。为了让这个通道更显专业不能简单按评分排序而是加入评分人数做加权public double hotScore(double rating, long ratingCount, long minCount) { return rating * Math.log10(ratingCount minCount); }这个公式的思路一本书9.5分但只有两个人评分权重必然低于一本8.5分但有一万人评分的书因为后者经过的大众检验更充分。对数函数log10让评分人数的边际效应递减符合实际直觉。冷启动的另一个应对策略是“热门分类加权”。新用户首次登录时给热门分类里的高评分图书增加展示机会相当于给新用户一个快速可感知的初始推荐流。具体实现是修改推荐列表的排序权重把分类热门度因子乘进去。5. Spring Boot后端快速实现与调试要点5.1 项目分层结构与关键接口后端代码的包结构我建议按职责分成五层从Controller到Repository一条线避免后期类多了看着乱controllerREST接口层负责参数接收与数据装配。service业务逻辑层推荐引擎和爬虫调度都放这里。repository数据访问层继承 JpaRepository。entity数据库实体层。config配置类包括定时任务配置、缓存配置。关键接口从用户视角走一遍注册登录接口用Token做身份校验图书列表接口支持分页和分类筛选详情接口记录行为并返回完整字段收藏接口维护收藏关系推荐接口是核心返回三个板块的数据。推荐接口设计上要注意推荐结果不是每次请求都实时计算而是先查缓存没有命中时触发一次异步计算把结果写回缓存然后返回。这样既保证了首次访问能拿到数据也保证了接口稳定。GetMapping(/api/recommend/{userId}) public Result recommend(PathVariable Long userId) { ListBookVO cached redisTemplate.opsForValue().get(recommend: userId); if (cached ! null) { return Result.success(cached); } ListBookVO result recommendService.generateRecommendation(userId); redisTemplate.opsForValue().set(recommend: userId, result, 10, TimeUnit.MINUTES); return Result.success(result); }5.2 数据同步与缓存设计爬虫抓完数据入库后不能忘记清缓存。如果推荐结果依赖的图书数据已经更新而缓存里还是老版本用户看到的就是过期推荐。最简单的方式是每次爬虫任务成功后调用一个缓存刷新接口把相关推荐结果全部失效。实际上更好的做法是把缓存失效逻辑放在爬虫任务完成时的回调里而不是手动去点。定时任务框架的使用是一个可以展开的小亮点。在Spring Boot里开启定时任务只需要两步启动类加一个开启定时任务注解然后在方法上写周期表达式Scheduled(cron 0 0 2 * * ?) public void crawlTask() { crawlerService.crawlHotBooks(); recommendService.refreshAllRecommendations(); }这段配置表示每天凌晨两点整执行一次爬虫和推荐刷新避开读者访问高峰数据每天保持新鲜。定时任务的调度日志要单独输出一份后面做统计分析和状态监控都要用到。缓存键的命名必须小心我见过很多同学在缓存键里拼用户ID结果ID重复导致数据串了。推荐缓存的键名建议用“recommend:userId”格式同时设置合理的过期时间。另外更新图书数据后被影响用户的缓存不要一次性全清可以先只清除当天有登录行为的活跃用户缓存降低缓存雪崩风险。5.3 远程调试经验毕设交易里常出现“远程调试”的承诺本质是把项目在你本机跑起来验证环境和代码都正常。这件事看着简单实际踩坑点很多。环境版本不一致是头号杀手。最好制作一张依赖版本表明确列出 JDK版本、Maven版本、MySQL版本、Redis版本并提供一个初始化脚本。拿到压缩包后先把数据库初始化脚本跑一遍再配application.yml把数据库连接改成localhost用户名密码最后启动应用。第二个高频问题是端口占用。Spring Boot默认8080端口经常被本地其他进程占住解决办法是改端口或在配置文件里增加随机端口配置。还有一个经验把项目里的上传文件路径和日志路径抽成配置项避免不同操作系统下路径分隔符不兼容导致的报错。第三个问题更隐蔽常见于时区配置。爬虫抓取的数据带时间字段如果数据库连接参数没有指定时区存进去的时间会少八小时这个坑排查起来非常费时间。连接串里明确加上serverTimezoneAsia/Shanghai就能解决。提示拿到项目后第一件事不是看源码是先按文档把环境搭好确保应用能启动、页面能打开、数据能看到。一个能跑通的系统比一百行优雅代码在答辩时更有说服力。6. 毕设答辩讲解心法让评委快速听懂你的项目6.1 演示路径设计答辩环节决定项目印象分。很多同学一上来就开始讲代码细节评委听两分钟就走神了。正确顺序是按“问题定义 → 数据通路 → 算法思路 → 运行效果 → 总结改进”来演示。先用一分钟讲清楚选题背景图书信息爆炸用户找书难需要一个懂用户口味的推荐工具。然后点开爬虫监控页展示最近一次抓取任务的数据量把实时数据亮出来。接着进入推荐页演示新用户登录看到的是热门书单老用户登录后推荐内容随浏览记录变化。最后再点开代码挑数据清洗的方法和推荐算法的余弦相似度计算讲细节。这里建议提前准备一份“脚本”把每一步点击哪里、鼠标悬停在哪个区域、预期出现什么效果都写清楚。演示时最尴尬的就是临时找不到按钮或者等待页面加载时冷场。提前录好一段操作录屏作为备用万一现场网络异常或者环境出问题依然可以播放录屏完成演示。有一个实操细节演示推荐效果时可以提前用测试账号刷几条浏览和评分记录。等评委来了现场登录这个账号推荐页会立即体现个性化差异比口说“我们这个系统有推荐功能”有冲击力得多。6.2 评委常问的高频问题答辩这部分评委通常会从三个方向提问数据哪里来、算法靠不靠谱、系统能不能真正运行。数据来源问题是必问的。回答口径建议是公开网页数据仅用于学习研究请求频率做了限速遵守多国网络爬虫伦理规范。如果评委追问爬取大量数据会不会对目标网站造成压力可以补充说明每秒请求控制在一次以下、每日抓取总量有限额并且做了失败重试和断点续抓。算法问题也是一个高频点。很多同学被问“推荐效果怎么评估”就卡住了。非深度学习的推荐系统同样可以做简单离线评估留下部分用户行为数据作为测试集用推荐结果命中率作为指标。毕设层面可以做一个简化版评估随机抽样一百个用户看推荐列表里用户真正交互过的图书占比能达到5%以上已经算很好的结果。这个评估不必做成复杂的交叉验证但能体现对推荐质量的正确认知。系统运行问题要提前演练。评委有时会故意问“如果某本书的封面图挂了怎么办”“如果数据库连接超时怎么办”对应答法是展示封面的默认占位图方案和全局异常处理器返回的统一错误结构证明你考虑过异常边界。关于讲解时语速和节奏我自己的经验是控制在十分钟之内讲完全程剩下时间留给评委提问。宁可少讲一些边角功能也要把主链路讲透。项目本身功能做得多不如讲得明白评委打分的核心依据是“你到底理解不理解自己做的东西”。6.3 定制扩展方向这个项目给我的感觉是后续扩展空间相当大。如果时间富余可以往三个方向加亮点。第一个方向换一条更复杂的数据源抓取包含多维度图书数据的信息比如把图书的目录、获奖记录、作者简介都纳入画像计算内容推荐的精度会有明显提升论文里也能多一章“多维度图书画像构建”。第二个方向给推荐引擎换一种更现代的召回策略比如将用户行为序列建模成向量再算相似度。这个话题稍微深入一些但网上公开资料多属于能实现、能讲清、能写进论文的安全扩展点。第三个方向把统计看板做得更丰富用图表库画出每日图书抓取量趋势、热门分类分布、用户活跃时段分布。这类可视化工作难度不高但成品非常直观答辩演示时很容易吸引眼球。写在最后几个实操中的经验提醒调试过程里最值得记录的几个心得集中放在这里供做同类项目的同学参考。第一爬虫部分的问题九成是响应结构变化导致的所以解析器要做容错。比如某个字段临时不存在时不要直接抛异常中断整批任务而是记录当前图书的URL跳过继续下一本全部跑完后统一输出失败清单。这样你手里永远有一份完整的执行报告。第二推荐算法的数据准备比算法本身重要。很多同学把时间花在调算法公式上却忽略了行为数据质量。比如用户刷了几百次页面行为表里噪声很多推荐结果自然不准。务必在行为记录入库前做去重和聚合把短时间内的连续浏览合并成一次有效浏览。第三项目交付前留出三天做整体回归测试不要以为能启动就万事大吉。数据库初始化脚本要在干净的机器上跑一遍前端资源路径要确认不带本机绝对路径部署文档里的配置项要和压缩包里的实际配置完全一致。这些细节在远程调试时最容易出问题提前处理好能大大降低沟通成本。一套爬虫加推荐系统的完整项目做下来确实能覆盖很多分布式开发的技术点而更重要的是你能从“写代码”进入“讲方案”的层面。这个能力比项目本身更值钱。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询