☰
Django+LSTM构建用户购物行为分析与预测系统
2026/9/26 13:04:38 网站建设 项目流程

先说点实际的。每年到了毕设季节,淘宝上搜"大数据毕设"能翻出来几百个同质化题目,但你仔细看会发现,绝大多数都是把几个开源项目拼一下、换个皮就拿出来卖。真正能让你在答辩现场站得住、被老师追问细节时不慌的,是你自己清楚这套系统每一层是怎么转起来的。我这个题目的核心是:"用户购物行为"这条数据链,从采集、清洗、存储、可视化展示,到最后的深度学习预测,完整走一遍。这篇文章我把这套系统的设计思路、核心代码实现、还有我在调试过程中踩过的坑全部写出来,给正在做同类题目的同学一个参考。

你看到标题里的"淘宝用户购物"别紧张,这里的数据并不是真的去爬淘宝(那既违规也爬不动),而是基于公开的电商行为数据集来构建用户画像和购物路径。整套系统的骨架是Django,深度学习部分我用的是序列模型做行为预测,可视化部分走的是ECharts大屏方案。适合谁看:正在做电商类、大数据类毕设的同学,以及想把"Django + 深度学习"这个组合真正跑通、搞懂原理的开发者。

1. 整体架构设计:为什么是Django而不是Spring Boot

1.1 技术选型的真实考量

我见过很多同学一上来就问"老师,能不能用Java写?",其实对于大数据可视化这类偏重数据处理和展示的题目,Python生态的优势太明显了。你在数据处理阶段要用Pandas做清洗聚合,在预测阶段要调TensorFlow或PyTorch的模型,在展示阶段后端要随时响应前端图表请求——这三个环节如果用Java,你得在Spring Boot里硬写JDBC调Hive,还得另外搞一套Python服务来做模型推理,系统复杂度翻了不止一倍。

Django最大的优势在于"自带全家桶":ORM让你不用写一行原生SQL就能操作MySQL里的用户行为表;Admin后台可以让你快速核对数据有没有清洗干净;自带的模板系统和DRF(Django REST Framework)让前后端交互变得非常规整。最关键的一点:Python的深度学习库在Django里直接import就能用,你训练好的模型文件就是一个.h5或者.pth,在视图函数里load进来就能预测,这个链路短到让人觉得"这不科学"。

1.2 系统分层与数据流向

我用一张流程把整个系统的数据流向讲清楚,你把这个逻辑记在脑子里,比背代码重要得多:

原始行为日志(CSV/Excel) ↓ 数据清洗:去重、缺失值处理、时间戳格式化 ↓ MySQL数据库(用户表、商品表、行为表) ↓ Django ORM读取 + Pandas聚合统计 ↓ Redis缓存热点统计结果(30秒过期) ↓ RESTful API输出JSON ↓ 前端ECharts大屏渲染 ↓ (并行)用户行为序列 → LSTM模型 → 预测结果回写

这套流程里有几个关键设计决策我解释一下:

  • 为什么中间加Redis?大屏页面通常是秒级刷新,如果每一次前端轮询都直接查MySQL,在数据量到百万级时聚合查询可能要两三秒,大屏就变成"幻灯片"了。用Redis做一层缓存,把最近30秒内的聚合结果直接给前端,既保证了实时性又不至于把数据库打爆。
  • 为什么行为预测单独走一条线?因为预测模块的响应速度不可能做到毫秒级,即使我用的是轻量级LSTM,单条序列推理也需要几十毫秒。所以预测模块是"异步的":系统先把用户最近N次行为序列发给模型,模型返回"购买概率、偏好品类、可能下单时间段",然后把这个预测结果存回MySQL,可视化页面读取的是这个结果而不需要实时跑模型。

1.3 所谓"大数据"到底数据量多大才够?

说实话,真正做毕设,你说要搭一套Hadoop集群处理TB级数据,既不现实也没必要。我这套系统用的数据集大概在100万条行为记录量级,MySQL单表存储完全能扛住。但为了体现"大数据"的处理流程,我在清洗阶段做了三个关键动作:去重(同一用户同一秒点击同一商品视为误操作)、会话划分(连续30分钟无操作视为会话中断)、特征构造(计算用户平均点击深度、加购转化率、品类集中度)。

