ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python+Django实现协同过滤推荐系统:招聘平台实战解析

Python+Django实现协同过滤推荐系统:招聘平台实战解析 简介面向软件开发者、算法工程师及对机器学习Web应用感兴趣的学习者提供基于PythonDjango的招聘信息协同过滤推荐系统实现资料覆盖在线职业平台与人力资源管理系统中的智能岗位匹配需求。文档从系统需求分析入手结合B/S结构与MySQL数据库依次讲解数据预处理、相似度计算、候选集生成和结果整合等推荐核心步骤同时介绍个人中心、用户管理、招聘信息管理、留言板管理等模块并给出模型评估标准、难点解决方案及部分核心源代码。资源为单个docx文档压缩包共3.83MB包含摘要、目录、需求分析、相关技术简介、系统设计与测试性能分析结果目录结构清晰便于按章节阅读。已有170人学习下载适合希望完整掌握推荐系统从理论到实践全过程的初学者也适合需要借鉴算法落地思路的技术人员完善自身产品。 我去年用 Python Django 给一个模拟招聘平台从零搭了一套招聘信息协同过滤推荐系统核心思路不复杂先记录求职者的浏览、收藏、投递、忽略行为构建出“用户—职位”的偏好矩阵再找出行为习惯相近的一批用户把他们认可但当前用户还没看过的职位推出去。这套方案最大的价值在于不用做复杂的内容画像也不用标注训练集普通 Django 项目就能落地非常适合正在做毕业设计、个人实战项目或者刚开始接触推荐系统想找一套可运行代码的人参考。1. 项目背景与需求拆解1.1 招聘平台为什么需要个性化推荐招聘平台天然存在严重的“信息过载”。一个中型城市每天新增职位可能上千条职位标题、技能要求、薪资范围、公司规模各自不同。传统做法是让求职者搜关键词、筛条件但搜索只能解决“我知道自己要什么”的场景。现实中大量求职者是“大致知道方向但不确定哪些岗位更适合自己”这种情况下用户很容易刷几页就划走匹配效率很低。推荐系统要解决的就是这后半段问题让系统根据用户的历史行为主动把“可能感兴趣但用户没主动搜”的职位呈现出来。在招聘场景里推荐做得好不好直接体现在两个指标上职位投递率和用户次日留存率。投递率高说明推荐结果和用户期望一致留存率高说明用户愿意持续依赖这套系统。1.2 项目核心目标与适用人群这个项目的核心目标可以拆成三层数据层设计一套能记录求职者行为的数据模型攒下“谁在什么时间对什么职位做了什么操作”。算法层基于行为数据构建偏好矩阵计算用户之间相似度生成个性化职位列表。应用层把推荐结果用 Django 视图和模板展示出来并支持排除已投递职位、控制推荐数量等规则。这项目本质上是“Web 开发 推荐算法”的组合。如果你正在学 Django 但一直卡在“只会做增删改查”的阶段这个项目能帮你把 Django 的能力从 CRUD 延伸到算法应用上如果你是想入门推荐系统这个项目能让你避开推荐系统课程里大量数学推导先看到一个可运行的算法全流程。我自己带过几个实习生给他们练这个题目普遍两周左右就能跑通完整链路。2. 整体方案设计与技术选型2.1 为什么选 Python Django 这套组合推荐系统的数据处理、相似度计算、排序逻辑最适合用 Python 表达因为它的列表推导、字典操作、科学计算库支持非常顺手。Web 层用 Django主要看中三个点自带 ORM可以直接操作 PostgreSQL 或 MySQL不用单独写数据访问层自带 Admin 后台开发阶段可以可视化地录入职位、查看用户行为调试效率高认证体系和会话机制现成可以直接绑定当前登录用户做个性化推荐。相比 FlaskDjango 在项目结构上更严谨models、views、templates 分层清晰适合配合推荐算法的模块化实现。如果只是做一个算法演示脚本Flask 确实更轻但一旦涉及到用户登录、行为记录、职位管理这些真实业务功能Django 的完整度优势立刻显现。2.2 为何选择协同过滤而不是内容推荐或热门推荐我在设计时对比过三条路线热门推荐按浏览量或投递量倒序推荐实现最简单但完全没有个性化每个用户看到的结果都一样对长尾职位不友好。内容推荐分析职位描述和用户简历文本用关键词或 TF-IDF 做相似匹配需要维护用户画像和职位画像冷启动问题依然存在而且中文分词、职位文本质量都会直接影响效果。协同过滤不关心职位内容是什么只关心“哪些用户行为相似”通过群体智慧完成推荐。最终选协同过滤原因是它最适合当前项目的资源条件。协同过滤不需要额外维护画像标签行为数据积累到一定量之后效果会越来越好。协同过滤还天然覆盖“意外发现”的场景用户可能自己都不知道还有“数据分析师”这类职位但相似用户的行为会把这类职位带出来。协同过滤内部还可以细分基于用户的协同过滤User-based CFUCF和基于物品的协同过滤Item-based CFICF。招聘场景我建议优先做 UCF因为职位的生命周期短很多岗位招满后就下线了如果基于职位算相似度新职位进来根本没有历史行为无法入库。而用户的兴趣相对稳定基于用户相似度做推荐更适合这个场景。2.3 推荐整体流程整个推荐流程可以概括为六个环节前端页面触发行为上报比如用户查看了某职位、点击了收藏、投递了简历。Django 视图接收请求把行为写入 Behavior 表。定时任务或实时计算读取近一段时间的行为记录构建用户—职位评分矩阵。对目标用户计算与其他用户的相似度选出 Top-N 个最相似用户。聚合相似用户的职位评分过滤掉用户已投递和已忽略的职位。按分数倒序取前 K 个职位渲染到前端推荐位。这套流程里最依赖算法实现的是第 4、5 两步其余都是标准 Django 开发工作。3. 核心模块实现细节3.1 Django 数据模型设计推荐系统的数据层是整个项目的地基设计得好不好直接影响算法实现复杂度。我采用了三个核心模型和若干辅助配置。from django.db import models from django.contrib.auth.models import User class Job(models.Model): title models.CharField(max_length128, verbose_name职位名称) company models.CharField(max_length128, verbose_name公司名称) city models.CharField(max_length32, verbose_name工作城市) salary_min models.IntegerField(default0, verbose_name最低薪资/K) salary_max models.IntegerField(default0, verbose_name最高薪资/K) skills models.CharField(max_length255, blankTrue, verbose_name技能要求) description models.TextField(blankTrue, verbose_name职位描述) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table recruit_job indexes [ models.Index(fields[city]), ] class Candidate(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE, related_namecandidate) name models.CharField(max_length32, verbose_name姓名) city models.CharField(max_length32, blankTrue, verbose_name意向城市) years_experience models.IntegerField(default0, verbose_name工作年限) class Meta: db_table recruit_candidate class Behavior(models.Model): VIEW view FAVORITE favorite APPLY apply REJECT reject BEHAVIOR_CHOICES [ (VIEW, 浏览), (FAVORITE, 收藏), (APPLY, 投递), (REJECT, 忽略), ] user models.ForeignKey(User, on_deletemodels.CASCADE, related_namebehaviors) job models.ForeignKey(Job, on_deletemodels.CASCADE, related_namebehaviors) behavior_type models.CharField(max_length16, choicesBEHAVIOR_CHOICES) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table recruit_behavior字段设计时有两个细节值得注意。Behavior 表的 behavior_type 用字符串常量 choices 而不是直接建多张行为表优点是以后加“转发”“面试邀约”这类行为只需要加常量不需要变更表结构。Job 表的城市字段增加了索引是因为后续可能按“同城职位优先推荐”做过滤这个字段的查询频率会非常高。还有一个容易被忽略的问题User 模型和 Candidate 模型的关系。我没有直接在 User 上增加字段而是用 OneToOne 外键单独的 Candidate 表这样可以避免对 Django 自带 auth_user 表做侵入式修改后面接认证体系升级也更稳。3.2 行为数据离散化处理协同过滤算法输入的是“用户对物品的评分矩阵”但原始行为是离散的事件需要把事件换算成分数。不同行为在招聘场景里代表不同的兴趣强度。我实际用的评分映射表如下行为评分说明浏览1有初步兴趣权重最低收藏2明确感兴趣的信号投递3强意向权重最高忽略-1负反馈降低该职位的推荐权重要注意评分映射并不是拍脑袋定的要考虑行为稀疏度。如果浏览行为权重给太高会导致系统只推荐高浏览量的热门职位个性化被稀释如果投递权重给太高又会导致用户只投过一两个职位时推荐结果过度集中在相似职位上。上述这套分值是我测试后觉得比较稳的组合。实际编码时我用一个函数完成“用户行为数据 - 评分字典”的转换def build_user_item_matrix(behaviors): score_map { Behavior.VIEW: 1, Behavior.FAVORITE: 2, Behavior.APPLY: 3, Behavior.REJECT: -1, } user_items {} for behavior in behaviors: uid behavior.user_id job_id behavior.job_id score score_map[behavior.behavior_type] if uid not in user_items: user_items[uid] {} # 同一个用户对同一个职位有多种行为时取最大绝对值得分 old user_items[uid].get(job_id, 0) if abs(score) abs(old): user_items[uid][job_id] score return user_items这里有一个隐含规则同一个用户对同一个职位可能既有浏览又有收藏比如先浏览过两天收藏最后投递。如果直接覆盖会造成信息丢失我的策略是取绝对值最大的作为最终评分让投递3分覆盖浏览1分这个规则在实际测试中效果比较合理。3.3 用户相似度计算与推荐生成相似度计算常用余弦相似度或皮尔逊相关系数。招聘平台的行为数据有一个明显特点大部分用户只对少量职位产生过行为评分矩阵非常稀疏。皮尔逊相关系数需要两个用户有交集才有意义而余弦相似度可以借助填充 0 的方式对全向量运算。我自己实现时优先用余弦相似度公式如下sim(A, B) Σ(Ai × Bi) / (√ΣAi² × √ΣBi²)代码实现如下import math from collections import defaultdict def cosine_similarity(user_a_items, user_b_items): common set(user_a_items.keys()) set(user_b_items.keys()) if not common: return 0.0 dot sum(user_a_items[item] * user_b_items[item] for item in common) norm_a math.sqrt(sum([v ** 2 for v in user_a_items.values()])) norm_b math.sqrt(sum([v ** 2 for v in user_b_items.values()])) if norm_a 0 or norm_b 0: return 0.0 return dot / (norm_a * norm_b)得到相似度之后推荐职位得分的计算逻辑是找出和目标用户最相似的 Top-N 个用户这些用户评分过的职位对目标用户计算加权得分score(item) Σ sim(user, neighbor) × neighbor_score(item)这个公式的含义很直观一个职位如果被多个“和我相似”的用户投递过那它就该排在前面如果推荐者是相似度极高的用户那权重还会进一步放大。def recommend_for_user(target_uid, user_items, top_n5, top_k10): target_items user_items[target_uid] target_set set(target_items.keys()) scores defaultdict(float) similarity_cache {} for uid, items in user_items.items(): if uid target_uid: continue sim cosine_similarity(target_items, items) if sim 0: continue similarity_cache[uid] sim similar_users sorted( similarity_cache.items(), keylambda x: x[1], reverseTrue )[:top_n] for uid, sim in similar_users: for job_id, rating in user_items[uid].items(): if job_id in target_set: continue scores[job_id] sim * rating ranked sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_k] return ranked这里我特意把相似度存入 similarity_cache 而不是每次计算因为推荐接口常被前端的“换一批”按钮触发同一个用户短时间内多次请求相似用户集合基本不变缓存能明显减少无效计算量。4. 从零搭建项目的完整实操过程4.1 环境准备与 Django 工程初始化如果你从空目录开始搭这个项目推荐先建 Python 虚拟环境避免把依赖装进全局环境里。我自己惯用的命令是python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install django django-admin startproject recruit_project cd recruit_project python manage.py startapp recommend把 recommend 应用注册到 settings.py 的 INSTALLED_APPS 后执行数据库迁移python manage.py makemigrations recommend python manage.py migrate还要在 settings.py 里加一条配置确保中文内容正常显示LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ True建完工程后我建议先准备一份职位数据和模拟行为数据的脚本。没有真实数据做测试算法再对也看不出来效果。可以写一个 management command循环生成 1000 个职位再模拟 50 个用户对其中部分职位产生浏览、收藏、投递行为。from django.core.management.base import BaseCommand from recommend.models import Job, Behavior from django.contrib.auth.models import User import random class Command(BaseCommand): help 生成模拟数据 def handle(self, *args, **options): for i in range(1000): Job.objects.create( titlefPython开发工程师-{i}, companyf示例科技, cityrandom.choice([北京, 上海, 广州, 深圳]), salary_minrandom.randint(10, 20), salary_maxrandom.randint(20, 40), skillsPython, Django, ) # 模拟用户行为...4.2 推荐视图与模板展示推荐逻辑在 Django 里以视图函数形式对外提供服务。登录用户访问推荐页时视图调用 recommend_for_user得到职位 ID 和分数的列表然后从 Job 表查出完整信息返回页面。from django.contrib.auth.decorators import login_required from django.shortcuts import render from django.contrib.auth.models import User from .recommend_core import build_user_item_matrix, recommend_for_user login_required def recommended_jobs(request): target_uid request.user.id behaviors Behavior.objects.select_related(job).all()[:50000] user_items build_user_item_matrix(behaviors) if target_uid not in user_items: # 冷启动兜底返回热门职位 jobs Job.objects.order_by(-created_at)[:20] else: ranked recommend_for_user(target_uid, user_items) job_ids [job_id for job_id, score in ranked] # 保持推荐顺序 jobs list(Job.objects.filter(id__injob_ids)) jobs.sort(keylambda j: job_ids.index(j.id)) return render(request, recommend/recommended.html, {jobs: jobs})不敞开发全部职位 ID而是按 sorted 顺序从数据库取出完整的职位对象。模板层就可以用普通循环输出结果。4.3 项目现场踩坑实录这个项目我在本机完整跑过几轮有几个问题如果不提前知道调试会非常痛苦。第一个坑是行为记录里混入了“自己”的脏数据。开发阶段我经常用同一个账号测试浏览、收藏、投递导致推荐结果被自己刷出来的异常行为带偏。解决方案是清理测试数据并且在前端给行为按钮做防重复点击控制。第二个坑是相似度明明算出来了但推荐结果只包含热门职位。排查后发现是因为我用了全量行为构建矩阵而热门职位天然有更多行为相似用户都有评分。这个问题可以通过把相似用户的数量上限调小来缓解比如只取最相似的 5 个用户减少被热门带偏的概率。第三个坑是性能问题。行为表数据到几万条时实时计算余弦相似度已经明显变慢。我的方案是增加一层缓存Expiring 缓存用户相似度 Top-N 结果而不是每次请求现算。对于数据量继续扩大的场景还可以把相似度矩阵放到 Redis 里或者改成定时离线计算任务。5. 常见问题与排查技巧推荐系统的 bug 和普通 CRUD 应用的 bug 不太一样往往不是“报错”而是“结果不对”排查时要有清晰方法。以下是我实际使用中整理的问题速查表现象可能原因解决思路推荐结果为空用户没有历史行为或相似用户集合为空冷启动兜底返回热门职位等行为数据积累后再进推荐链路推荐结果全是热门职位Top-N 相似用户数过大热门职位被重复抬分调小 top_n 参数限制只看最像的 3-5 个用户推荐结果包含用户已投递职位过滤逻辑放在了加权之后但漏了 target_set 判断在推荐候选集中显式排除 target_set 中的职位 ID相似度全为 0行为数据量太少用户之间没有交集扩大行为收集范围浏览行为权重可以适当调低但保留用户数量大时接口变慢每次请求实时算全量相似度增加用户相似度缓存或定时离线计算个别用户出现“推荐了同一公司相似职位”的重复现象相似用户行为高度集中对职位 ID 做去重或引入职位描述相似度做重排还有一个隐蔽问题负反馈忽略职位没被过滤干净。我在 initial 版本里过滤了已投递职位但忘了过滤忽略职位导致用户点“不感兴趣”后职位还是偶尔出现在推荐流里。解决方式很简单在 recommendation 函数的候选筛选中把 rating 0 的职位也排除掉。另外调试推荐系统时不要只看单个样例效果。我自己习惯的做法是拉一个小规模测试集给定 20 个真实用户用前两周行为做训练第三周行为做效果验证统计“推荐的职位中用户实际产生了行为的占比”。这个比例如果能稳定超过 5%基本说明推荐链路是有效的如果远远低于这个值优先检查行为评分映射和相似度算法而不是继续调参。6. 几个实用的扩展方向与心得如果这套基础版你已经跑通我建议再补两件事。一是推荐接口做 API 化用 Django REST Framework 暴露 /api/jobs/recommended/ 接口这样移动端也可以直接用。二是做相似职位的“相关职位推荐”这个可以用基于物品的协同过滤但要注意职位下线后相关数据要及时清理。我自己实测下来这套实现配合 1000 条职位和 50 个测试用户的规模每次推荐的响应时间可以控制在几百毫秒内数据量大到十万级后如果不加缓存就会明显等待这时候才需要考虑引入 Celery 做离线相似度计算。最后给后来者一句实在话推荐系统的难点从来不在代码本身而在于“如何定义一个好推荐”。认真想一想你的用户到底需要什么再动手写代码比什么都重要。本文还有配套的精品资源点击获取
返回列表