最近有个学弟在准备毕业设计,问我能不能做一个“有点工作量、又不至于烂大街”的题目。他本身学过Python,也搭过Django项目,但不想再做那种简单登录注册加CRUD了。我给他推荐了一个方向:基于Python的电商商品销售可视化分析与协同过滤推荐系统,并且以潮流电商平台(得物这类业务模型)作为研究对象。这个题目覆盖面很广,既有数据分析、可视化大屏,又有推荐算法、Web开发,还能挂上大模型Agent、算法优化这些热点,工作量足够,答辩也有故事可讲。
这篇文章就把这个系统的完整设计思路、技术选型、核心算法实现、大模型扩展和踩坑经验一次性讲清楚。适合正在做毕业设计、项目实训,或者想系统接触数据分析加推荐系统开发的读者。不管你是刚入门Django,还是已经写过几个小项目想往算法方向靠一靠,这篇文章都能给你一套可以直接复用的设计方案。
1. 项目整体设计思路与功能拆解
1.1 这个系统的定位与核心价值
先把这个系统到底要做什么说清楚。它不是一个单纯展示数据的后台,而是把一个电商平台的“数据资产”转化成两个可用的产品功能:一是给运营和管理者看的销售可视化分析大屏,二是给普通用户用的个性化商品推荐。这两块业务切得越清楚,后面的架构设计就越顺。
从功能上看,系统需要落地四件事:
- 对商品销售数据进行多维度统计分析,比如销量趋势、品牌分布、价格带分布、用户地域分布、热销商品排行等;
- 用可视化大屏把这些统计结果直观呈现出来,让非技术人员也能一眼看懂业务情况;
- 构建用户-商品交互数据集,用协同过滤算法给用户生成个性化推荐列表;
- 将推荐结果嵌入Web系统,同时保留管理后台、用户登录、数据管理等基础闭环,让整个项目“像真正的产品”而不是Demo。
从毕设评分角度看,这个题目同时覆盖了数据分析、机器学习算法、Web全栈开发三个能力域,逻辑完整,技术深度也有保障。
1.2 技术选型背后的考虑
选技术栈不是越新越好,关键看能不能快速实现、稳定运行、方便答辩讲解。
后端选择Django,理由有三个:一是Django自带ORM、Admin后台、认证系统和模板引擎,能省下大量重复开发时间;二是Django的MTV架构非常规整,写论文画架构图很好讲;三是Django社区资料多,遇到问题随便一搜就有答案。Flask虽然轻量,但这个项目涉及后台管理、ORM、数据迁移,用Flask会让代码结构散掉。
可视化选择ECharts,是因为它的中文文档完善、图表类型丰富,对处理销售类数据展示有非常成熟的方案。相比之下,Matplotlib更适合做静态分析图和论文插图,不适合做交互式Web大屏。Pyecharts也可以考虑,本质就是Python封装ECharts的API,如果更习惯纯Python写法,用pyecharts也完全没问题,后面我会细讲这两者的取舍。
数据库选MySQL,这是国内毕设和中小型项目的标配,部署资料多,Navicat等可视化工具也方便录制演示视频。如果本机没装MySQL,SQLite也能先跑起来,但强烈建议直接用MySQL,因为MySQL 8.0的窗口函数做同期对比、排名分析等SQL聚合会比SQLite方便得多。
推荐算法选协同过滤,是因为这个项目的数据形态天然适合UserCF和ItemCF。协同过滤不依赖商品内容特征,只需要用户对商品的交互记录(浏览、收藏、加购、购买、评分),这正好和电商销售数据的场景吻合。相比直接用深度学习模型,协同过滤更容易在有限数据量下训练,也更方便论文里画算法流程图。
1.3 系统模块划分与数据流
整个系统的数据流可以分成五层:
数据采集层 → 数据存储层 → 数据处理层 → 算法推荐层 → 展示与应用层
标题里提到的CSV或Excel格式,项目采用自动化ETL脚本处理:抓取商品信息、清洗字段、写入MySQL数据库。由于涉及第三方电商平台,抓取时要遵循网站规则,控制频率和并发,仅供学习研究使用,不得用于商业用途。如果没有稳定的数据来源,手工构造一套符合业务逻辑的模拟数据也是可行方案,这部分我会在第二章详细说明。
数据处理层主要做两件事:一是把数据表按维度进行预聚合,生成可视化接口需要的数据结构;二是构建协同过滤需要的用户-物品评分矩阵。很多人在这一步会走弯路,把大量计算放在前端或视图里做,导致接口响应很慢,后面第三章我会给出后端计算聚合的正确姿势。
算法推荐层是系统的核心亮点。离线阶段训练好物品相似度矩阵或用户相似度矩阵,在线阶段接收用户请求,快速生成Top-N推荐列表。如果还接入了大模型Agent,这一层还会多一个“语义理解与文本生成的调度器”,第五章专门讲。
展示与应用层则是Django模板渲染的Web页面,通过Ajax异步请求可视化接口,把大屏和推荐结果渲染到前端。
2. 环境准备与数据模型设计
2.1 开发环境与依赖库清单
推荐环境是Windows 10/11 + Python 3.10 + PyCharm,这几个版本组合比较稳。需要安装的依赖库,建议直接用requirements.txt管理:
Django>=4.2 djangorestframework>=3.14 mysqlclient>=2.1 pandas>=2.0 numpy>=1.24 scikit-learn>=1.3 scipy>=1.10 pyecharts>=2.0 requests>=2.31 celery>=5.3 redis>=4.5 django-redis>=5.2 openai>=1.0其中scikit-learn和scipy是用来辅助协同过滤计算的,pandas和numpy做数据清洗与矩阵运算,celery与redis用来做异步推荐和缓存热门数据。大模型调用我放在后面扩展部分,核心推荐功能可以先不依赖它。
安装时有一个常见坑:mysqlclient在Windows上经常装不上。如果遇到报错,直接去https://pypi.org/project/mysqlclient/#files下载对应Python版本的whl文件安装,或者改用pymysql并在项目__init__.py里加一句:
import pymysql pymysql.install_as_MySQLdb()这两个方案实测都能解决。
2.2 数据来源与数据处理流程
关于数据来源,我在这里先说清楚一个原则:直接抓取第三方平台的实时数据,只能用于个人学习和学术研究,不能用于商业用途。所以在毕设场景下,我推荐两条路混着走:
第一条是构造仿真数据。按照得物这类平台常见的商品品类(潮鞋、服饰、数码、潮玩、配饰)生成几千条商品记录,再按照长尾分布生成几万条用户行为记录。这样做的好处是数据完全可控,字段干净,方便算法调参,坏处是缺乏真实感。所以我在项目里做了一个数据混合策略:部分来源于人工构造仿真数据,部分来源于公开数据集的脱敏数据。这样既有业务真实感,又规避了合规风险。
第二是自己造一个“数据爬虫脚本”。如果确实想演示数据采集能力,也可以用requests爬一些公开的电商展示数据。但务必要做好限速、去重、容错,避免给对方服务器造成压力。不管用哪种方式,最终都要输出一份统一格式的CSV文件,方便后续入库。
我处理数据的通用脚本长这样:
import pandas as pd # 读取原始数据,注意编码 df = pd.read_csv('raw_sales.csv', encoding='utf-8') # 基础清洗:去空、去重、统一时间格式 df = df.dropna(subset=['order_no', 'user_id', 'product_id']) df = df.drop_duplicates(subset=['order_no']) df['order_date'] = pd.to_datetime(df['order_date']) # 衍生字段:月份、季度、年龄段 df['month'] = df['order_date'].dt.month df['quarter'] = df['order_date'].dt.quarter # 检查是否泄露,简单分组统计 print(df.groupby('category')['sales_amount'].sum()) # 导出清洗后的文件 df.to_csv('clean_sales.csv', index=False, encoding='utf-8-sig')这里特别注意:导出CSV时编码要用utf-8-sig,否则用Excel打开会乱码。Django读取时也需要指定编码,不然会报UnicodeDecodeError。这是我早期踩过的坑,后面专门讲。
2.3 Django数据模型的核心表结构
数据模型是整个系统的基础。我设计了五张核心表,放在models.py里:
User表:扩展Django自带的用户模型,额外加上nickname、gender、age_group、region、favorite_categories字段。推荐系统需要用户的属性信息,直接在自定义表中维护比每次实时到认证系统查更方便。
Product表:商品表,字段包括product_name、category、brand、price、style、stock、sales_volume、release_date、image_url。协同过滤只依赖商品ID,但可视化分析需要这些属性维度,所以商品表是沟通两个模块的桥梁。
Behavior表:用户行为表。这是整个推荐系统的“燃料”,我用一个behavior_type字段区分view、collect、cart、purchase四种行为,再给每种行为赋予不同权重,作为协同过滤评分矩阵的依据。字段包括user、product、behavior_type、quantity、create_time。
Order表:订单表。记录订单号、下单时间、商品、数量、实际支付金额、用户ID。销售可视化的核心数据源就是这张表。
RecommendLog表:推荐日志表。记录每次为用户生成的推荐列表和推荐算法版本。这张表在算法优化时非常有用,可以用来做A/B对比,也是论文中“系统评估”部分的重要支撑。
额外说明一个细节:为了查销量趋势方便,我在Order表里冗余了order_date和month字段。虽然这违反数据库第三范式,但对报表查询来说性能提升非常明显。数据分析场景下,适当冗余是合理的工程取舍。
2.4 数据初始化脚本的写法
光有模型还不够,得写一个可重复执行的初始化脚本。我在项目根目录下建了一个scripts包,里面放init_data.py:
import os import django from django.db import transaction os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sales_system.settings') django.setup() from apps.goods.models import Product, Behavior, Order from apps.users.models import UserProfile import pandas as pd def load_products(csv_path): df = pd.read_csv(csv_path, encoding='utf-8-sig') products = [] for _, row in df.iterrows(): products.append(Product( product_name=row['product_name'], category=row['category'], brand=row['brand'], price=row['price'], stock=row['stock'], sales_volume=row['sales_volume'], release_date=row['release_date'] )) with transaction.atomic(): Product.objects.all().delete() Product.objects.bulk_create(products, batch_size=1000) print(f'load {len(products)} products done')用bulk_create批量插入,效率比逐条create高几十倍,几万行数据几秒就能导入。同时用transaction.atomic()保证如果半途出错,数据不会处于半导入状态。每次初始化前先清空旧数据,保证脚本可以重复执行,这对答辩前反复调试非常友好。
3. 销售可视化分析模块的实现要点
3.1 可视化大屏的整体布局思路
大屏的核心价值是“让看的人几秒钟内理解业务状况”。所以布局不能随便堆图表,要按人的视觉动线设计。
我采用的是经典的三段式布局:
顶部是核心KPI指标栏,从左到右排列:累计销售额、订单总数、活跃用户数、热销商品数。这四个数字一定要用醒目的大号字体,并且要有环比/同比的增长率箭头。
中部左侧放销售趋势折线图,展示最近12个月的销售额变化;中部中间放品类销售占比饼图和品牌Top10柱状图;中部右侧放地域热力地图和价格带分布图。
底部留一块滚动区域,显示实时订单列表和热门推荐商品,给大屏增加“活”的感觉。
这套布局很讲究“上数据、中趋势、下构成”,不管是从阅读习惯还是答辩效果来看,都是最稳的选择。前端页面我建议直接用原生的HTML + CSS + ECharts,通过Ajax从后端拉JSON数据渲染,不要用现成的DataV或大屏模板。原因有两个:一是自己写能更好地讲清楚每个图表的配置逻辑,二是答辩时老师大概率会问“你这个图表数据是怎么从后端取出来的”,用原生Ajax + ECharts回答起来最简单。
3.2 核心图表选择与ECharts配置技巧
不同的业务分析问题对应不同的图表,这里给出我最终的选型方案:
| 分析目标 | 推荐图表 | ECharts系列 |
|---|---|---|
| 销售额月度趋势 | 折线图/面积图 | line |
| 品类销量占比 | 环形饼图 | pie |
| 品牌销量排行 | 横向柱状图 | bar |
| 商品价格带分布 | 直方图/漏斗图 | bar / funnel |
| 用户地域分布 | 中国地图热力图 | map |
| 热销商品榜 | 横向条形图 | bar |
| 用户年龄段分布 | 玫瑰图 | roseType pie |
ECharts配置里有一个非常关键的技巧:不要让后端直接返回ECharts的完整option对象,而是返回最朴素的二维数据。举个例子,后端只需要返回:
{ "months": ["2024-01", "2024-02", "..."], "sales": [120.5, 98.3, "..."] }前端拿到数据之后,再拼装成ECharts需要的{xAxis: ..., series: ...}结构。好处是接口复用性高,以后换图表库、换设备都不用改后端。很多人喜欢在后端用pyecharts生成完整HTML嵌入模板,这对单个独立图表可行,但对大屏这种多图表联动场景会很别扭,因为页面刷新时所有图表要重新渲染,无法做局部更新。
还有两个实操细节要注意。第一个是大屏自适应,ECharts实例创建后一定要监听窗口变化,调用chart.resize()。第二个是Tooltip格式化,销售金额动辄几十万,显示时要保留单位,或用tooltip.formatter把数字转成“12.5万”这样的格式,否则大屏会满屏是超长的数字串,很难看。
3.3 后端聚合接口的设计实现
可视化的所有数据都应该由后端聚合后提供。我这里在views.py里写了一个集中的仪表盘接口,用Django ORM的聚合查询直接完成统计,避免在内存里用Python做二次聚合,那样除了慢没有别的效果。
以销售趋势接口为例:
from django.db.models.functions import TruncMonth from django.db.models import Sum, Count from django.http import JsonResponse from apps.order.models import Order def sales_trend(request): rows = ( Order.objects .filter(status='paid') .annotate(month=TruncMonth('order_date')) .values('month') .annotate( total_sales=Sum('pay_amount'), total_orders=Count('order_no') ) .order_by('month') ) data = { 'months': [r['month'].strftime('%Y-%m') for r in rows], 'sales': [float(r['total_sales']) for r in rows], 'orders': [r['total_orders'] for r in rows], } return JsonResponse(data)这套接口尽量走“一个请求出一块数据”的模式。前端页面加载后再并发发多个Ajax请求,每个图表对应一个接口。这样每个图表的加载是独立的,个别图表报错了也不会影响全屏展示。另外记得在Django的settings.py里加上跨域配置。如果前后端不分离,只是模板渲染的话不会有跨域问题,但如果想单独调试前端页面,最好还是配上:
INSTALLED_APPS = [ ... 'corsheaders', ] MIDDLEWARE = [ ... 'corsheaders.middleware.CorsMiddleware', ] CORS_ALLOW_ALL_ORIGINS = True # 仅限开发调试CORS_ALLOW_ALL_ORIGINS生产环境不要开,开发期无所谓。
4. 协同过滤推荐系统的算法实现
4.1 协同过滤的原理:UserCF和ItemCF对比
协同过滤的核心假设是“相似的人喜欢相似的东西”或“相似的商品会被相似的人喜欢”。对应到算法上,就是UserCF和ItemCF两种思路。
UserCF先找与当前用户兴趣最相似的K个用户,再把这K个用户喜欢的、当前用户没看过的商品推荐给他。ItemCF则反过来,先根据所有用户的历史行为计算商品之间的相似度,然后推荐与用户历史喜欢商品相似的商品。
电商场景下,我更推荐以ItemCF为主、UserCF为辅。原因很简单:电商用户的行为多、商品更新快,但用户的兴趣可能很快变化。ItemCF推荐出来的结果是“和某商品相似的商品”,解释性更强,而且商品相似度矩阵可以离线算好存起来,在线响应速度快。UserCF适合用户量相对稳定、兴趣更持久的场景,比如资讯类App。这个差异在答辩时可以讲得很出彩。
4.2 相似度计算与矩阵构建的具体实现
协同过滤的输入是一张用户-物品评分矩阵。实际项目中,用户行为不是评分,所以我把四种行为映射成不同权重:
- 浏览:1分
- 收藏:3分
- 加购:4分
- 购买:5分
这样就把隐式反馈转成了显式评分。为什么要这么设计?因为直接二值化(有过行为记1,没有记0)会丢失行为的强度信息,而实际的收藏、加购行为的确比浏览更能反映用户兴趣。
矩阵构建和相似度计算我用scipy.sparse来做,避免内存爆炸:
import numpy as np from scipy.sparse import csr_matrix from sklearn.metrics.pairwise import cosine_similarity # 假设 user_item_matrix 是稀疏矩阵,行=用户,列=商品,值=行为权重 def compute_item_similarity(user_item_matrix): # 商品-用户矩阵 = 用户-商品矩阵的转置 item_user_matrix = user_item_matrix.T item_sim = cosine_similarity(item_user_matrix) np.fill_diagonal(item_sim, 0) return item_sim讲一个很容易翻车的地方:不要把用户-商品矩阵直接转成稠密DataFrame去算相似度。如果有一万用户、一千商品,稠密矩阵就是1000万个数,内存早爆了。用稀疏矩阵存0值,内存占用直接缩小一个量级。
另外还要对相似度做“同品牌/同品类惩罚或加成”。我有一次做出来的推荐结果全是同一品牌的高价商品,后来在相似度矩阵上叠加了一个品类相似系数:同品类的相似度乘以1.2,不同品类乘以0.8,这样推荐结果的多样性明显改善。
4.3 完整的推荐流程代码
推荐流程分两个阶段。离线阶段,Django启动后或者通过定时任务计算物品相似度矩阵并缓存到Redis;在线阶段,用户请求时动态计算未交互商品得分。
离线训练代码:
def train_item_cf(): # 1. 从数据库构建行为矩阵 behaviors = list(Behavior.objects.values_list('user_id', 'product_id', 'behavior_type')) user_idx = {} item_idx = {} row, col, data = [], [], [] weight_map = {'view': 1, 'collect': 3, 'cart': 4, 'purchase': 5} for uid, pid, btype in behaviors: if uid not in user_idx: user_idx[uid] = len(user_idx) if pid not in item_idx: item_idx[pid] = len(item_idx) row.append(user_idx[uid]) col.append(item_idx[pid]) data.append(weight_map.get(btype, 1)) matrix = csr_matrix((data, (row, col)), shape=(len(user_idx), len(item_idx))) item_sim = compute_item_similarity(matrix) # 2. 存储映射关系 redis_conn.set('item_index_map', json.dumps({str(k): v for v, k in item_idx.items()})) redis_conn.set('item_user_matrix', matrix.tobytes()) # 将相似度矩阵存入 Redis 或文件,供在线阶段使用在线推荐:
def recommend_for_user(user_id, top_n=10): user_item_row = get_user_vector(user_id) # 该用户的稀疏行为向量 if user_item_row.nnz == 0: return get_hot_products(top_n) # 冷启动回退策略 # 计算用户对每个物品的兴趣分 = 行为得分向量 × 物品相似度矩阵 score = user_item_row.dot(item_sim_matrix) # 过滤已交互商品 interacted = set(user_item_row.indices) ranking = [(score[0, i], idx) for idx in range(score.shape[1]) if idx not in interacted] ranking.sort(reverse=True, key=lambda x: x[0]) return [item_id for _, item_id in ranking[:top_n]]这里用的是一个简单的矩阵乘法来计算得分,数学本质是:用户对目标商品的兴趣 = 用户对历史商品的行为权重 × 这些历史商品和目标商品的相似度,然后求和。比遍历每个历史商品再累加相似度快得多,这也是算法优化的核心。
离线矩阵训练完之后,会根据新增的订单、行为数据定期更新矩阵。一种做法是每天凌晨用Celery定时任务重算一次,另一种做法是在用户产生新行为时触发增量更新。毕设项目用定时任务即可,增量更新消耗的时间不值得。
4.4 冷启动问题与混合策略
协同过滤有个躲不开的缺陷:冷启动。新用户没有行为,新商品没有交互记录,算法给不出任何推荐。我用三种策略兜底:
第一,热门榜兜底。新用户默认展示最近15天销量最高、收藏最多的商品,用Redis缓存这个榜单,避免每次都查库。
第二,基于属性的相似推荐。对新商品,用品牌、品类、价格带组成特征向量,先和已有商品算相似度,替代行为协同过滤。虽然这本质上变成了“基于内容的推荐”,但作为冷启动阶段的弥补非常有效。
第三,规则加权混合。最终推荐列表按5:3:2的比例混合:50%来自ItemCF结果,30%来自热门商品补充,20%来自同品类热门、同品牌新款等规则商品。这个比例可以做成配置项,答辩时也可以说成是“可调节的混合推荐策略”,比单一算法更有说服力。
我试过很多次,纯协同过滤的推荐结果容易陷入“全部推荐爆款”或者“全部推荐太冷门”两个极端。混合策略虽然看起来土,但用户的点击率和满意度反而是最高的。这个结论在我做的离线测试上表现也很稳定。
5. 大模型Agent扩展与算法优化
5.1 引入大模型后的系统形态
标题里的“大模型 Agent”不是噱头。现在很多电商系统的毕设都会带一个“智能导购”功能,用大模型来做自然语言交互。比如用户输入“想找一双500块左右的休闲运动鞋”,系统能理解意图,结合后端数据和推荐算法,返回一个带解释的商品推荐结果。
核心思路是:大模型负责“理解+表达”,推荐算法负责“计算+召回”。大模型不做数值计算,但会把你给的候选商品列表重新组织成一句自然语言的推荐语。因此在系统里,大模型Agent是一个调度层,也是一个表达层。
整体调用链如下:
用户在对话窗口输入一句话 → 大模型做意图识别和关键信息抽取(价格范围、品类偏好、使用场景) → 后端用抽取出的条件召回候选商品 → 协同过滤生成个性化排序 → 大模型把最终Top N商品转成一段推荐语返回给用户。
5.2 核心代码:Django中调用大模型接口实现智能导购
我用OpenAI格式的接口兼容层来统一调用,这样不管是哪家大模型,只要兼容OpenAI协议都能接入。Django的api/views.py里写了一个对话接口:
import json, re from openai import OpenAI from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt # 初始化一个客户端(这里用环境变量管理密钥) client = OpenAI( api_key=os.getenv('LLM_API_KEY'), base_url=os.getenv('LLM_BASE_URL') ) @csrf_exempt def chat_recommend(request): if request.method == 'POST': data = json.loads(request.body) user_input = data.get('message', '') user_id = data.get('user_id') # 第1步:用大模型抽取结构化查询条件 sys_prompt = ( "你是一个导购助手,请从用户的话里抽取商品检索条件," "只输出JSON,格式为:" '{"category": "运动鞋", "max_price": 600, "min_price": 300, ' '"brand": null, "scene": "日常休闲"}' ) resp = client.chat.completions.create( model=os.getenv('LLM_MODEL_NAME'), messages=[ {"role": "system", "content": sys_prompt}, {"role": "user", "content": user_input} ], temperature=0.1 ) try: query_cond = json.loads(resp.choices[0].message.content) except Exception: query_cond = {} # 第2步:根据条件召回候选商品 candidates = filter_products_by_condition(query_cond) # 第3步:协同过滤生成个性化排序 ranked = itemcf_rank(user_id, candidates) # 第4步:让大模型组织最终推荐语 product_text = ";\n".join( [f"{p.name},{p.brand},价格{p.price}元" for p in ranked[:5]] ) final_resp = client.chat.completions.create( model=os.getenv('LLM_MODEL_NAME'), messages=[ {"role": "system", "content": "你是电商导购,请根据商品清单生成简洁推荐语,不要编造商品"}, {"role": "user", "content": f"用户需求:{user_input}\n候选商品:{product_text}"} ], temperature=0.7 ) return JsonResponse({"reply": final_resp.choices[0].message.content})这个接口有几点值得注意:第一,第一轮调用的temperature要设得很低(0.1左右),确保输出JSON稳定;第二轮可以适当调高一点,让推荐语更自然。第二,所有密钥用环境变量管理,不要硬编码在代码里,这是安全底线,答辩时也能加分。第三,候选商品列表要先经过算法排序再交给大模型,否则大模型可能会被无关商品带偏,生成的推荐语质量参差不齐。
5.3 算法层面的几种实用优化
协同过滤虽然经典,但能优化的点非常多。我在这套系统里做了三个改进,都是有实测效果支撑的:
第一个:对相似度做“同品牌惩罚”。原版ItemCF很容易把同一品牌的不同商品互相推荐,导致推荐列表全是同品牌产品。我给相似度矩阵做了一个品牌掩码:不同品牌时相似度乘以0.85,同品牌但不同品类时乘以0.6。这样既保留了同品牌推荐,又避免推荐结果过于单一。要注意,这一步很考验业务理解,答辩时讲出来会显得你考虑得很周全。
第二个:增加时间衰减因子。用户三个月前买过的商品和昨天刚看过的商品,对兴趣的贡献应该不一样。我在用户行为向量里加入了时间权重系数:
import datetime import math def time_decay_weight(behavior_time): days_ago = (datetime.datetime.now() - behavior_time).days return math.exp(-0.02 * days_ago)把每个行为的分值乘上这个衰减系数,这样近期行为权重更大,推荐结果能跟随用户当前兴趣变化,而不是永远停留在几个月前的兴趣上。
第三个:用Top-K截断控制计算量。计算用户得分时,不需要让用户向量去乘整个商品相似度矩阵。我只会取用户历史交互商品中权重最高的K个商品,用他们的相似向量做聚合。K通常取50,这样在线响应时间能从几百毫秒降到几十毫秒。很多教材不会讲这个优化,但在真实推荐系统里这是最常用的一招。
大模型Agent这块还有一点值得扩展:可以在Django里注册一个Celery任务,定时把大模型的日志存到数据库,分析用户问得最多的是哪类商品,反哺到可视化模块。这样大模型和分析大屏就打通了,整个系统的故事线会变得非常完整。
6. 常见问题排查与部署心得
6.1 开发过程中的典型问题速查表
我整理了一份实际开发中高频踩坑的问题表,每条都是真实遇到过的:
| 问题现象 | 根本原因 | 解决方案 |
|---|---|---|
| CSV导入后中文乱码 | 文件编码不是UTF-8 | 读文件用encoding='utf-8-sig',导出也用utf-8-sig |
| mysqlclient安装失败 | Windows缺少编译环境 | 使用pymysql代替,或在PyPI下载whl文件 |
| 前端图表不显示 | Ajax请求返回了HTML页面而非JSON | 检查urls.py配置和视图函数return类型 |
| Redis连接超时 | Redis服务未启动或地址配置错误 | 先启动redis-server,再检查CACHES配置 |
| 协同过滤结果全是NaN | 归一化时除以0 | 检查是否所有行为都被过滤、某用户没有历史 |
| ECharts地图不显示 | 缺少中国地图GeoJSON坐标数据 | 在页面加载时引入china.js地图注册文件 |
| 同步请求导致页面卡顿 | 视图内做了大量for循环计算 | 改用ORM聚合、缓存计算结果,或走Celery异步任务 |
其中“CSV导入中文乱码”最常出现在答辩前夜的导入操作里,一定提前测试好。而“前端图表不显示”往往不是脚本问题,而是接口返回了500错误,浏览器把错误信息吞掉了。建议开发者工具打开Network面板看具体响应状态码,比盲目调代码有效得多。
6.2 部署与上线的一些提醒
如果要在服务器上演示或者跑给老师看,我建议用一台Linux云主机,部署方式按这套流程操作:
首先安装MySQL、Redis和Python环境,然后用git clone把项目代码拉到服务器,创建虚拟环境并安装依赖。接着修改settings.py里的数据库配置和ALLOWED_HOSTS,执行python manage.py migrate和python manage.py collectstatic。
启动时用gunicorn配合nginx做反向代理:
gunicorn sales_system.wsgi:application -b 127.0.0.1:8000 --workers=3nginx配置一个server块,把80端口代理到8000端口即可。这样服务器上就不需要一直开着Django的开发服务器了。
还有几个容易漏的点:第一,Django的DEBUG必须设成False,否则会泄露服务器配置信息,答辩时有老师会专门看这个;第二,ALLOWED_HOSTS里要填服务器公网IP或域名,不然访问会直接403;第三,collectstatic之后记得让nginx托管静态目录,不然大屏的CSS和JS全部加载不出来。
如果不想折腾服务器,也可以用Docker Compose把MySQL、Redis、Django三件套打包,一键启动。这个方案更利于答辩现场演示,状态会不会因为操作失误而崩掉。
6.3 一些个人的心得
最后分享一点我自己做这类项目最深的体会。
第一个经验:保证数据先通,算法后上。很多同学上来就去调协同过滤代码,结果数据表还没建好,一直在跑空数据。正确的顺序应该是先把数据管理、可视化接口跑通,让系统“有东西可以看”,再去做推荐模块。这样每个阶段的进度都看得到,也更容易坚持下去。
第二个经验:算法结果一定要留日志和评估过程。我在系统里加了推荐日志表和A/B对比接口,可以把协同过滤推荐、热门推荐、混合推荐三种策略的结果都记录下来,然后算覆盖率、点击率、收藏率这些指标。答辩时拿出这些评估数据,远比空口说“推荐效果好”有说服力。论文里的实验章节也全靠这些数据撑着。
第三个经验:系统设计一定要画图、讲话自成体系。毕设答辩时老师不一定会细看代码,但一定会让你讲架构。建议画一张包含“数据采集-数据处理-数据存储-推荐引擎-可视化展示”的分层架构图,再画一张协同过滤的算法流程图。这两张图就能把工作量说清楚,把“为什么要用协同过滤”而不是随便选一个算法讲的明明白白。
这个项目做完,基本上Web开发、数据分析、机器学习、大模型Agent调用这条链路你都能走一遍。后面再想扩展,可以考虑接实时流数据做动态推荐,或者把推荐日志接入一个大模型报表Agent,自动生成每天的运营总结。方向很多,先把当前的系统跑稳,再慢慢往深处挖。