这个体量说不上"大数据",但整个数据流水线是完整的,答辩时你只需要说清楚一个点:如果数据量再大10倍,只要把MySQL换成ClickHouse,把ORM查询换成预计算离线结果,架构依然成立。这个"演进能力"就是老师想听到的东西。

2. 数据层实现:从原始日志到可分析的结构化数据

2.1 Django模型设计的关键细节

三个核心模型别偷懒,字段设计直接决定了后端的查询效率和你做特征工程时是否顺手。我的设计如下:

# models.py class User(models.Model): user_id = models.IntegerField(primary_key=True) age_group = models.CharField(max_length=20) gender = models.CharField(max_length=10) occupation = models.CharField(max_length=50, default='unknown') class Product(models.Model): product_id = models.IntegerField(primary_key=True) category = models.CharField(max_length=50) price = models.FloatField() brand = models.CharField(max_length=50, blank=True) class Behavior(models.Model): id = models.AutoField(primary_key=True) user = models.ForeignKey(User, on_delete=models.CASCADE) product = models.ForeignKey(Product, on_delete=models.CASCADE) behavior_type = models.CharField(max_length=10) # 取值:pv(浏览)、cart(加购)、fav(收藏)、buy(购买) timestamp = models.DateTimeField() class Meta: indexes = [ models.Index(fields=['user', 'timestamp']), models.Index(fields=['behavior_type']), ]

这里有几个你如果自己写会忽略的细节:

  • Behavior表必须加联合索引。你后面统计"某用户的时间序列"要频繁执行WHERE user_id=xxx ORDER BY timestamp,没有索引的话百万级数据直接全表扫描,一次查询5秒起步。索引加上后能压到几十毫秒。
  • 行为类型用CharField而不是IntegerField。有些开源项目用0/1/2/3表示行为类型,写起来省事,但你在做可视化时要映射回中文标签,多一层不说,调试时你还得背数字含义。直接用pv/cart/fav/buy,代码可读性好一万倍。
  • 外键关联别省。虽然Django ORM查外键会自动JOIN,但你在统计"品类维度"时要通过behavior__product__category这类跨表查询,没有外键关系这一步会非常痛苦。

2.2 数据清洗:我用Pandas做了什么处理

原始数据集(我用的公开电商数据集,格式是CSV)问题很多,常见的有:时间戳是Unix格式需要转换、部分用户ID是空的、同一秒内的重复点击。清洗脚本我单独写了一个data_processor.py,核心逻辑是这样:

import pandas as pd from datetime import datetime df = pd.read_csv('raw_data.csv') # 1. 去重:同一用户在同一秒对同一商品的行为只保留一条 df = df.drop_duplicates(subset=['user_id', 'product_id', 'timestamp']) # 2. 过滤异常值:price为负或者超过100万的数据删掉 df = df[(df['price'] > 0) & (df['price'] < 1000000)] # 3. 时间戳转换 df['timestamp'] = df['timestamp'].apply( lambda x: datetime.fromtimestamp(x).strftime('%Y-%m-%d %H:%M:%S') ) # 4. 缺失值处理:性别未知的填'unknown',不要直接删行 df['gender'] = df['gender'].fillna('unknown')

清洗时的教训分享一个:千万别直接删掉性别缺失的用户。电商行为数据里性别缺失非常常见,你直接删行,会导致样本偏差——留下来的人全是"愿意填资料"的,预测模型会学到错误规律。正确做法是保留并标记为unknown,让模型自己学习这个类别的模式,效果反而更好。

清洗完之后的数据导入MySQL,我用的是Django的bulk_create批量写入,一次性导入10万条需要大概15秒,如果用单条create循环,半小时起步。

3. 可视化大屏开发:ECharts + Django REST Framework

3.1 后端API如何设计才能应对大屏密集请求

大屏页面通常同时展示6-8个图表,如果每个图表单独请求一次接口,每次请求都要查一次数据库,前端的加载体验会很差。我的做法是一次聚合接口返回所有图表数据:

