做毕设选这个方向的人这两年明显多了,Python商品推荐系统、比价、可视化三个词绑在一起,光看标题就知道是一个典型的电商大数据综合项目。它不像那种纯粹的管理系统,只做个增删改查就能交差,而是真的要把数据采集、算法推荐、统计分析、前端可视化串起来,考察的是完整的产品思维和技术落地能力。这篇文章就从选题拆解、技术选型、数据库设计、推荐算法实现、比价逻辑、可视化大屏,到论文写作和答辩视频准备,把整个项目从0到1讲清楚,每一步都给到可以直接用的方案和代码思路。
1. 项目整体设计与技术栈选型
1.1 三大核心模块的功能边界
先把这个系统拆开看。商品推荐系统解决的是“用户不知道买什么”的问题,核心是基于用户历史行为(浏览、收藏、加购、购买)预测用户感兴趣的商品;商品比价系统解决的是“同样一件商品在哪个渠道买更划算”的问题,核心是识别同一商品在不同平台的价格差异;商品可视化解决的是“数据太多看不懂”的问题,核心是把商品价格分布、品牌热度、销量排行、趋势变化用图表直观呈现。三个模块看起来独立,实际上共享同一套数据库和商品数据源。
这个设计的妙处在它以“商品数据”为核心枢纽,把推荐、比价、可视化全部串起来。推荐系统需要商品和用户行为数据,比价系统需要多平台价格数据,可视化需要的统计结果也来自同一批数据。集中式数据模型能让整套系统逻辑非常清晰,不用各模块各搞各的数据,联调起来也非常顺畅。
1.2 为什么选择Python这一套技术栈
这个项目几乎是为Python量身定制的。算法层面需要做协同过滤、相似度计算,Python的pandas、numpy处理起来非常简单高效;数据采集层面,requests配合BeautifulSoup或者更专业的Scrapy框架都很成熟;Web框架用Django或Flask都行,后台管理、ORM、模板渲染一站式解决;可视化部分有ECharts这个神器,后端只要把处理好JSON数据返回给前端就行。
我的建议是Web框架选Django。虽然Flask更轻量,但毕业设计通常需要系统完整,Django自带Admin后台,可以直接管理商品、用户、价格记录,省掉大量写管理页面的时间。ORM也比裸SQL更适合做数据关联查询,比如用户行为关联商品信息、价格历史关联商品,这类多表查询用Django ORM的select_related和prefetch_related能省掉很多底层优化。
推荐算法部分不用一上来就上scikit-learn,实际上手写一个协同过滤并不难,而且答辩的时候反而更好讲原理。sklearn适合做进阶展示,比如用TruncatedSVD做矩阵分解,但基础版本手写用户相似度矩阵和物品相似度矩阵就够了。
1.3 系统架构与数据流向
整个系统的架构可以理解为四层:数据采集层负责从公开数据源或开放接口获取商品信息、价格信息;数据存储层使用MySQL存放结构化数据,Redis缓存热数据和推荐结果;业务逻辑层用Django实现推荐算法、比价计算、统计聚合;展示层用HTML + CSS + ECharts做前端页面和数据可视化大屏。
数据流是这样走的:采集脚本把商品基础信息和价格历史写入MySQL,用户访问系统产生的行为记录也写入MySQL的user_behavior表;推荐模块定时从行为表读取数据,计算相似度矩阵,得到候选推荐列表并缓存到Redis;比价模块定时刷新价格历史表,当前端请求某个商品详情时,按商品匹配关系去查询多平台价格;可视化模块则通过聚合查询生成统计API,前端用AJAX拉取JSON数据渲染图表。这套数据流在答辩的时候画一张架构图就能说清楚整条链路。
2. 数据库设计与电商数据准备
2.1 核心数据表结构设计
数据库设计决定了整个系统的扩展性和查询效率。我用了7张核心表:用户表、商品表、平台表、价格历史表、用户行为表、商品类目表、推荐结果表。商品表和价格历史表通过商品ID关联,用户行为表通过用户ID和商品ID关联。
商品表是核心中的核心,字段设计要有前瞻性。除了基本的标题、品牌、类目ID、图片URL之外,我还加了平台ID、商品URL、销量、评论数、上架时间,更重要是多加几个辅助字段——规格型号、条形码、商品唯一标识码,这几个字段是比价模块识别同一商品的关键依据。给商品表建一个复合索引:platform_id + product_code,这样查询同平台同商品时效率非常高。
价格历史表是比价和价格趋势图的数据基础,设计为每次检测价格都插入一条记录,不直接覆盖旧值,这样以后能画价格走势、算最低价、分析降价幅度。表结构包含id、product_id、platform_id、价格、优惠价、抓取时间。这个表数据量增长很快,要留意索引优化,建议建立product_id + capture_time的联合索引。
CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, brand VARCHAR(64), category_id INT, platform_id INT, product_code VARCHAR(128), spec_model VARCHAR(128), barcode VARCHAR(64), image_url VARCHAR(512), product_url VARCHAR(512), sales_count INT DEFAULT 0, comment_count INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, idx_platform_code (platform_id, product_code) );用户行为表动作字段用枚举值:浏览记为1、收藏记为2、加购记为3、购买记为4。不同动作权重不一样,建议购买权重最高,浏览权重最低,这样推荐结果会偏向转化率高的商品。
2.2 商品数据来源与采集方案
做这种系统面临的第一道门槛就是“数据从哪来”。最稳妥的方案是用公开数据集,比如一些开源的电商商品数据集合,数据已经清洗好,格式统一,适合先跑通整个系统。但公开数据集的问题是没有价格历史和实时价格,比价模块的“动态价格变化”效果体现不出来。
更贴近实际的做法是用爬虫从几个主流平台采集数据。采集时要注意三个问题:第一是合法合规,要遵守平台robots协议,控制抓取频率,只采集公开页面信息,不碰个人信息和交易数据,这个是必须守住的红线;第二是反爬策略,需要做好请求头伪装、访问频率限制、重试机制;第三是数据清洗,平台间同一件商品的标题格式差异很大,需要通过统一规则截取品牌、型号等信息。
我实际的做法是做了一个两阶段采集:初识阶段通过搜索关键词批量获取商品列表和价格,之后每隔一段时间更新在库商品的价格,这样价格历史表才能积累足够的数据支撑趋势图。
2.3 数据清洗的两大核心问题
电商数据清洗有两大拦路虎。一是重复商品,同一件商品在不同平台标题可能写成“某品牌 无线蓝牙耳机 5.3 入耳式”和“真无线蓝牙耳机 某品牌 旗舰版 入耳降噪”,格式差异很大;二是价格噪声,有些平台默认显示划线价,实际有满减促销,如果只抓划线价,比价结果会出现很大偏差。
清洗方案分两步:首先做通用文本归一化,所有标题统一小写,全角转半角,去掉空格、特殊符号,把“(”“(”“【”这些括号统一处理后剔除;其次做商品匹配,这里需要提取特征词,优先通过条形码匹配,没有条形码就通过品牌词+核心型号匹配。这里我用jieba分词做关键词提取,配合品牌词库匹配,一个简单的相似度规则就能达到不错的匹配效果。
关于价格噪声,采集时需要区分“原价”和“促销价”两个字段,如果页面存在促销规则,就把实际成交价存入价格字段。答辩时可以重点讲这个细节,老师会觉得你考虑到了真实场景。
2.4 大数据量下的存储与查询优化
毕设级系统数据量大概在几万到几十万条商品记录、几十万条价格历史记录之间,MySQL完全能扛得住,关键在于索引和查询写法。别在ORM里无脑写.objects.all(),涉及关联查询时用select_related、prefetch_related,比如查询商品信息连同最新价格,可以用子查询subquery先取每个商品的最新价格记录,再关联商品表去查。价格历史表不要一次性捞出来所有记录,做趋势图时可按时间粒度聚合,比如按天取均值。
Redis在这里承担的角色很关键。推荐结果每隔几小时算一次,算完放进Redis加过期时间;热门商品Top榜也是定时算好放进缓存;商品详情页的实时比价信息缓存几十分钟,避免每次访问都打数据库。
3. 商品推荐系统核心实现
3.1 推荐算法选型与原理拆解
推荐系统算法选择要符合毕设的体量,不能为了炫技直接上一个深度学习模型,数据和算力都撑不住。经典的协同过滤是首选,它分为基于用户的UserCF和基于物品的ItemCF两种方向。UserCF的核心逻辑是找到和你兴趣相似的用户,把那些用户喜欢而你还没买过的商品推荐给你;ItemCF则是找到和你历史喜欢的商品相似的其他商品。对电商场景来说,ItemCF的计算稳定性和可解释性更强,用户规模大时物品间相似度相对稳定,适合商品数量少于用户数量的情况。
实际的毕设项目建议两个都实现,这样可以做一个对比实验,效果好的作为主推荐算法,效果差的写在论文的对比分析里。
相似度计算用余弦相似度。以物品相似度计算为例,首先构建“用户-物品”评分矩阵,评分不是直接从数据库查出来就完事,需要对用户行为做加权转换:浏览记1分、收藏记3分、加购记5分、购买记8分,这样主动行为的权重更大。
def cosine_similarity(item1, item2, user_item_matrix): vec1 = user_item_matrix[item1] vec2 = user_item_matrix[item2] dot_product = float(np.dot(vec1, vec2)) norm1 = float(np.linalg.norm(vec1)) norm2 = float(np.linalg.norm(vec2)) if norm1 == 0 or norm2 == 0: return 0 return dot_product / (norm1 * norm2)3.2 基于物品的协同过滤实现细节
基于物品的协同过滤分三步:构建评分矩阵、计算物品相似矩阵、生成推荐列表。推荐过程并不复杂:找出用户历史评分过的商品集合,遍历每个商品找到它最相似的K个商品,排除用户已经行为过的,对剩余商品按相似度加权的预测评分排序。
这里有个工程细节,计算物品相似度矩阵时速度会很慢,因为物品对数量是O(n^2)。我实际改动是用物品-用户倒排表加速,先构建每个商品的用户行为集合,然后只对共同用户数大于阈值的商品对计算相似度,砍掉大量无效计算。
def build_item_similarity(user_item_matrix, min_common_users=2): item_users = {} for user, items in user_item_matrix.items(): for item in items: item_users.setdefault(item, set()).add(user) C = defaultdict(float) # 共同用户数 N = defaultdict(int) # 商品被多少用户访问过 for item, users in item_users.items(): for u in users: N[item] += 1 for v in users: if u != v: C[item][v] += 1 # 只保留共同用户数大于阈值的商品对,计算余弦相似度 ...生成推荐时,给用户的每一个行为商品一个贡献度,行为权重越高,相似商品的推荐价值越大。购买过的商品相似物品要排最前面,浏览过的次之,这个偏好和用户真实购物习惯完全吻合。
3.3 冷启动问题的应对方案
冷启动是毕设答辩的必问题目。新用户没有任何行为数据,协同过滤直接失效。解决方案是先做基于用户维度的粗推荐:先用热门榜兜底,把销量最高、评分最好的商品推荐给新用户,等用户产生几条行为后,再切到协同过滤。
新商品冷启动更麻烦,没有任何用户行为记录,协同过滤里的相似矩阵根本找不到它。方案是给推荐系统加基于内容的推荐分支:提取新商品的品牌、类目、关键词,用jieba分词和TF-IDF计算它与现有商品的文本相似度,然后拿文本相似的商品作为纽带,把新商品挂到推荐列表里。这在代码里实现也不难,核心就是计算文本特征向量之间的余弦相似度。
3.4 推荐接口与性能优化经验
推荐模块不要每次请求都重新计算,这样线下跑30分钟算好的矩阵就白费了。真实做法是定期任务后台跑一次,生成好每个用户的推荐列表,存进Redis,前端请求时直接读取,扛不住的时候再降级成热门推荐。如果Redis里没有该用户缓存,就实时算一遍并写回缓存,下次再请求就不需要重算。
更难处理的是相似度矩阵的存储量,比如1万件商品就会有5000万对组合,如果把所有对都存下来,内存直接爆掉。解决办法是只保留每个商品相似度最高的TopN(通常取50~100),这样内存占用能大幅下降,推荐效果损失很小。
4. 商品比价系统实现逻辑
4.1 如何识别“同一件商品”
比价系统的灵魂不是价格抓取,而是商品匹配。同一件商品在不同平台标题差异极大,比如“某品牌 K50 手机 8GB+128GB”和“国行 某品牌 K50 顶配版 8+128”,不看型号规格很难识别是同一款。
我的解决方案是三级匹配策略:第一优先级用条形码,存在EAN或UPC码的件直接按条码比对,准确率接近100%,但这个数据源不是每个商品都有;第二优先级用“品牌+核心型号”,从标题中抽取品牌词和型号词,比如上面的例子能抽取到品牌词和型号“K50”,匹配同品牌、同型号的商品归为一组;第三优先级用标题文本相似度兜底,用jieba分词后计算余弦相似度,超过设定阈值放在同一组。匹配完成后给每个商品组分配一个relationship_id,把所有平台的同组商品关联起来。这个字段放在商品表中,每次做比价查询时直接按relationship_id分组取数据。
4.2 比价页面与价格历史记录
比价的展示逻辑按“商品组”维度来组织。页面上展示商品主标题,下面的表格列出这个组在不同平台的价格、销量、店铺名称、历史最低价、价格涨幅等信息。用户能直接看到哪家平台最便宜,同时能看到这件的历史价格走势——判断现在到底是真降价还是先涨价再促销的关键。
价格历史表就像这个商品的“体检报告”,每次抓取价格都留痕。在代码里,定时任务爬完价格后会先判断价格有没有变化,如果变化了就插入历史记录,没变化只更新检测时间,这样可以大幅减少无用的重复记录。历史数据的聚合查询效果对比价系统是质的提升,清晰的折线图能直观看出降价周期和平台底价。
4.3 比价排序与筛选逻辑
比价结果排序需要处理一个实际问题,不同平台之间可能存在包邮门槛、优惠券、会员价差异,不能只按价格字段排序。我给每个商品的价格排序提供三种模式:按最终成交价排序、按含运费总价排序、按历史最低价排序。默认按最终成交价排序,满足大多数用户的需求。
筛选逻辑方面,按品牌筛选、按价格区间筛选、按平台筛选是基本盘,再加一个“只看有降价”的开关,只拉取过去7天内价格下降过的商品,这个模块的开发它能直接对接价格历史表的查询,看一眼最近记录中的最低价对比就算出来了。
4.4 定时更新策略与数据增量管理
比价数据不是一次性完成,而是持续更新的过程。定时任务建议用APScheduler或Django的Celery Beat,每若干小时执行一次全量价格检查。采集策略要设计成增量为主、全量为辅:第一次采集全量建立商品基础信息库存,后续每次只检查价格变化,比全量抓取的压力小得多。
增量更新的核心是设计一张“采集任务表”,记录每个商品组的最近采集时间、下次采集时间、需要采集的平台列表。触发策略做成两级:普通商品每隔12小时更新一次,价格波动频繁的热门商品每隔2小时更新一次。这样整体系统资源开销可以控制,且热门商品的价格时效性更有保障。
5. 商品数据可视化与前端呈现
5.1 可视化大屏的整体布局思路
可视化模块是整个系统最好演示的部分,也是视频展示中的亮眼环节。大屏整体布局采用典型的电商数据驾驶舱风格:顶部是系统标题和核心指标卡(商品总数、用户数、今日采集价格数、平均价格),中间核心区域是价格走势图和销售排行,左右两侧面板分布着平台价格对比柱状图、品牌词云、类目占比饼图、商品价格分布直方图。
大屏尺寸设计成标准的1920x1080,后台用Django模板渲染页面骨架,各项数据通过AJAX接口动态加载。图表统一用ECharts,对毕业设计来说它足够成熟,默认的样式效果很好,只要配置一点主题色就能有专业感。注意大屏不要做翻页和跳转,一目了然才是它的作用。
5.2 后端数据聚合接口设计
可视化数据接口要聚合能力强,前端拿到数据直接就能渲染,不在前端做太多数据再加工。我设计了几个重点接口:统计总览接口返回商品数、用户数、价格记录数;价格趋势接口接收商品组ID返回最近一段时间每天的均价;销售排行接口返回按销量Top10商品;品牌分布接口返回品牌名及其商品数;价格分布接口对商品价格分段统计。
这里用Django ORM写聚合查询时注意写法。比如品牌分布统计,一行代码能搞定:
brand_stats = Product.objects.values('brand').annotate(count=Count('id')).order_by('-count')[:10]价格分段统计稍微复杂一点,可以用SQL的CASE WHEN手动分组,或者用pandas读出来再分组聚合。后者在数据量几万条时执行简单、调试友好,代码更直观大大提升了开发效率。
5.3 ECharts配置与前端细节
ECharts的配置有一些容易踩坑的细节。折线图的x轴时间数据要排序好再传,不然走势线会乱掉——这是很多新手容易忽略的;柱状图的y轴要从0开始,这样“销量差距”才不会被放大扭曲;词云的字体大小要在数据里预处理好,ECharts词云插件读的是value字段。
大屏数据更新用setInterval定时拉取接口,5分钟刷新一次足够,不要设置太短的刷新间隔,否则数据库查询压力大且没有实际意义。页面布局用flex加百分比宽度,确保不同分辨率的屏幕下不会挤压变形,配色建议整体统一,主色用深色背景搭配亮色高亮,数据可读性会更好。
5.4 从数据到故事:让图表说话
可视化最高级的用法不只是展示数据,而是通过图表讲出数据背后的故事。价格走势图可以说明某商品在促销季的价格波动特性;平台价格对比图可以分析不同平台的价格策略差异;地区分布图可以展示商品热销区域。做毕设视频演示时,每一步引导观看者看哪个图表、图表说明什么问题,演示的感染力会远超逐个页面干巴巴地翻。
6. 常见问题排查与答辩准备
6.1 推荐系统效果差的排查思路
推荐系统做完后经常遇到“推荐结果感觉不对”的问题。排查顺序是:先看用户行为数据量,如果总共只有几十条行为记录,算法再准都推不对,先造一批模拟数据把系统跑起来;再看相似度矩阵,如果用户没有共同购买过的商品,余弦相似度几乎全是0,推荐列表空掉,处理方式是增加一个“历史浏览相似商品”的关联替换“共同购买”,这样相似关系更稠密;最后看行为权重,如果用户点了一堆商品就都算高权重,推荐结果容易集中在一个类目,失去多样性。
6.2 数据抓取被拒、验证码、频率限制怎么办
采集过程中最常见的坑是请求被拒。不要一上来就开高并发,控制采集频率在每秒几次到十几次之间,伪装好请求头,尤其是User-Agent浏览器版本要常换。随机延时比固定延时更好,抓取节奏模拟人工访问。验证码问题不要硬刚,碰到疑似验证码页面直接跳过,等下一轮再补采。所有异常重试加指数退避策略,超市连续失败立刻停止,避免账号或IP被拉黑。
注意:整个项目的使用前提是数据来源合法合规。毕设项目建议使用公开数据集或经允许的开放接口采集数据,遵守平台规则,不采集个人隐私数据。
6.3 系统性能瓶颈、页面明显变慢的定位方法
一个常见瓶颈是首页数据太多导致加载慢联。解决办法:加载慢先看SQL耗时,用Django的django-debug-toolbar查看各页面SQL数量和时间,如果某个页面发了几十条SQL,优先用select_related优化。再看缓存,所有统计类和推荐类数据都必须走Redis缓存,设置好过期时间。最后看数据量,如果价格历史表几十万条,查询趋势图会卡,考虑按天聚合预先把数据算好存成统计表,查询直接从统计表读取,速度会提升非常明显。
6.4 LW文档写作要点与答辩PPT结构
LW文档(论文/设计文档)别写成了需求说明书,重点应该放在系统设计思路、算法原理、对比实验、测试分析上。推荐算法部分详细推导UserCF和ItemCF的公式,配合伪代码和截图,价格走势图、相似度热力图、推荐效果指标表,都是加分项。测试部分不只是写“系统运行正常”,最好有具体的评估指标,比如推荐准确率、价格匹配准确率、接口响应时间。做的对比实验放在论文里,让算法选型有数据支撑。
答辩PPT的建议结构:项目背景与意义、需求分析、技术选型、系统架构、核心模块实现、算法设计、系统测试与展示、总结与展望。每页尽量放图和代码片段,不要太密的文字,核心算法部分两层讲透:原理步骤和代码对应关系。讲演示的时候先把大屏数据打开,再到推荐页面和比价页面,最后展示后台管理,一气呵成。
6.5 讲解视频录制的一个实用建议
讲解视频不需要花里胡哨的剪辑,但一定要先说清楚“项目解决了什么问题”。我见过很多视频上来就开始操作页面,观众一头雾水,其实应该在最开头用两三句话讲清楚:这是一个面向电商场景的推荐与比价分析系统,解决商品发现和购买决策两个核心问题。然后按“数据怎么来、怎么推荐、怎么比价、怎么展示”四个层次串起来,每个功能操作前先说一句话“接下来演示的是某功能”,讲解看的是逻辑顺畅,不是炫技。
最后再分享一个实操心得:做这种综合型毕设项目,不要按顺序从爬虫写到可视化再写到推荐系统,这样倒序开发效率太低了。我的建议是先做数据库设计,再把可视化和比价的壳子搭起来,因为这两部分能最快看到成果,给自己一个完整的项目感知,最后再啃推荐算法这个硬骨头,把算法结果嵌进现有的数据流里。这样每完成一个阶段都有反馈,不至于在冷冰冰的算法调试里耗掉所有信心,更推荐这种增量式的开发节奏。