☰
基于Python Django的美容院优质客户筛选系统设计与RFM评分模型实现
2026/10/2 15:34:54 网站建设 项目流程

做毕业设计选型的时候,很多同学一看到“客户筛选系统”这类题目,下意识就觉得是简单的增删改查,随便拿个框架糊弄一下就能交差。但美容院这个场景其实很有意思——它的核心业务既不是销售实物商品,也不是标准化服务,而是高度依赖“客户关系”和“复购率”的线下消费模式。真正把“优质客户筛选”这件事做明白,背后需要一套完整的数据建模和评分逻辑,这恰恰是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模型,通过三个维度评估客户:

  • R(Recency):最近一次消费时间距离现在多久,时间越短,代表客户活跃度越高。
  • F(Frequency):统计周期内的消费频次,频次越高,代表客户粘性越强。
  • M(Monetary):统计周期内的消费总金额,金额越高,代表客户贡献价值越大。

但这个模型拿到美容院场景需要做本地化调整。美容院的消费特点是客单价高、周期性强(比如面部护理通常按月做一次)、套餐和会员卡形态复杂,所以我在设计时把三个维度的权重做了倾斜:消费金额权重最高(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 django==4.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_length=50, verbose_name='客户姓名') phone = models.CharField(max_length=20, db_index=True, verbose_name='手机号') gender = models.CharField(max_length=1, choices=GENDER_CHOICES, default='U', verbose_name='性别') birthday = models.DateField(null=True, blank=True, verbose_name='生日') level = models.CharField(max_length=1, choices=LEVEL_CHOICES, default='C', verbose_name='客户等级') balance = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='储值余额') preference = models.CharField(max_length=200, blank=True, verbose_name='偏好项目') remark = models.TextField(blank=True, verbose_name='备注') created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') class Meta: verbose_name = '客户信息' verbose_name_plural = verbose_name def __str__(self): return f'{self.name}({self.phone})'

这里有个经验之谈:db_index=True一定要给搜索频率高的字段加上。客户列表页肯定会按手机号或姓名搜索,没有索引的话,一旦数据量过万,查询性能就会明显下降,答辩现场演示时卡顿会非常尴尬。

消费记录模型放在consumption app里,核心是外键关联Customer和ServiceItem,同时冗余存一份成交价和到店批次号。

class ConsumptionRecord(models.Model): customer = models.ForeignKey('customer.Customer', on_delete=models.CASCADE, verbose_name='客户') service_item = models.ForeignKey('consumption.ServiceItem', on_delete=models.PROTECT, verbose_name='服务项目') amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='成交金额') visit_batch = models.CharField(max_length=20, db_index=True, verbose_name='到店批次号') consume_date = models.DateField(db_index=True, verbose_name='消费日期') operator = models.CharField(max_length=50, blank=True, verbose_name='操作员工') remark = models.TextField(blank=True, verbose_name='备注') created_at = models.DateTimeField(auto_now_add=True, 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__icontains=keyword) | Q(phone__icontains=keyword) ) return qs

URL路由配置上加一条关键代码:path('customers/', CustomerListView.as_view(), name='customer_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_days=90): """计算单个客户的价值评分,返回总分和等级""" cutoff_date = timezone.now().date() - timedelta(days=window_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__gte=cutoff_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__gte=cutoff_date ).aggregate(total=Sum('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_spent=Sum('consumptionrecord__amount') ).order_by('-total_spent')

再用prefetch_related预加载每客户近90天的消费记录,避免在循环里触发N+1查询。这样优化后,即使客户数据量到了上万条,页面响应时间也能控制在可接受范围内。

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 utf8mb4,settings中配置OPTIONS={'charset':'utf8mb4'}
查询不到按时区过滤的数据未设置USE_TZ和TIME_ZONEsettings.py中设置USE_TZ=True、TIME_ZONE='Asia/Shanghai',消费日期用DateField而非DateTimeField
Admin后台样式错乱Django自带的静态文件未收集部署前执行python manage.py collectstatic,并配置STATIC_ROOT
分页后搜索条件丢失分页链接未携带GET参数模板翻页链接中手动拼接request.GET.urlencode()
外键关联数据被误删未设置合适的on_delete策略业务数据关联用PROTECT,主数据关联用CASCADE,根据业务语义逐字段确认
列表页加载缓慢未使用prefetch_related导致N+1查询列表页的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模板,做出来就是一个平平无奇的增删改查系统;但一旦深入思考了“什么样的客户才是好客户”“如何用数据描述客户价值”这些问题,项目的深度和答辩的支撑材料就完全不一样了。后面如果有时间,我还会把这个系统往客户流失预警、智能营销推荐的方向扩展,那又是另一篇博文的故事了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询