# api/views.py from rest_framework.views import APIView from rest_framework.response import Response from django.core.cache import cache import pandas as pd class DashboardDataView(APIView): def get(self, request): cached_data = cache.get('dashboard_data') if cached_data: return Response(cached_data) # 从ORM批量取数,转为DataFrame qs = Behavior.objects.select_related('product', 'user').all()[:50000] df = pd.DataFrame(list(qs.values( 'behavior_type', 'timestamp', 'product__category', 'product__price' ))) # 按小时统计活跃度 df['hour'] = pd.to_datetime(df['timestamp']).dt.hour hour_activity = df.groupby('hour').size().reset_index() # 按品类统计销量TOP10 cate_sales = df[df['behavior_type'] == 'buy'].groupby( 'product__category' ).size().nlargest(10).reset_index() result = { 'hour_activity': hour_activity.to_dict(orient='records'), 'category_top10': cate_sales.to_dict(orient='records'), # ... 其他图表数据 } cache.set('dashboard_data', result, timeout=30) return Response(result)

这里有一个对毕设来说非常重要的点:Django的ORM查询不擅长做聚合统计,你非要用ORM写Count加GroupBy也能写,但稍微复杂一点的分组就非常痛苦。我的经验是:ORM负责筛选数据,Pandas负责聚合计算,各干各最擅长的事。数据量在50万以内这个组合的性能非常好,实测接口响应在300毫秒以内。

3.2 用Redis缓存解决大屏轮询性能瓶颈

缓存本身不复杂,复杂的是"缓存了什么"和"缓存多久"。

大屏的实时性要求是"看起来在动",不是"每一秒都精确"。所以我设置缓存30秒过期,前端每10秒轮询一次,这样用户看到的是最多30秒前的数据,感官上就是实时的。如果设置1秒过期,Redis的命中率极低,等于每次都穿透到MySQL,缓存就没意义了。

Redis在Windows上调试有坑,很多同学装不上Windows版Redis。我的建议是直接用Docker拉一个redis:7镜像跑:

docker run -d --name redis -p 6379:6379 redis:7

Django端配置也很简单:

# settings.py CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': 'redis://127.0.0.1:6379/1', 'TIMEOUT': 30, } }

3.3 大屏布局与ECharts动态数据刷新

前端我没用复杂的Vue框架,直接用Django模板渲染一个大屏页面加原生JS轮询ECharts。布局采用最经典的"中间主图 + 两侧辅助图"模式:

  • 中间主图:用户行为转化漏斗(浏览→加购→收藏→购买)
  • 左侧上:每小时活跃度折线图
  • 左侧下:品类销量TOP10柱状图
  • 右侧上:用户画像雷达图
  • 右侧下:最新实时行为滚动列表

核心的轮询逻辑在这个JS片段里:

