基于Python Django的美容院优质客户筛选系统设计与RFM评分模型实现
做毕业设计选型的时候很多同学一看到“客户筛选系统”这类题目下意识就觉得是简单的增删改查随便拿个框架糊弄一下就能交差。但美容院这个场景其实很有意思——它的核心业务既不是销售实物商品也不是标准化服务而是高度依赖“客户关系”和“复购率”的线下消费模式。真正把“优质客户筛选”这件事做明白背后需要一套完整的数据建模和评分逻辑这恰恰是Django这种自带ORM、Admin后台和模板引擎的全家桶框架最擅长解决的问题。这篇文章就把这个基于Python和Django的美容院优质客户筛选系统从需求拆解、数据库设计到核心算法实现、文档撰写和答辩准备完整地讲一遍给正在做毕设或者想接私活的朋友一份可以直接抄作业的参考。1. 项目整体设计与思路拆解1.1 美容院客户管理场景的核心需求我接触过好几个美容院实际运营的案例发现大部分中小型美容院的管理方式还停留在“一本纸质登记册 一个Excel表格”的水平。客户来店办卡、做项目、充值、消耗全靠店长和前台人工记录顶多月底对着Excel手动拉个透视表看看谁消费多。这种方式的痛点是致命的第一客户信息分散员工离职可能带走一大批客户资料第二无法快速识别哪些客户是真正的高价值人群导致营销资源平均分配该重点维护的大客户反而被冷落第三客户流失预警基本靠感觉等发现老客户几个月不来的时候人早就跑到竞争对手那里了。这个毕设题目要做的就是用Django搭建一个Web系统把客户信息、消费记录、服务项目、到店频次全部数字化然后通过一套可量化的评分模型自动筛选出优质客户辅助美容院老板做精准营销和客户关怀决策。它的核心价值不是“管理客户”而是“分析客户”——这也是“筛选”两个字的关键所在。1.2 为什么选择Django作为Web框架市面上的Python Web框架主要有Django和Flask两大阵营。我给这个项目的选型建议是Django理由非常实际毕设项目要求在有限时间内完成一个功能闭环必须包含前端页面、后端逻辑、数据库设计和文档撰写Django的“全家桶”特性正好覆盖了所有环节。具体来说Django有四个杀手级优势。第一自带Admin后台只需要注册模型就能免费得到一个完整的后台管理界面这意味着客户信息、消费记录的CRUD操作不需要额外写代码就能在后台完成毕设演示的时候也很有说服力。第二内置ORM不需要手写SQL用Python类定义数据表结构语法优雅且安全天然防止SQL注入。第三模板引擎配合类视图可以快速搭建包含列表页、详情页、表单页的完整前端哪怕前端基础一般也能做出能看的界面。第四文档和社区资源极其丰富遇到问题搜一下基本都有现成答案对初学者非常友好。相比之下Flask虽然轻量灵活但需要自己集成数据库扩展、表单验证、用户认证等一堆第三方库把时间花在“搭轮子”上对毕设来说投入产出比太低。1.3 优质客户筛选的业务逻辑设计“优质客户”到底怎么定义这是整个系统的灵魂问题。如果只是按消费金额排名那结果就是一个简单SQL的order by根本撑不起一篇论文。业界经典的客户价值分析模型是RFM模型通过三个维度评估客户RRecency最近一次消费时间距离现在多久时间越短代表客户活跃度越高。FFrequency统计周期内的消费频次频次越高代表客户粘性越强。MMonetary统计周期内的消费总金额金额越高代表客户贡献价值越大。但这个模型拿到美容院场景需要做本地化调整。美容院的消费特点是客单价高、周期性强比如面部护理通常按月做一次、套餐和会员卡形态复杂所以我在设计时把三个维度的权重做了倾斜消费金额权重最高0.5因为美容院的核心利润来源就是高客单价消费频次其次0.3反映客户对店里的依赖程度最近消费时间权重最低0.2原因是它属于动态指标波动大主要用于识别“活跃”和“流失风险”但不完全代表客户价值。通过这个加权评分公式系统会给每个客户算出一个总分然后按分数区间划分等级A类高价值客户、B类潜力客户、C类普通客户、D类流失风险客户。有了这个分层美容院在做促销活动、节假日关怀、充值赠送时就能优先把资源倾斜给A类和D类客户——前者值得投入后者需要挽回。2. 核心功能模块与数据库设计2.1 客户信息管理模块的数据建模数据库设计是整个系统的基础一旦表结构定错了后期改起来非常痛苦。这个系统的核心表我做了三张客户表Customer、服务项目表ServiceItem和消费记录表ConsumptionRecord再加上一张用户表User用于系统登录。客户表的设计上有个特别容易踩坑的点手机号到底要不要设唯一约束从业务角度讲一个客户可能有多个手机号或者夫妻共用一张会员卡但实际系统中同一个客户会反复登记所以我会建议用自增ID作为主键手机号只加索引用于搜索不做Unique约束。字段上除了常规的姓名、性别、生日、手机号还要加上会员等级、储值余额、偏好项目、备注信息。其中“偏好项目”看似不起眼但在后续做客户画像和精准推荐时非常有用。服务项目表相对简单包含项目名称、所属类别面部护理、身体护理、SPA等、单价、时长。这里要注意项目价格会浮动所以消费记录里要冗余存储“成交价”不能实时去关联项目表的单价——否则当年做促销打折的记录事后统计时就会得出错误金额。2.2 消费记录与到店频次的数据建模消费记录表是整个评分系统的数据基础它的设计直接决定了筛选算法的可实现性。我设计的ConsumptionRecord表包含这些关键字段客户ID外键、服务项目ID外键、消费金额、消费日期、操作员工、备注。这里有一个核心建模思想每一次到店消费存一条记录而不是一个客户对应一条记录。这样设计的好处是后期可以用ORM的聚合函数灵活计算任意时间窗口内的消费频次和总金额。比如系统首页要展示“今日营收”只需要对消费记录表按日期过滤再求和要算“某客户近三个月消费频次”只需要一条annotate Count查询。到店频次有两种计算口径按天去重统计到店天数或者按消费记录条数统计消费次数。美容院场景里客户可能一天内做多个项目但算频次时应该按“到店次数”算因此我用了一个小技巧在消费记录表中加一个“到店批次号”visit_batch字段同一客户同一天到店产生的多条消费记录共享同一个批次号。这样统计频次时对批次号去重就行既简单又准确。2.3 优质客户评分模型的具体设计评分模型我用一个独立模块实现没有把评分逻辑散落在各个视图里。核心思路是为每个客户计算三个维度得分再做加权求和。具体算法如下最近一次消费距今的天数先归一化处理根据天数映射分数30天内、30到60天、60到90天、90天以上分别对应一档分数。消费频次取近90天的到店批次数量按0次、1-2次、3-5次、6次以上分档赋分。消费金额取近90天的累计消费总额按500元以下、500-2000元、2000-5000元、5000元以上分档赋分。为什么统计周期选90天而不是一年因为美容院的消费周期通常在1个月左右90天可以覆盖3个完整周期既能反映客户的近期消费行为又不会因为拉长周期而让“历史辉煌过但最近流失”的客户被误判为优质。这个90天的窗口期参数建议做成可配置项存到一个系统配置表里方便日后再调整。评分计算代码写在独立的service层中视图只需要调用接口。这样做的好处是答辩时可以说“采用了分层解耦的设计思想”而且在后续测试阶段可以直接对评分模块写单元测试不用启动整个Web服务。3. Django实操实现从零搭建完整系统3.1 环境准备与项目初始化本地开发环境建议使用虚拟环境避免污染系统全局Python。我习惯用Python 3.10搭配Django 4.2 LTS版本这个组合经过大量项目验证稳定性和第三方库兼容性都很好。在项目初始化前先把虚拟环境建好再安装依赖命令如下# 创建虚拟环境 python -m venv venv # 激活虚拟环境Windows环境执行venv\Scripts\activate source venv/bin/activate # 安装Django和扩展库 pip install django4.2 pip install django-simpleui # 可选用于美化Admin后台 pip install mysqlclient # 如果用MySQL数据库则需要装项目创建和配置的完整流程如下django-admin startproject beautysystem cd beautysystem python manage.py startapp customer python manage.py startapp consumption创建好项目和两个app后别急着写代码先把settings.py中的INSTALLED_APPS注册好把语言改为中文、时区改为Asia/Shanghai然后配置数据库连接。开发阶段我建议先用SQLite零配置跑起来最快等系统功能验证通过后再切换到MySQL用于部署和毕设演示。3.2 关键模型的编写与数据迁移customer这个app的核心模型我写了Customer类它承担客户基本信息的存储。字段定义上要注意选择CharField还是TextField、IntegerField还是BigAutoField这些细节直接影响后期的数据容量和查询效率。from django.db import models class Customer(models.Model): GENDER_CHOICES ( (M, 男), (F, 女), (U, 保密), ) LEVEL_CHOICES ( (A, A类高价值客户), (B, B类潜力客户), (C, C类普通客户), (D, D类流失风险客户), ) name models.CharField(max_length50, verbose_name客户姓名) phone models.CharField(max_length20, db_indexTrue, verbose_name手机号) gender models.CharField(max_length1, choicesGENDER_CHOICES, defaultU, verbose_name性别) birthday models.DateField(nullTrue, blankTrue, verbose_name生日) level models.CharField(max_length1, choicesLEVEL_CHOICES, defaultC, verbose_name客户等级) balance models.DecimalField(max_digits10, decimal_places2, default0, verbose_name储值余额) preference models.CharField(max_length200, blankTrue, verbose_name偏好项目) remark models.TextField(blankTrue, verbose_name备注) created_at models.DateTimeField(auto_now_addTrue, verbose_name创建时间) class Meta: verbose_name 客户信息 verbose_name_plural verbose_name def __str__(self): return f{self.name}({self.phone})这里有个经验之谈db_indexTrue一定要给搜索频率高的字段加上。客户列表页肯定会按手机号或姓名搜索没有索引的话一旦数据量过万查询性能就会明显下降答辩现场演示时卡顿会非常尴尬。消费记录模型放在consumption app里核心是外键关联Customer和ServiceItem同时冗余存一份成交价和到店批次号。class ConsumptionRecord(models.Model): customer models.ForeignKey(customer.Customer, on_deletemodels.CASCADE, verbose_name客户) service_item models.ForeignKey(consumption.ServiceItem, on_deletemodels.PROTECT, verbose_name服务项目) amount models.DecimalField(max_digits10, decimal_places2, verbose_name成交金额) visit_batch models.CharField(max_length20, db_indexTrue, verbose_name到店批次号) consume_date models.DateField(db_indexTrue, verbose_name消费日期) operator models.CharField(max_length50, blankTrue, verbose_name操作员工) remark models.TextField(blankTrue, verbose_name备注) created_at models.DateTimeField(auto_now_addTrue, verbose_name录入时间)外键的on_delete参数务必慎重。客户删除时消费记录用CASCADE级联删除这是合理的但服务项目被删除时消费记录应该用PROTECT保护防止误删掉有历史消费数据的项目导致数据统计断链。数据迁移的过程很简单两条命令搞定python manage.py makemigrations生成迁移文件python manage.py migrate执行迁移。之后就可以用python manage.py createsuperuser创建管理员账号进入Admin后台管理数据。3.3 视图、URL路由与模板渲染这个系统我用了函数视图和类视图混合的方案。列表页和详情页用ListView和DetailView类视图代码非常精简评分计算和筛选逻辑用函数视图方便在代码讲解时逐行解释逻辑。以客户列表页为例ListView的写法如下from django.views.generic import ListView from django.db.models import Q from customer.models import Customer class CustomerListView(ListView): model Customer template_name customer/customer_list.html context_object_name customers paginate_by 10 def get_queryset(self): qs super().get_queryset() keyword self.request.GET.get(keyword, ).strip() if keyword: qs qs.filter( Q(name__icontainskeyword) | Q(phone__icontainskeyword) ) return qsURL路由配置上加一条关键代码path(customers/, CustomerListView.as_view(), namecustomer_list)。模板里通过{% for customer in customers %}循环渲染数据用分页组件展示翻页按钮。这整个过程代码量不大但知识密度很高包含了CBV、ORM过滤、模板标签、分页器多个知识点答辩时能讲的内容非常丰富。3.4 筛选算法实现与ORM查询优化评分计算的service层是整个系统的技术亮点我单独写出核心代码片段from datetime import timedelta from django.utils import timezone from django.db.models import Count, Sum def calculate_customer_score(customer, window_days90): 计算单个客户的价值评分返回总分和等级 cutoff_date timezone.now().date() - timedelta(dayswindow_days) # 1. 计算最近消费时间得分 R latest_record customer.consumptionrecord_set.order_by(-consume_date).first() if latest_record: days_since (timezone.now().date() - latest_record.consume_date).days if days_since 30: r_score 5 elif days_since 60: r_score 3 elif days_since 90: r_score 1 else: r_score 0 else: r_score 0 # 2. 计算90天内消费频次得分 F按到店批次号去重 visit_count customer.consumptionrecord_set.filter( consume_date__gtecutoff_date ).values(visit_batch).distinct().count() if visit_count 6: f_score 5 elif visit_count 3: f_score 4 elif visit_count 1: f_score 2 else: f_score 0 # 3. 计算90天内消费总金额得分 M total_amount customer.consumptionrecord_set.filter( consume_date__gtecutoff_date ).aggregate(totalSum(amount))[total] or 0 if total_amount 5000: m_score 5 elif total_amount 2000: m_score 4 elif total_amount 500: m_score 2 else: m_score 0 # 4. 加权求和 total_score r_score * 0.2 f_score * 0.3 m_score * 0.5 if total_score 4.5: level A elif total_score 3.5: level B elif total_score 2.5: level C else: level D return total_score, level这段代码有两点值得拿出来在答辩时重点讲。第一values(visit_batch).distinct().count()的写法精确实现了“按到店批次去重统计消费次数”这是很多初学者写不出来的查询技巧。第二对aggregate(Sum(amount))的处理没有直接取返回值而是加了个or 0兜底防止没有消费记录的客户出现None值导致评分异常——这种细节能让代码更健壮。当客户数量达到几千甚至上万时逐个调用这个函数计算评分会产生严重的性能问题。我的优化方案是用ORM的annotate一次性完成批量计算把数据聚合推到数据库层执行而不是在Python层循环。比如统计所有客户的总消费金额可以这么写from django.db.models import Sum from customer.models import Customer customers_with_total Customer.objects.annotate( total_spentSum(consumptionrecord__amount) ).order_by(-total_spent)再用prefetch_related预加载每客户近90天的消费记录避免在循环里触发N1查询。这样优化后即使客户数据量到了上万条页面响应时间也能控制在可接受范围内。4. 系统测试、文档编写与交付4.1 功能测试与数据验证要点毕设项目的测试重点不是写多少单元测试用例而是用合理的数据验证业务逻辑的正确性。我构建了一套模拟数据造了约80个客户和近1200条消费记录覆盖了“长期不来的高消费老客户”“每月固定到店的忠实客户”“刚来一次的试客”等多种典型画像然后跑一遍评分逻辑检查不同画像客户是否能得到符合预期的等级。这个验证过程非常重要因为评分逻辑的参数比如天数分档、金额阈值往往是拍脑袋定的必须用真实分布的数据来验证合理性。比如我一开始把“90天内消费金额5000元以上算5分”定得太高导致模拟数据里80%的客户都挤在C级完全失去了筛选的意义后来下调到3000元分档客户等级分布才变得有区分度。这类调参过程记录下来写进论文里就是很好的“系统测试与分析”章节素材。4.2 毕业设计文档的撰写思路毕设文档一般要求1万字以上、包含需求分析、总体设计、数据库设计、详细设计、测试、总结六大章节。我的建议是代码还没写完一半就开始写文档而不是全部做完再憋。可以采用边开发边记录的方式每完成一个模块立刻把设计思路、核心代码片段、实现截图补充到对应章节。需求分析部分重点写“业务痛点→功能需求→非功能需求”这条线把这个系统要解决的问题说明白。总体设计画系统功能模块图和系统架构图不需要多专业能清晰表达前端、后端、数据库三层关系就行。数据库设计章节放E-R图和三张核心表的字段说明把字段类型、是否为空、默认值、外键关系列成表格。详细设计按模块拆分每个模块描述功能、贴核心代码、说明实现思路。测试章节放功能测试用例表格和测试截图。总结部分写遇到的问题和解决过程这比空洞的大段感想更有说服力。4.3 代码讲解与答辩准备的技巧答辩时的代码讲解千万不要从头到尾念代码评委没耐心也没兴趣听你一行行读。正确的讲解方式是按数据流来讲客户在前台提交信息后数据怎么通过表单验证存入数据库消费记录录入后怎么触发评分模块的更新评分结果怎么展示在仪表盘和客户列表上。整个流程串起来既展示了系统的完整性又体现了你的项目理解深度。答辩老师大概率会问这几个问题提前准备好答案第一问“为什么选Django不选Flask”可以从“全家桶开箱即用、自带Admin和ORM、适合快速交付完整项目”角度回答第二问“筛选模型的合理性如何保证”回答思路是“基于RFM经典模型结合实际业务场景调整权重并通过模拟数据验证等级分布”这个答案展现的是你思考过业务而非只会调API第三问“系统有哪些不足和可扩展点”要坦诚承认当前方案没做数据可视化图表未来可以引入ECharts做客户消费趋势分析可以接入微信公众号做客户自助查询和充值提醒还可以引入机器学习做流失预测。实事求是加上可落地的改进方向评委印象分会高很多。5. 常见问题与排查技巧实录5.1 疑难问题速查表开发这个系统过程中我踩过不少坑整理成一张速查表希望对你有用。问题现象根本原因解决方案输入中文变成乱码数据库字符集不是utf8mb4MySQL建库时指定CREATE DATABASE xxx CHARACTER SET utf8mb4settings中配置OPTIONS{charset:utf8mb4}查询不到按时区过滤的数据未设置USE_TZ和TIME_ZONEsettings.py中设置USE_TZTrue、TIME_ZONEAsia/Shanghai消费日期用DateField而非DateTimeFieldAdmin后台样式错乱Django自带的静态文件未收集部署前执行python manage.py collectstatic并配置STATIC_ROOT分页后搜索条件丢失分页链接未携带GET参数模板翻页链接中手动拼接request.GET.urlencode()外键关联数据被误删未设置合适的on_delete策略业务数据关联用PROTECT主数据关联用CASCADE根据业务语义逐字段确认列表页加载缓慢未使用prefetch_related导致N1查询列表页的queryset加上prefetch_related(consumptionrecord_set)模板中获取关联字段报错访问了null外键模板中用{% if customer.consumptionrecord_set.first %}判断或用默认空集合5.2 实战踩坑与独家避坑技巧我在实际开发这个项目的过程中印象最深的坑是“数据统计口径不一致”的问题。最初计算消费频次时直接对消费记录表做count但一个客户同一天做了面部护理和身体护理两个项目就会产生两条记录直接统计导致“到店次数”被翻倍。后来才意识到必须在设计阶段就考虑到这个场景加一个batch批次字段用distinct去重。另一个值得分享的技巧是脚本批量造模拟数据。毕设答辩现场的演示数据必须足够“像样”否则评委点开客户列表看到只有七八条记录会严重怀疑系统的实用性。我写了一个独立的generate_data.py脚本随机生成客户姓名、手机号和消费记录并根据设定好的概率分布刻意制造出“高价值老客户”“活跃普通客户”“流失风险客户”这几类人群。比如有10%客户的消费金额随机落在5000元以上区间有15%客户最后一次消费在90天以前。这样生成的演示数据分布自然能在评分结果里看到明显的客户分层演示效果非常好。最后还要提醒一个细节如果你打算把系统部署在服务器上做线上演示别忘了改Django的ALLOWED_HOSTS配置加上服务器的域名或IP否则访问时会直接报DisallowedHost错误现场搞定这个bug非常狼狈。这个项目做完之后最大的启发是技术本身不难把业务逻辑想清楚才是核心。美容院的“优质客户筛选”如果只是套用通用CRM模板做出来就是一个平平无奇的增删改查系统但一旦深入思考了“什么样的客户才是好客户”“如何用数据描述客户价值”这些问题项目的深度和答辩的支撑材料就完全不一样了。后面如果有时间我还会把这个系统往客户流失预警、智能营销推荐的方向扩展那又是另一篇博文的故事了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →