
最近赶上毕业设计高峰好多学弟学妹来问我同一个题目django豆瓣图书推荐系统。这个题目看着简单但实际做起来涉及的东西不少——Django框架的完整开发流程、推荐算法从零落地、后台管理、部署上线每一块都能让新手卡住好几天。我前前后后帮人改过好几套这个题目的源码也自己从空白项目一步步搭过今天干脆把整条线路整理出来从项目结构到算法实现从数据库设计到常见坑位一次说透。这篇文章适合正在做图书推荐类毕设的人也适合想拿Django练手、又不想只做个简单CRUD的同学参考。1. 项目整体设计与技术选型思路1.1 为什么是 Django 而不是其他框架选型这件事在毕设里经常被忽略但其实非常关键。豆瓣图书推荐系统本质上是一个“数据密集型Web应用”用户、图书、评分、收藏、评论表与表之间关系复杂再加上推荐算法要跑数据后台还需要管理大量图书信息。拿Flask这类微框架来做路由和ORM都要自己配做到后期代码会越写越散维护成本高。Django的哲学是“batteries included”认证、Admin后台、ORM、模板引擎开箱即用最适合这种业务模型清晰、需要快速交付的项目。更重要的是Django的ORM能让我在写推荐算法的时候省下大量功夫。比如统计某本书的评分人数、计算用户之间的共同评分项一条查询就能搞定不用手写SQL拼来拼去。对于毕设源码来说Django这种“自带后台”的特性也是加分项——答辩演示的时候你可以直接通过Admin往数据库里加图书、调权重评委能看到系统的完整度。另外一个现实原因是网上能参考的同类项目源码Django版本占了绝大多数。遇到问题搜一下基本都是Django的解决方案。对于时间紧张的毕设党来说“踩坑之后能搜到答案”比“技术栈更酷”重要得多。1.2 系统功能模块与角色划分这个系统我建议按三类角色来设计普通用户、管理员、未登录访客。每类角色看到的内容和能执行的操作都不一样这也是答辩时评委比较看重的地方——说明你考虑了权限设计和用户场景。未登录访客可以浏览图书列表、查看图书详情、按分类筛选但一旦想评分、收藏、看推荐结果就必须登录。普通用户登录后可以评分、收藏、提交评论、查看“猜你喜欢”和“相似图书”推荐还能在个人中心看到自己的评分历史和收藏列表。管理员则通过Django自带的Admin后台管理图书数据、用户数据也可以查看推荐算法的运行状态。功能模块对应到页面就是首页含推荐位、图书列表页含分类筛选和搜索、图书详情页含评分和评论、用户登录注册页、个人中心页。这套结构做下来该有的功能都有又不会像网上有些版本那样堆砌一堆用不上的模块代码量看起来很大但实际逻辑乱成一团。1.3 技术栈与版本选择版本选择是个容易踩坑的点。Django 2.x和4.x的写法差异不小网上很多老教程用的是2.x你装个最新版4.x照着敲第一行urls配置就报错。我的建议是用Django 4.2 LTS版本这个版本够稳定到2026年4月之前都有安全更新而且支持Python 3.10以上的环境。数据库方面本地开发用SQLite完全够用等要部署上线了再切MySQL。这两个切换在Django里就是改一下settings.py的DATABASES配置ORM的代码不用动这也是我推荐用Django做毕设的另一个原因——数据层被ORM抽象掉了后面扩展的余地很大。前端部分用Django自带的模板系统配上Bootstrap做样式就够了。不用上Vue或React毕设的核心是推荐系统和后端业务逻辑前端够用、好看、不出错就行。非要上前后端分离工作量翻倍不说反而把推荐算法的亮点稀释了。2. 数据库设计与推荐算法核心2.1 图书领域的数据模型设计数据模型是整个系统的地基。图书推荐系统至少要建四张核心表用户表、图书表、评分表、收藏表。Django的auth_user表可以直接拿来当用户表不建议自己重写User模型除非你一开始就确定要加手机号等自定义字段否则徒增工作量。图书表是核心字段至少要包括书名、作者、出版社、出版日期、ISBN、封面图URL、分类、简介、豆瓣评分、评分人数。这里要注意一个细节——豆瓣评分是图书的静态属性叫“豆瓣评分”容易和用户行为产生的“平均评分”混淆。我建议数据库里这个字段命名用douban_rating或者rating然后用户打分的数据单独存到评分表里逻辑才清晰。评分表是推荐算法的主要数据来源字段就三个关联用户外键、图书外键、评分值。注意加上UniqueConstraint保证同一个用户对同一本书只能有一条评分记录不然算法算相似度的时候数据会出问题。收藏表类似记录用户和图书的收藏关系加上收藏时间。2.2 推荐算法的三种主流方案怎么选图书推荐系统的算法业界主流就三类基于内容的推荐、基于用户的协同过滤、基于物品的协同过滤。基于内容的推荐最容易理解就是“找和你喜欢的书相似的书”。这里“相似”可以按分类、作者、标签等属性算。实现简单但有个致命缺点推荐结果永远在同一个小圈子里打转用户看的都是同类型书没有惊喜感。基于用户的协同过滤是“找到和你口味相似的用户推荐他们喜欢而你没看过的书”。这种思路最经典但有个在毕设阶段很头疼的问题——需要维护一个“用户-物品”评分矩阵每当新用户注册、新评分产生矩阵都要更新。而且当用户量上来后两两计算用户相似度的计算量是O(n²)性能会崩。基于物品的协同过滤是亚马逊最早用的思路——“买了这本书的人还买了那些书”。它先通过所有用户的历史行为算出图书之间的相似度矩阵然后针对某个用户把他评分高的图书的相似图书推荐给他。这个方案的优点在于图书之间的相似关系相对稳定不用每次请求都重新算可以定时计算并缓存结果性能友好得多。我不建议在毕设里只做一种算法。评委很容易问“你这个推荐为什么准”或者“有什么缺点”。比较稳妥的做法是以基于物品的协同过滤为主算法辅以基于内容的热门兜底再在代码里预留一个策略接口方便切换和对比。2.3 基于物品的协同过滤数学细节与代码实现基于物品的协同过滤核心分三步构建用户评分矩阵、计算物品相似度、生成推荐列表。第一步将评分数据整理成“用户-物品”矩阵行是用户列是图书交叉点是评分。这个矩阵在代码里就是一个嵌套字典或者一个Pandas DataFrame。数据量不大的时候直接用Python字典硬算就够了没必要上Spark。第二步是计算物品相似度。常见的有余弦相似度和皮尔逊相关系数。余弦相似度的公式是两个物品的评分向量之间夹角的余弦值。如果两个向量分别是[5, 4, 0]和[5, 0, 0]它们的余弦相似度就算出来了。实际使用中我建议把评分做“去均值化”处理也就是每个评分减去该用户的平均评分这样能把用户打分偏严或偏松的尺度差异消除掉效果会好很多。第三步是生成推荐。对用户U先找出他评分最高的N本图书然后遍历这些图书的相似图书列表把不在用户历史行为里的图书收集起来按照“相似度×用户评分”加权求和的得分排序取Top-K推荐给用户。我用Python写了一个简化版的实现方式可以直接跑def item_based_recommend(user_id, top_k10): # 1. 构建用户评分字典: {user_id: {book_id: score}} ratings Rating.objects.all() user_dict {} for r in ratings: user_dict.setdefault(r.user_id, {})[r.book_id] r.score # 只对当前用户做推荐 if user_id not in user_dict: return [] target_user_ratings user_dict[user_id] # 2. 找出用户评分最高的5本书 top_books sorted(target_user_ratings.items(), keylambda x: x[1], reverseTrue)[:5] # 3. 遍历这5本书累计相似图书得分 score_dict {} for book_id, score in top_books: # 这里省略了sim_matrix的计算过程 # 实际项目中sim_matrix可以通过定时任务预计算后存缓存 similar_books sim_matrix.get(book_id, {}) for sim_book_id, sim_score in similar_books.items(): if sim_book_id in target_user_ratings: continue score_dict[sim_book_id] score_dict.get(sim_book_id, 0) sim_score * score # 4. 排序取Top-K ranked sorted(score_dict.items(), keylambda x: x[1], reverseTrue)[:top_k] return [book_id for book_id, _ in ranked]这段代码是一个骨架重点在于理解逻辑相似度矩阵我建议用独立的脚本在后台定时计算而不是在用户请求推荐的时候实时算否则首页会慢到让人怀疑系统坏了。2.4 冷启动与混合推荐真实系统必须面对的问题很多毕设源码停留在“用全部用户数据算相似度”的理想状态完全不考虑冷启动这是答辩最容易翻车的地方。冷启动分两种新用户冷启动和新书冷启动。新用户一进来没有任何评分记录协同过滤直接傻眼推荐列表为空。这时候就需要“热门兜底”策略——直接推荐全站评分人数最多、平均分最高的书。用Django的ORM写就是popular_books Book.objects.annotate( avg_scoreAvg(ratings__score), rating_countCount(ratings) ).filter(rating_count__gt10).order_by(-avg_score)[:10]新书冷启动则是反过来图书刚录入系统没有任何用户评分。协同过滤永远不会推荐它。这种书只能靠基于内容的推荐或者Admin手动置顶来获得初始曝光。我在系统里就加了一个“编辑推荐”位Admin可以在后台把新书放进去这样既解决了冷启动又让系统有了人工运营的味道答辩时还能说这是“人工算法结合的混合推荐策略”。混合推荐的核心思想是推荐结果不是单一算法算出来的而是多层策略叠加的结果。它的好处是有兜底、有惊喜、也有运营控制这在工业界是标配思路放在毕设里就是亮点。3. 核心功能实现与实操细节3.1 创建项目与app的正确姿势项目初始化这块别看简单我见过太多人栽跟头。正确流程是先建虚拟环境再装Django然后创建项目和应用顺序不能乱。python -m venv venv source venv/bin/activate # Windows下是 venv\Scripts\activate pip install django4.2 pillow django-admin startproject douban_books cd douban_books python manage.py startapp books python manage.py startapp recommend这里有个操作细节startproject生成的目录结构外层douban_books/是项目配置目录内层才是真正代码所在。通常我们会把books作为图书与评分相关apprecommend作为推荐算法app这样分层清晰不会一个app塞一堆互不相关的业务逻辑。创建完app之后记得先去settings.py的INSTALLED_APPS里注册。很多人写了一半发现模板加载不了、迁移不生效检查半天结果就是忘了把app加进去。还有一件常被忽略的事——startproject生成的项目默认没有静态文件目录配置如果你打算在页面里放第三方CSS和JS要提前在settings.py里配好STATIC_URL和STATICFILES_DIRS。3.2 用户注册登录与权限控制用户登录注册直接用Django内置的auth模块不建议自己手写session逻辑。注册的时候用UserCreationForm做基础然后加一个邮箱字段就够了。这里我踩过一个坑直接用User.objects.create_user创建用户时忘记传密码字段结果用户密码是明文存进去的后台怎么也登录不上去。正确写法是from django.contrib.auth.models import User from django.contrib.auth import login def register_view(request): if request.method POST: username request.POST[username] password request.POST[password] email request.POST[email] user User.objects.create_user(usernameusername, passwordpassword, emailemail) user.save() login(request, user) return redirect(home) return render(request, register.html)权限控制有两种思路。一种是函数视图里手动判断request.user.is_authenticated另一种是给类视图加LoginRequiredMixin。我建议统一用类视图配合Mixin代码更整洁也不会漏掉某个接口忘记判断登录态。对于评论区这种需要校验归属的操作还要加上request.user comment.user这种归属判断不能只看登录状态。3.3 图书展示、搜索、评分与收藏的实现要点图书列表页看起来简单但加上搜索、筛选、分页之后就涨了不少代码量。我推荐用Django的基于类的ListView配合django-filter插件能省很多事。搜索我实现的是“书名、作者、出版社”三个字段的模糊匹配from django.db.models import Q def book_list(request): keyword request.GET.get(keyword, ) category request.GET.get(category, ) books Book.objects.all() if keyword: books books.filter( Q(title__icontainskeyword) | Q(author__icontainskeyword) | Q(publisher__icontainskeyword) ) if category: books books.filter(categorycategory) # 分页 paginator Paginator(books, 12) page_number request.GET.get(page) page_obj paginator.get_page(page_number) return render(request, book_list.html, {books: page_obj})评分功能是最体现细节的地方。用户提交评分时要判断是“新增评分”还是“更新评分”——同一用户对同一本书只能有一条记录第二次提交应该覆盖而不是再插一条。用update_or_create函数一行搞定Rating.objects.update_or_create( userrequest.user, bookbook, defaults{score: score} )收藏功能逻辑相对简单但要关注前端交互已经收藏的图书按钮要变成“取消收藏”不能再放着“收藏”按钮让用户重复操作。这个状态判断后端要传一个is_collected标志给模板模板里用条件渲染判断。3.4 推荐页面如何渲染把算法结果送到前端推荐页面的核心逻辑其实是“数据准备”。我们的推荐算法算出来的是图书ID列表但页面上要展示的是书名、封面、评分这些信息。所以视图函数里要拿ID列表去批量查询图书对象再传给模板。def recommend_view(request): user request.user if not user.is_authenticated: # 未登录用户直接展示热门图书 books get_popular_books(12) return render(request, recommend.html, {books: books}) rec_ids get_recommend_books(user.id, top_k12) # 按推荐列表顺序取出图书对象 books Book.objects.filter(id__inrec_ids) # 保持推荐顺序不要用默认的id排序 book_dict {b.id: b for b in books} ordered_books [book_dict[bid] for bid in rec_ids if bid in book_dict] return render(request, recommend.html, {books: ordered_books})这里有个特别容易踩的坑就是filter(id__inrec_ids)返回的图书是按数据库默认排序的不等于你推荐的顺序。如果不做上面那个重新排序的步骤你辛辛苦苦算出来的推荐顺序会被打乱得一塌糊涂。这个小细节能让你的推荐系统显得“准”很多。模板渲染这块我的建议是做一组图书封面卡片每张卡片显示封面、书名、平均评分和“去评分”按钮。视觉上参考豆瓣读书的样式就很舒服——不需要自己多创新关键是信息层级清楚用户进来一眼就知道点什么。3.5 管理后台与数据导入毕设演示的关键Django Admin是毕设演示的神器一定要用好。默认的Admin只给你一个空壳要把图书管理做得顺手得注册模型和配置列表展示。先继承admin.ModelAdmin然后指定列表显示的字段、过滤器、搜索字段class BookAdmin(admin.ModelAdmin): list_display (title, author, publisher, category, price, douban_rating) list_filter (category, publisher) search_fields (title, author, isbn) list_per_page 20 admin.site.register(Book, BookAdmin)数据导入是个大坑。网上能找到的图书数据多为JSON或CSV直接手工往Admin里录500本书不现实。我的建议是写一个management command把爬好或者下载好的JSON导入到数据库作为项目的初始化脚本。放在books/management/commands/import_books.py然后执行python manage.py import_books books_data.json这样一键导入省时省力。而且给源码的时候把数据文件和脚本一起带上别人复现起来也方便。要注意一点图片尽量用外链URL而不是下载到本地否则部署后静态图片文件管理会很麻烦。4. 部署上线与常见问题排查4.1 本地运行与数据初始化从零到跑起来整个项目写好之后要保证“clone下来就能跑”。这里我坚持一个原则能脚本化的绝不手动操作。写一个init.sh脚本自动完成建虚拟环境、装依赖、迁移、导数据、创建超级用户、启动服务这几个步骤#!/bin/bash python -m venv venv source venv/bin/activate pip install -r requirements.txt python manage.py makemigrations python manage.py migrate python manage.py import_books books_data.json python manage.py createsuperuser python manage.py runserver这个脚本的价值在答辩前特别明显。你能在评委面前一键演示从零到运行那是真正的加分项。同时也逼着自己把依赖整理清楚——requirements.txt里要精确列出版本别用Django这种不带版本号的写法隔两年之后装出来的依赖大概率会有兼容性问题。一个我亲自踩过的坑是迁移顺序。如果先makemigrations再import_books导入脚本里用了ORM需要确保模型映射已经存在。所以顺序必须是先迁移再导数据。别把顺序写反。4.2 部署到服务器Python环境、静态文件与数据库切换很多人的项目在本地跑得好好的一部署到云服务器就各种问题。比较省心的方案是用宝塔面板部署Django。流程大概是装好宝塔之后配置Python项目管理器添加项目时选择Python版本设置启动方式为gunicorn或uwsgi然后把settings.py里的调试开关关掉配置好静态文件映射。这里要特别注意三件事第一是ALLOWED_HOSTS。本地调试时是空列表或者localhost一旦部署就要把它改成你的域名或服务器IP不然后端直接返回400错误。用ALLOWED_HOSTS [your-domain.com, your-server-ip]。第二是静态文件和媒体文件。DEBUGFalse之后Django不再帮你处理静态文件需要用到whitenoise或者让Nginx代理静态目录。我推荐用whitenoise配置超级简单适合小规模项目pip install whitenoise然后在settings.py的MIDDLEWARE里加一行whitenoise.middleware.WhiteNoiseMiddleware再配置STATIC_ROOT和STATICFILES_ROOT执行collectstatic收集静态文件就完事了。第三是切数据库。本地用SQLite服务器上建议切MySQL。切换时除了改settings.py还要注意MySQL的utf8mb4字符集否则中文书名可能存出乱码。迁移时直接跑python manage.py migrate就行ORM会自动帮你建表。4.3 常见问题速查表与避坑技巧实录做这个项目的过程中我记录了一堆让人抓狂的问题整理成表格分享给大家都是亲身踩过、实打实解决的问题。问题现象原因分析解决办法注册用户后无法登录密码用的明文存储用create_user方法创建用户不要直接赋值password字段首页推荐数据为空没有做冷启动兜底未登录/新用户展示热门图书评分少于阈值不参与推荐CSRF验证失败模板表单没有加{% csrf_token %}所有POST表单统一加上模板标签图片显示404DEBUGFalse后静态文件无人处理接入whitenoise或Nginx处理静态文件迁移文件丢失改了模型后直接部署没有生成迁移文件部署前先makemigrations再migrate修改字段后老数据报错数据库表结构和新模型不匹配测试阶段直接flush重置数据上线前别这么干gunicorn启动后页面502项目路径或python环境没选对检查宝塔中的启动脚本路径确认wsgi.py位置正确除了表格里这些再分享一个玄学但很实用的经验如果你在本地一切正常部署后出了问题优先看settings.py里和DEBUG相关的配置。80%的部署问题都出在DEBUGFalse之后的静态文件、ALLOWED_HOSTS和CSRF_TRUSTED_ORIGINS这三处。4.4 性能优化推荐模块扛住并发的小技巧毕设系统虽然不用面对象知乎那种亿级QPS但如果答辩现场有几十个同学一起用再加上评委老师可能会点来点去推荐模块的逻辑写得太笨的话页面还是会明显变慢。这里分享几个廉价的优化技巧。第一个是Django的ORM查询优化。用select_related和prefetch_related解决N1查询问题。比如展示图书列表时每本书还要显示平均评分如果不做预取每本书都会额外发一条SQL查询10本书就是10条SQL页面自然慢。改成books Book.objects.prefetch_related(ratings).all()一次查询把关联数据全部取回来。第二个是模拟真实环境的缓存。推荐系统的相似度矩阵计算复杂度高但结果变化频率低。我把计算结果存到Redis里每天凌晨定时更新一次。用户请求推荐时直接从缓存取矩阵响应时间从几百毫秒降到几十毫秒。import redis r redis.Redis(hostlocalhost, port6379, db0) sim_matrix_json r.get(book_sim_matrix) if sim_matrix_json is None: sim_matrix compute_sim_matrix() r.set(book_sim_matrix, json.dumps(sim_matrix), ex86400) else: sim_matrix json.loads(sim_matrix_json)这段代码不是最佳实践但作为毕设足够简单清晰。如果你不想引入Redis直接用django.core.cache内置的LocMemCache或者FileBasedCache也可以部署成本更低。5. 给毕设源码一个加分结尾5.1 README文档怎么写源码交付的时候README是第一印象也是很多同学容易忽略的地方。一个合格的README至少要有项目简介、技术栈、功能特点、目录结构说明、环境要求、安装运行步骤、项目截图展示。重点写清楚“如何跑起来”这个部分——把上面那个init.sh脚本的用法写清楚别人拿到源码第一件事就是照着跑能跑通才会继续看你的代码。5.2 后续可以怎么扩展如果你做完基础版还想再加亮点我推荐两个方向。第一是引入更多的推荐策略对比。把基于用户的协同过滤也实现出来做一个效果对比页面用准确率、召回率、覆盖率这几个指标来对比两种算法在同一数据集上的表现。这个做法既展示了算法功底又能让答辩内容更充实。第二是加入用户行为日志。记录用户浏览了哪些图书、停留了多长时间把这些行为数据作为推荐算法的辅助信号。配合ECharts等可视化库做一个用户行为分析图表项目从“简单推荐系统”进阶到“数据驱动产品”档次完全不一样。根据我个人的实操经验做一个能跑、能演示、能自洽讲清楚的图书推荐系统在Django的加持下大概需要三到五天的全神贯注编码时间大部分时间其实花在数据预处理和算法调试上。这个项目的核心价值在于它把Web开发、数据库设计和算法应用串在了一起做一遍下来你对Django套路和推荐系统的理解都会上一个台阶。希望这篇文章能帮正在做类似项目的你绕过我踩过的坑把时间花在真正有思考的地方。