ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Django+Vue+ECharts构建京东茶叶数据可视化分析系统

Django+Vue+ECharts构建京东茶叶数据可视化分析系统 京东茶叶数据可视化分析系统这个题目是不是看着就头大数据在哪、怎么存、图表怎么画、算法往哪塞每一环都像一堵墙。别急着焦虑这类“Django Vue 数据可视化 大数据/深度学习”组合的毕设项目恰恰是性价比最高的选择——它把一条完整的数据分析链路清晰地拆成了几段每一段都能独立拿下合起来又是一个有说服力的整体。这篇分享我会把自己做这个项目时的完整思路、技术选型逻辑、代码细节以及踩过的坑、答辩时的加分技巧全部摊开来讲。如果你正在做类似的选题或者正在技术选型里摇摆不定这篇文章应该能帮你省下不少走弯路的时间。它适合计算机、软件工程、数据科学甚至电商专业的学生参考核心目标是让你在有限时间内交付一个“数据采集 → 数据清洗 → 后端接口 → 前端可视化”链路完整、演示效果直观、并且还能挂上深度学习算法做亮点的系统。1. 项目定位与整体架构设计思路1.1 为什么选京东茶叶数据作为毕设题目选这个题我当初是被三个现实条件逼出来的数据可得、图表好看、框架能扛。先说数据可得。茶叶在京东是标准商品品类结构非常稳定——名称、价格、销量、店铺、品牌、规格、评价数这一套字段从爬虫界面就能拿到清洗难度低足够支撑后续的日常分析和统计。我也不需要去求人买数据直接写爬虫就能抓到上万条样本论文里写清楚来源就能过关。相比之下你要是选“社交媒体舆情分析”光数据接入环节可能就要耗掉毕设一半的时间。其次是图表好看。茶叶价格从几十块到几千块跨度很大销量差着好几个量级天然是折线图、柱状图、散点图、词云的绝佳材料。可视化系统的核心卖点就是“一眼看明白”数据本身形态越丰富你做出来的看板就越漂亮答辩时演示效果就越加分。最后是框架能扛。这一类项目的技术栈高度范化Django提供成熟的MTV架构和ORMVue管理前端交互ECharts负责渲染图表。整个项目就像搭积木——后端写好接口前端卡片式组合图表最后串起来就能跑。而且“JD茶叶数据”这个题目挂上“大数据”和“深度学习算法”的标签在答辩包装上天然站得住脚数据量过万就算批量数据处理评价文本可以做情感分析直接就能衔接经典深度学习模型。1.2 技术栈选型的深层逻辑为什么是DjangoVueECharts毕设技术选型有一个原则内核要稳边缘要炫。Django就是那个内核。它的ORM把所有数据库操作封装得极其丝滑你几乎不用手写SQL自带Admin后台免费送你一套数据管理界面MTV分层明明白白论文里写系统设计一章非常顺手。你一个学生项目不可能把大量时间花在底层的SQL拼接和安全加固上Django的“开箱即用”就是帮你把省下的时间投入到前端可视化和数据分析上去。Vue扮演的是“边缘要炫”的角色。它的MVVM双向绑定让数据和DOM自动同步组件化开发让我能把图表模块独立封装成一个组件换个数据源就能复用到下一个页面。很多人纠结要不要上React但毕设的终极目标不是前端大前端工程化而是把“可视化”这件事做得干净利落。Vue的模板语法比JSX更接近传统HTML的思维上手速度快踩坑也容易查。ECharts则是整个项目的颜值担当。它由Apache基金会孵化图表渲染性能相当能打折线图、柱状图、饼图、散点图、地图、词云都有现成类型。更重要的是它可以接收一个大的JSON配置对象渲染逻辑全封装在组件内部你只需要生产数据然后喂给它。把这三个组合起来就是一条完整的链路Django在后端跑模型和接口Vue在前端组织页面ECharts负责把数据变成观众能直接吃掉的可视化信息。顺便说一句如果担心“大数据”三个字压不住其实不用去碰Hadoop那套重型框架。毕设语境下的大数据指的是“数据量大到不能简单用Excel手工分析需要一套完整的数据处理与展示链路”Django MySQL完全能承载。论文里把“大数据架构四个层次——采集层、存储层、计算层、应用层”对应到自己的系统上就已经踩到了大数据架构的及格线。1.3 系统整体架构与模块划分我实际划分的时候用了“三层一总线”的结构。最底层是数据采集与存储模块用独立爬虫脚本抓取京东茶叶页面结果直接写入MySQL数据库包含原始数据表与清洗后的维度表。中间层由Django应用对外提供RESTful风格接口每个接口对应一种分析需求比如按日期聚合价格、按品牌聚合销量、按评价聚合情感得分。最上层就是Vue单页应用内部再拆成“总览看板”“价格趋势”“品牌排行榜”“评价分析”四个子页面。“总线”指的是一张统一的接口约定表。我提前把前端所有需要的数据类型列出来比如/api/price-trend、/api/brand-ranking、/api/comments-sentiment每条接口的返回值都固定为{code, data, message}格式。这个习惯帮我省掉了非常多联调时的扯皮。很多新手最容易栽的坑是“后端写完再写前端”结果后端给了10个字段前端只需要3个反复改接口改到怀疑人生。强烈建议先在文档里把接口约定表确定下来哪怕手动画个表格都行。还有个隐藏设计点Django Admin后台拿来当“数据检查器”。爬虫跑完数据以后我第一件事不是写SQL而是打开/admin页面人工扫一遍价格有无异常值、商品名是否混入广告文本、品牌字段是否统一。可视化结果再好看底层脏数据照样能把图表带偏前期数据质检比后期在代码里写一堆判空逻辑划算得多。2. 前端VueECharts可视化核心实现2.1 Vue环境搭建与项目初始化先交代我自己的环境Node v16.20.2、npm 8.19.4Vue版本用的3.x。这里有个非常容易踩的坑——npm默认源在国外下载依赖慢到让人怀疑人生尤其装vue-router和axios时卡个几分钟很常见。第一步就建议切换淘宝镜像源npm config set registry https://registry.npmmirror.com然后创建Vite构建的Vue项目。老教程还让你用vue/cli但Vite的冷启动速度要快得多尤其你反复改代码时热更新的爽快度差距非常明显npm create vitelatest tea-visual-front -- --template vue进入项目目录后安装基础依赖vue-router做路由axios做HTTP请求echarts做图表。这里提醒一下ECharts的包体很大全部引入会导致首屏加载慢。我采用的是按需引入方式在main.js里用use注册import * as echarts from echarts/core import { LineChart, BarChart, PieChart, ScatterChart } from echarts/charts import { TooltipComponent, GridComponent, LegendComponent } from echarts/components import { CanvasRenderer } from echarts/renderers echarts.use([LineChart, BarChart, PieChart, ScatterChart, TooltipComponent, GridComponent, LegendComponent, CanvasRenderer])这样首屏打包体积能降下来一半左右。前端开发时建议在Chrome装一个Vue Devtools插件排查数据绑定异常时你能直观看到组件的数据流省掉大量盲猜的时间。如果你电脑里还没装Node记得先把环境配好这一步是后续所有前端操作的地基。2.2 ECharts图表组件封装与数据绑定把图表封装成一个复用组件是我整个前端工程里最重要的决策。我建了一个BaseChart.vue核心逻辑就是接收一个option对象然后在watch里监听变化数据一变就调用setOption。这样我就再也不用在页面组件里堆一堆初始化代码了template div refchartDom stylewidth: 100%; height: 100%;/div /template script setup import { ref, onMounted, watch } from vue import * as echarts from echarts/core const props defineProps({ option: { type: Object, required: true } }) const chartDom ref(null) let instance null onMounted(() { instance echarts.init(chartDom.value) instance.setOption(props.option) }) watch(() props.option, (newOpt) { instance.setOption(newOpt) }, { deep: true }) /script封装完以后每个分析模块只需要在页面里构造一个option对象。比如价格趋势页我要一个带平滑曲线和面积填充分层的折线图那就在接口返回后组装出{ xAxis: { data: dates }, series: [{ data: prices }] }喂给组件即可。这里说个踩过的坑Vue 3的watch默认是浅层的属性对象的内容变了不会触发回调必须加{ deep: true }。我第一次写就漏了这个参数图表刷新了几次都不更新查了半天才发现是监测深度的问题。另外一个很容易犯的错页面跳转后旧图表实例不会自动销毁长时间运行会造成内存泄漏。我给BaseChart.vue在onUnmounted里加上了instance.dispose()逻辑这个细节在答辩时提出来多少能显得你有工程意识。2.3 交互式数据看板设计与路由配置数据看板不能只是放几张静态图要能让用户通过交互去挖掘信息。我页面设计分了三个维度时间维度用日期选择器分组维度用下拉框切换品牌类型维度用Tab切换图表显示方式。比如同一个“价格分析”模块用户可以选“近30天”再切到柱状图直观对比每天价格的高低变化。路由配置这里踩过坑。我一开始把页面全挂到根路由下每个子模块各自传参数代码一多就很乱。后来改用Vue Router的动态路由和路由传参页面间跳转通过params传递筛选条件{ path: /product/:category/:id, name: ProductDetail, component: () import(../views/ProductDetail.vue), props: true }这种做法在“从品牌列表点击进入该品牌详情”这种联动场景下特别好用URL也符合语义化。另外如果你需要在同一个页面里做多条件筛选用query传参会更合适刷新页面后参数还在URL里体验更好。前端这块核心就是把页面组件拆小、把图表做抽象、把路由理清楚剩下的事就是数据对接了。3. 后端Django接口设计与数据支撑3.1 Django项目结构与App划分后端我用的是Django 4.2版本搭配Python 3.10。创建项目后第一件事就是规划App我建立了products商品与品牌、analysis聚合统计与情感分析、api对外接口三个App职责单一跟前端页面也能一一对应。创建App的命令很简单python manage.py startapp products python manage.py startapp analysis注意一定要在settings.py的INSTALLED_APPS里把新App注册上去否则模型表不会生成。另一个细节是ALLOWED_HOSTS本地调试时建议填[*]因为前端Vite开发服务器跑在localhost:5173后端跑在localhost:8000跨域请求必然会发生*能让联调阶段少一些阻碍。Django框架本身有个好习惯每个App内部models.py、views.py、urls.py是分开的。这意味着你在写后端代码时就自然有了一种模块化约束不至于像老式PHP项目那样堆出一堆不可维护的脚本。写毕设论文的时候这套结构也方便你画系统架构图。3.2 数据模型设计与数据库选型数据库我选了MySQL查询性能比SQLite好答辩时被问到“为什么不用MySQL”用“生产环境常用”作为理由也更站得住脚。我设计了五张核心表Product商品ID、名称、品牌、店铺、规格、价格、销量、评价数、上架时间Brand品牌ID、品牌名、产区可选字段Comment评论ID、商品ID外键、评论文本、评分、评论时间DailyPrice商品ID、日期、价格快照Category商品的茶类分组绿茶、红茶、白茶、黑茶等这里有一个关键决策为什么不把价格直接存在Product表里因为图表要按天展示价格趋势如果商品改价历史价格就会被覆盖。我单独建了DailyPrice快照表每天爬虫跑一次就往里写一条记录这样“环比昨天”“近30日走势”这类分析就有了数据基础。模型定义的时候Django ORM的DecimalField比FloatField更适合存价格避免浮点误差TextField处理评论文本给常用过滤字段加db_indexTrue因为后端的SQL经常需要按时间或商品ID过滤大量记录。Django ORM里执行查询和删除对象其实是非常直观的例如# 查询近7天的价格记录 daily DailyPrice.objects.filter(date__gteseven_days_ago) # 按品牌分组聚合 from django.db.models import Sum, Avg brand_summary Product.objects.values(brand).annotate(total_salesSum(sales), avg_priceAvg(price)) # 删除某件失效商品及其关联评论注意外键级联 comment_count, _ Comment.objects.filter(product_id1024).delete() Product.objects.filter(id1024).delete()这块代码量不大但它体现了你对ORM和关系型数据库的理解答辩时挑一两个例子讲讲“为什么用快照表”“为什么删评论要优先于删商品”能立刻提升答辨深度。3.3 数据采集与清洗爬虫与预处理这一块是项目的“基建工程”决定了后续所有图表数据的水质。爬虫我用的组合是requests加BeautifulSoup4先把京东搜索页面的HTML抓下来再用CSS选择器定位商品卡片里的各字段翻页一次以50为步长。核心爬虫逻辑简化版如下import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.jd.com/ } def fetch_products(page1): url fhttps://search.jd.com/Search?keyword茶叶page{page} resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, html.parser) items soup.select(.gl-item) for item in items: title item.select_one(.p-name em).text.strip() price item.select_one(.p-price i).text.strip() seller item.select_one(.p-shop a).text.strip() if item.select_one(.p-shop a) else 京东自营 yield {title: title, price: float(price), seller: seller}爬下来的原始数据不能直接用我做了四个清洗动作去重按标题店铺组合判断重复的只留一条、去除噪声用正则把“满减”“立减”等营销文案从标题里剔除、缺失值处理价格字段空就放弃该条图表计算不允许空值店铺为空则兜底填“未知”、字段统一把所有单位、评分区间统一成标准格式。数据装进MySQL以后我顺手在Django Admin后台做了一次可视化确认看到17000多条产品记录和42000多条价格快照悬着的心才算落地。3.4 数据接口API设计与跨域处理后端接口推荐直接上Django REST FrameworkDRF比裸写JsonResponse省太多事。拿一个最典型的聚合接口举例按品牌聚合销量并返回JSON# api/views.py from rest_framework.views import APIView from rest_framework.response import Response from products.models import Product class BrandRankingView(APIView): def get(self, request): category request.GET.get(category, ) queryset Product.objects.all() if category: queryset queryset.filter(categorycategory) result list(queryset.values(brand).annotate( total_salesSum(sales), avg_priceAvg(price) ).order_by(-total_sales)[:20]) return Response({code: 0, data: result, message: success})跨域问题在前端发请求时绕不开。Django处理跨域建议安装django-cors-headers在settings.py里配置INSTALLED_APPS [ # ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # 建议放在最前面 # ... ] CORS_ALLOW_ALL_ORIGINS True开发阶段够用答辩演示也够用。如需演示登录权限可以结合JWT或者Django Session把Token放进Cookie里鉴别身份。至于热词里提到的“python django websocket实现后台有数据前端推送”这个是很好的加分项用channels跑WebSocket定时任务把最新的价格快照推送到前端实现看板数据的实时刷新。答辩时“数据主动到达前端”比“前端每隔几秒轮询”要炫很多但注意工作量不小建议放在核心功能全部稳定之后再当彩蛋来做。4. 可视化看板功能详解与实战效果4.1 茶叶价格趋势分析模块价格趋势是整个系统门面。我做的图表是把DailyPrice表按日期聚合计算当日所有茶叶商品的平均价、中位数和最高价三条线放在同一张折线图上。如果中位数和平均价出现明显分离就能揭示“高端茶拉高均值”的市场现象这种洞察在讲解时特别有说服力。后端聚合逻辑用Django ORM的annotate加TruncDatefrom django.db.models import Avg, Count, Max, Min from django.db.models.functions import TruncDate daily_data DailyPrice.objects.filter( date__gtestart_date ).annotate( dayTruncDate(date) ).values(day).annotate( avg_priceAvg(price), max_priceMax(price), min_priceMin(price), item_countCount(id) ).order_by(day)前端拿到这个数据以后以合适的X轴步长建议7天一个刻度构建ECharts的折线图tooltip开启trigger: axis鼠标划过就能看到某一天的平均价和样本数。这个功能虽然看起来简单但它的价值在于串联了“数据库聚合”和“前端渲染”两个环节抽掉了中间所有的硬编码。价格分析还配了“品类细分”联动顶部的下拉框选了“绿茶”后前端重新请求接口后端只返回该品类的聚合结果图表平滑更新。这种交互让评委感觉系统在世而不是一张静态图片放在那里。4.2 品牌销量排行与词云展示品牌销量排行我用了横向柱状图因为品牌名称都比较长横向展示时标签不遮挡。排行榜默认取Top20每个品牌右侧展示一个“总销量”标签。我还在图表下方加了一个子层级的“品牌内热销商品”表格点击柱子行跳转到该品牌的商品列表页实现简单的下钻操作。词云用来展示用户对茶叶关注点。我把所有商品名称用jieba分词后按词频统计筛选掉“茶叶”“包装”“礼盒”这类太通用的词让“西湖龙井”“金骏眉”“铁观音”这类品类词和“清香”“回甘”“礼盒装”这类特征词浮出来。ECharts的wordCloud类型支持直接渲染词频数组效果爆炸。词云的数据在后端算好再返回不能把原始文本全塞给前端否则网络传输和渲染都是负担。我在Redis里缓存了词频结果因为数据更新频率不高缓存几小时完全没问题需要重新统计时再覆盖清除。这里用到的“词频统计”本质上是个轻量文本挖掘任务论文里可以顺势引出后续的情感分析。4.3 用户评价情感分析与深度学习算法扩展这里是对标题里“深度学习算法”最直接的呼应。基础版本的情感分析我用的是基于词典的规则匹配把评论文本分词后去匹配积极词库“好喝”“醇厚”“推荐”“回购”和消极词库“难喝”“苦”“涩”“失望”统计得分后映射为正面、中性、负面三个区间。这个方案简单有效对于茶叶品类的短评来说准确率不低而且代码量很小import jieba positive_words set([好喝, 醇厚, 推荐, 回购, 清香, 甘甜]) negative_words set([难喝, 苦, 涩, 失望, 差评, 劣质]) def simple_sentiment(text): tokens jieba.lcut(text) pos_score sum(1 for w in tokens if w in positive_words) neg_score sum(1 for w in tokens if w in negative_words) if pos_score neg_score: return positive elif pos_score neg_score: return negative return neutral如果你想真正把深度学习算法融进去升级路径清晰用TextCNN或者LSTM对评论文本做情感分类。数据量不够没关系可以先用公开的中文情感分类语料做预训练再用你爬的评论微调。答辩时讲清楚“为什么选TextCNN”——结构简单、参数少、适合小规模文本分类任务而不是搬出需要大算力的巨型Transformer吓唬自己反而更扎实。如果时间充裕也可以引入HuggingFace的transformers加载一个轻量BERT模型做特征提取但毕设时间有限我建议“规则情感分析”作为主功能“深度学习模型”作为实验结果对比展示。这样既能保证系统当前可用又能拿出算法对比的数据写论文一举两得。5. 部署、常见问题与排坑实录5.1 本地联调常见问题联调阶段我遇到的第一个坑是前端报CORS policy错误配置了django-cors-headers还是报错。翻遍文档才发现CORS_ALLOW_ALL_ORIGINS True要写在INSTALLED_APPS注册之后再设顺序不对很可能被默认配置覆盖。上面的配置代码块就是踩坑后定下来的模板。第二个坑是axios默认用application/json发POST但Django的普通视图默认按form-data解析如果你用request.POST.get()去取值永远取到None。我的解法是统一走JSON POSTimport json post_data json.loads(request.body) if request.body else {}前端用axios.post(url, this.formData)发送后端直接读post_data里的字段再也不会有丢参数问题。第三个坑是中文乱码。MySQL建表时如果没有显式指定字符集默认utf8mb4之外还可能用历史默认值导致评论文本从库里读出来乱码。解决办法是建库时指定CREATE DATABASE tea_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;前端请求层面也要确保响应头是UTF-8一般给Django的DEFAULT_CHARSET utf-8就够。5.2 部署上线注意事项毕设虽然拿来演示但答辩当天现场环境没配好项目印象分会大打折扣。我提前演练了部署一台2核4G的云服务器Ubuntu 22.04Python用venv隔离Django通过uWSGI跟Nginx配合前端构建成静态文件后由Nginx直接服务。后端部署关键指令pip install -r requirements.txt python manage.py collectstatic --noinput python manage.py migrate uwsgi --http :8000 --module tea_visual.wsgi前端执行npm run build把dist目录复制到Nginx的/var/www配置反向代理请求/api前缀的路径转发到Django服务端口。这里最容易被忽视的坑是ALLOWED_HOSTS本地开发填[*]没问题部署上线后必须改成域名或IP列表否则Django拒绝响应返回400错误。还有一点不要在生产环境开DEBUGTrue。DEBUGTrue会把报错堆栈完整暴露给访问者安全隐患严重而且静态文件服务方式也不适合生产。把DEBUGFalse后需要配合STATIC_ROOT和collectstatic这块提前测一遍别等答辩那天手忙脚乱。5.3 答辩加分技巧与心得体会答辩本质上是一场说服游戏你要在十分钟内让评委相信三件事你真的动手做了、你理解每一步的来龙去脉、你做的东西有一定完整性和创新性。我准备了三个展示切入点。第一个切入点是“数据完整性”。我会当着评委面打开Admin后台展示17000条商品记录和42000条价格快照然后现场跑一条DELETE操作说说ORM删除对象时外键级联策略该怎么设计我在表里加了is_deleted逻辑删除字段硬删除在业务中很少见。第二个切入点是“链路闭环”。我会从爬虫代码跑到数据清洗脚本再到API接口最后到前端图表完整演示数据在整条链路中是怎么流动的。这种叙事方式比单讲某一个网页图表有说服力得多。第三个切入点是“算法创新”。我会对比规则情感分析和测试集上微调后的TextCNN效果把准确率提升多少列成表格让评委看到你在基础功能之外确实做了探索。用VueECharts搭出来的这个可视化看板配合Django后端的数据支撑演示时几乎不可能冷场。如果答辩环节评委抛来“大数据体现在哪”这种问题你就拿“每日价格快照表、批量清洗脚本、聚合分析与情感分析”回应把话题引回你的链路闭环牢牢把握话语权。最后说点实在的毕设不是产品完成度比炫技重要得多。与其做一个技术栈花样百出但跑不通的半成品不如把DjangoVueECharts这条主链路打磨到无懈可击再把深度学习算法当“惊喜彩蛋”呈现。我自己做这个项目最大的体会是当你能流畅地跟别人讲清楚“数据从哪里来、经过什么处理、最后怎么呈现”你对这个项目的掌控力就完全到位了。那股顺畅的信心就是你答辩时最好的底牌。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进