function fetchDashboardData() { fetch('/api/dashboard/') .then(response => response.json()) .then(data => { myChart.setOption({ series: [{ data: data.funnel_data }] }); // 其他图表同理 }); } // 每10秒刷新一次 setInterval(fetchDashboardData, 10000); fetchDashboardData();

这里有一个前端开发常见的坑:直接setOption在新数据不完整时会把旧图表的某些配置清掉。稳妥的做法是在setOption里加上notMerge: true,或者每次重新init图表。第一种方式更平滑,推荐。

4. 深度学习行为预测模块:不是所有"预测"都叫AI

4.1 我为什么选了LSTM而不是Transformer

题目要求的"行为预测",实际上要预测的是:根据用户最近N次行为序列,预测下一次行为是什么(浏览/加购/收藏/购买)。这本质是一个序列分类问题。

如果你关注深度学习领域,现在Transformer是主流,但在这个场景下用LSTM依然合理,原因有三:

  • 用户行为序列长度通常在几十到几百之间,LSTM在这类中等长度序列上效果不输Transformer;
  • LSTM的参数量小,CPU就能推理,不需要GPU,在答辩演示时你不用依赖实验室的显卡;
  • 从毕设的创新角度讲,LSTM的遗忘门机制可以和"用户兴趣衰减"做对应解释——用户的旧行为对当前预测的影响本来就应该逐渐减弱,这在学术上叫"时间衰减",用LSTM解释非常自然。

4.2 特征工程:把行为序列变成模型输入

原始数据不能直接喂给模型,需要做序列构建。每个用户的最近50次行为按时间排序,编码成ID序列,再padding到固定长度:

# sequence_builder.py from tensorflow.keras.preprocessing.sequence import pad_sequences def build_sequences(df, max_len=50): # 按用户分组,按时间排序,取出行为类型编码 user_groups = df.sort_values('timestamp').groupby('user_id')['behavior_type'] sequences = [] labels = [] for _, group in user_groups: # 行为类型编码: pv=0, cart=1, fav=2, buy=3 seq = [behavior_map[x] for x in group] if len(seq) < 10: continue # 滑动窗口:用前N-1个行为预测第N个 for i in range(10, len(seq)): sequences.append(seq[i-10:i]) labels.append(seq[i]) # padding统一长度 sequences = pad_sequences(sequences, maxlen=10) return sequences, labels

这里我踩过一个很大的坑:负样本严重不均衡。真实电商数据里pv占了80%以上,buy可能只有2%,如果你直接用原始数据训练,模型会学成"永远预测当前浏览",准确率看着95%,实际毫无价值。我的处理方式是对pv做下采样,对buy和cart做上采样,让四类行为比例控制在4:2:2:2左右,模型才能真正学到行为转化的模式。

4.3 模型训练与部署

模型结构不复杂,单层LSTM加全连接就能达到不错效果:

import tensorflow as tf model = tf.keras.Sequential([ tf.keras.layers.Embedding(4, 16, input_length=10), tf.keras.layers.LSTM(64, return_sequences=True), tf.keras.layers.LSTM(32), tf.keras.layers.Dense(16, activation='relu'), tf.keras.layers.Dense(4, activation='softmax') ]) model.compile( optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'] ) model.fit(x_train, y_train, epochs=10, batch_size=256, validation_split=0.2)

在100万行为数据上训练10个epoch,CPU大约要30分钟。建议训练完保存成behavior_prediction.h5,部署时Django直接load:

# prediction_service.py from tensorflow.keras.models import load_model import numpy as np _model = load_model('behavior_prediction.h5') def predict_next_behavior(user_recent_sequence): # user_recent_sequence: list of behavior ids, 如 [0,1,2,0,0] padded = pad_sequences([user_recent_sequence], maxlen=10) probs = _model.predict(padded, verbose=0)[0] # 返回概率最大的行为类型和置信度 behavior_names = ['浏览', '加购', '收藏', '购买'] idx = int(np.argmax(probs)) return { 'predicted_behavior': behavior_names[idx], 'confidence': float(probs[idx]), 'all_probs': { name: float(p) for name, p in zip(behavior_names, probs) } }

有一个细节必须要提醒:Django的视图函数在处理请求时是线程并发的,而TensorFlow模型默认不是线程安全的。如果你在视图里直接调用模型,高并发时会报错。我的做法是加一个线程锁:

import threading _model_lock = threading.Lock() def safe_predict(sequence): with _model_lock: return predict_next_behavior(sequence)

这个细节如果你在答辩时主动讲出来,老师会认为你真正考虑过生产环境的问题,加分不少。

5. 系统部署与展示:从开发机到答辩现场的完整流程

5.1 本地开发环境的坑与解决

我预估你大概率会有这几个问题:

Django版本与TensorFlow版本冲突。TensorFlow 2.x系列要求Python 3.6-3.9,而Django 4.x要求Python 3.8+,这个交集没问题。但如果你用Python 3.11装TensorFlow,恭喜你,准备折腾一晚上吧。我的建议:直接用Python 3.9 + Django 3.2 + TensorFlow 2.10,这个组合是我实测最稳的。

static文件加载不出来。这个问题在Django里十个新手九个遇到。模板里写{% load static %}<img src="{% static 'images/logo.png' %}">,但页面死活不显示,F12一看404。原因是Django开发服务器默认不处理static文件,需要在settings.py里加:

STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static']

代码没问题但缓存问题还可能出现——你改了CSS文件但页面没变,浏览器缓存了。Ctrl+F5强制刷新就好,不用怀疑代码写错了。

5.2 答辩演示时怎么保证不出意外

毕设答辩翻车现场十有八九是"网络问题"和"环境问题"。我的建议是:把所有服务全部本地化。MySQL用本机的,Redis用本机的,模型文件在本地加载,大屏页面走127.0.0.1访问。

一个非常实用的技巧:在演示前先把大屏页面所有图表数据加载好并截图,万一现场浏览器崩溃或服务启动失败,你至少可以用截图"将演示进行到底"。这个备份方案不丢人,反而说明你有风险意识。

5.3 说明文档和LW材料怎么组织

标题里提到的"说明文档和LW",其实就是你交给老师的过程材料和论文。我写文档时的组织顺序,你可以参考:

  1. 摘要和关键技术说明(Django、LSTM、ECharts各解决什么问题)
  2. 系统架构图和数据流图(用Visio或draw.io画)
  3. 数据库设计说明(三张表的字段含义和关联关系)
  4. 核心功能截图(大屏效果、预测结果页面)
  5. 项目运行部署文档(环境配置、启动步骤)
  6. 系统测试报告(功能测试用例表、性能测试结果)
  7. 总结与展望(不足点也要写,比如"当前模型未考虑商品文本特征,后续可引入BERT进行语义增强")

其中第7点非常重要,老师看到你写"不足与改进",比写十页"创新点"更可信。毕竟一个毕设系统怎么可能没有缺点,你能自己说出来说明你真的做过和思考过。

6. 调试中遇到的经典问题实录

我把自己开发这套系统时踩过比较深的坑整理成了表格,你有类似症状时可以直接对着排查:

现象根本原因解决方案
大屏图表数据一直不变Redis缓存没设过期时间cache.set()时显式加timeout=30
预测接口响应要2秒每次请求都重新load_model模块加载时一次性加载到全局变量
训练准确率很高但预测结果全是一个类样本不均衡对多数类下采样
前端轮询后旧图表消失setOption缺少notMerge参数加setOption(option, true)
Django admin后台打开很慢数据表没有索引给Behavior表加联合索引
导入数据时外键报错数据中product_id不存在导入前先做外键完整性检查
模型在Windows上加载特别慢TensorFlow初始化开销改成SSD盘存储模型文件,首次访问后预热
前端访问接口跨域报错Django未启用CORS安装django-cors-headers,中间件加白名单

这些坑如果你提前知道,至少能节省一周的调试时间。特别是"模型加载到全局变量"这一点,我见过太多人在视图函数里写load_model,每一次请求都要重新读磁盘加载文件。

7. 这套系统价值在哪:从毕设代码到项目经验

做完这套系统,你在简历上可以正式写:"主导开发了基于Django和LSTM的用户购物行为分析与预测系统,处理百万级行为数据,实现多维度可视化分析与行为序列预测,模型准确率达到XX%"。

这个"XX%"的数据要注意——我们的模型在样本均衡处理后准确率大概在72%到78%之间。有的同学觉得"准确率低不好看",干脆把数字写成95%,这是非常危险的做法,因为答辩老师可能追问你用了什么数据、多少样本、混淆矩阵长什么样。你诚实写75%左右,然后补一句"通过调整序列长度到20后,准确率提升了约五个百分点",这个回答含金量比虚报100%高得多。

最后聊聊后续扩展的方向。我个人做这套系统时,就规划了三个可以提高的维度,你顺着一条线往下走就能变成期刊论文的素材:一是序列模型引入注意力机制,让模型自适应地关注那些"高价值行为"(比如加购和收藏)而不是一视同仁地看待每次点击;二是加入商品画像和用户画像的交叉特征,把冷启动用户的问题也纳入模型;三是可视化层加入地理热力图和实时推荐结果联动,从"看数据"进化到"用数据做决策"。这三条路任意一条做到位,你这个毕设选题的价值就不只是毕业,而是一个能写论文、能面试讲、能真正放在作品集里的完整项目。

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

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

立即咨询