
前两天一个学弟问我毕设选题说想做一个能“看得见成果”的系统我第一反应就是给他推荐这个方向Python 唯品会商品数据可视化分析系统。这题目听起来很唬人但拆开看其实是一个特别经典的闭环——用requests爬虫把电商商品数据抓下来做完数据清洗和数据分析再用Flask搭一个网页把分析结果用可视化图表展示出来。数据采集、数据处理、Web开发、可视化四件事全占了毕设答辩时有得讲代码量又不会像真正的大数据平台那样失控非常适合当作计算机专业本科毕业设计来做。无论你是正在选题的学生还是想学一套“爬虫到可视化”完整流程的Python学习者这篇文章都值得收藏。我会把这个项目的架构思路、每个环节的实现细节、我踩过的坑一条一条写清楚尽量让你照着就能把整套东西跑起来。1. 项目整体设计与核心思路拆解1.1 这个系统到底解决什么问题先别急着写代码想清楚业务才走得下去。电商平台上的商品数据非常密集比如价格、折扣、品牌、销量、评论数这些数据分散在一个个商品页里人眼根本看不过来。这个项目做的事情就是把散落的商品信息聚合成一张“作战地图”——哪个品牌的折扣力度大、价格区间集中在哪、销量最高的商品有什么共性打开网页就能看得明明白白。对于学生来说它的定位很清晰这是一个轻量级、可演示、技术栈完整的数据分析展示系统。它不追求处理千万级数据也不是真正的大数据平台而是把“数据采集-数据清洗-数据分析-数据可视化”这条链路完整走通让你在答辩时能讲清楚每一个环节做了什么、为什么这么做。1.2 为什么选这套技术栈技术选型是这个项目里最值得说的一件事。核心组合就四个requests、Pandas、Flask、ECharts。环节技术选型理由数据采集requests BeautifulSoup/JSON解析比Scrapy轻量适合单个目标站点的中小规模采集数据清洗分析Pandas NumPy表格化数据处理的标准工具代码量少效果直观后端WebFlask轻量、灵活单文件就能起服务适合教学演示数据可视化ECharts 原生JavaScript图表美观、交互流畅、中文文档丰富前端工作量小为什么不用Scrapy因为毕设项目的数据量通常只有几千到几万条用requests写循环请求完全够用Scrapy的异步框架和中间件机制反而会增加理解和调试成本。为什么不用DjangoDjango自带ORM、Admin后台等一堆重量级功能在这个项目里用不到Flask把路由定义、数据返回、页面渲染处理好就够了。前端可视化我坚持用ECharts而不是Matplotlib。Matplotlib生成的是静态图片放在Web页面里交互性太差ECharts的图表支持鼠标悬停、缩放、点击联动这些操作演示效果好也显得系统“有点东西”。1.3 标题里的大模型、大数据、agent怎么理解这个标题最后挂了一串热词大模型、大数据、agent。说实话这套系统本身不依赖大模型也谈不上真正的海量大数据但这不代表这些词是空话。所谓“大数据”在这个项目里指的是“用工程化思维处理批量数据”的训练过程——采集多少条、清洗多少条、聚合维度怎么设计这些思维正是大数据开发的入门基础。至于大模型和agent它们是天然的扩展方向。比如抓下来的商品评论可以做情感分析传统方式用规则匹配现在可以直接调用大模型API做分类再比如做一个分析agent自动读取数据库里的最新数据生成“本周折扣力度最大的十个品牌”之类的结论。毕设答辩被问到“以后还能怎么改进”时这两个方向就是很好的论述点。2. 数据采集层用requests爬虫拿到商品数据2.1 先理清思路找接口而不是硬啃页面爬虫最容易犯的错是上来就用BeautifulSoup解析HTML。唯品会这类电商网站的商品列表是动态加载的页面上的数据大部分由JavaScript请求后端接口填充直接解析HTML往往只能拿到一个空壳。正确做法是打开浏览器开发者工具切到Network面板刷新页面筛选XHR请求找到返回商品JSON数据的那个接口。这个接口通常会带上搜索关键词、页码、排序方式等参数返回的JSON里就有商品标题、品牌、价格、销量等字段结构化程度比HTML高得多解析成本低一大截。拿到接口后先用requests模拟请求把返回结果打印出来看看结构。注意requests要带上请求头至少要有User-Agent很多平台对空UA的请求直接拒绝。import requests import json headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://www.vip.com/, Accept: application/json, text/plain, */*, } url https://你找到的商品列表接口URL params { keyword: 连衣裙, page: 1, pageSize: 60, } resp requests.get(url, headersheaders, paramsparams, timeout10) data resp.json() # 先打印出来确认字段结构 print(json.dumps(data, ensure_asciiFalse, indent2)[:2000])第一次请求不建议直接写全流程先确认三件事接口能不能通、返回的JSON里有哪些字段、分页参数是什么。这三件事确认了爬虫的主体逻辑其实就完成了一半。2.2 爬虫代码怎么写才不容易挂网络请求不是本地函数调用说失败就失败所以代码必须考虑健壮性。我在实际开发和带学生改代码时总结出几个必做点。第一是设置超时。requests不设置timeout碰到慢接口就可能一直挂在那里。我惯用timeout10超过10秒直接放弃本次请求。第二是重试机制。网络抖动是常态单次失败不意味着接口挂了用循环做三次重试每次重试间隔递增。第三是控制请求频率。电商平台对高频请求非常敏感在每一页请求之间加一个随机延时比如time.sleep(random.uniform(1, 3))既能有效降低被封风险又不会让采集速度慢到无法接受。第四是请求头要贴近真实浏览器至少带上User-Agent、Referer、Accept-Language这老三样。import time import random import requests def fetch_page(keyword, page, retries3): for attempt in range(retries): try: resp requests.get( url, params{keyword: keyword, page: page, pageSize: 60}, headersheaders, timeout10, ) if resp.status_code 200: return resp.json() elif resp.status_code 429: wait_time 30 * (attempt 1) print(f请求频率过高等待 {wait_time} 秒) time.sleep(wait_time) else: print(f请求失败状态码{resp.status_code}) except requests.RequestException as e: print(f请求异常{e}) time.sleep(2 * (attempt 1)) return None这里要重点说一个真实会碰到的坑HTTP 429 Too Many Requests。这个状态码的意思是服务器认为你请求太频繁直接限流了有时响应里还会带一句类似“exceeded retry limit”的提示。遇到429最忌讳的是继续硬刚正确做法是立刻降低频率、拉长等待时间或者换个入口继续。我自己测试时为了赶进度经常碰到这个后来学乖了把每次请求之间的间隔拉到3秒以上再配合随机延时基本就稳定了。2.3 能爬哪些字段与数据怎么落库商品列表接口能拿到的字段基本覆盖了毕设所需的全部维度。我常用的核心字段如下字段说明数据类型productId商品唯一ID用于去重字符串title商品标题字符串brand品牌名称字符串price当前售价浮点数originalPrice原价浮点数discount折扣力度字符串/浮点数salesVolume销量字符串/整数commentCount评论数整数category商品分类字符串shopName店铺名称字符串存储方式我推荐两种。数据量在几千条以内直接用CSV文件Pandas读写都方便也便于后续核对数据量超过几万条建议用SQLite轻量、无依赖Pandas的read_sql和to_sql可以直接对接。字段设计上有一个细节一定要保留productId并设置唯一约束后面清洗去重时全靠它。另一个细节是加一个updated_at时间戳字段记录数据抓取时间以后增量抓取或者做趋势分析都用得上。3. 数据清洗与数据分析从脏数据到有价值指标3.1 数据清洗到底洗什么很多学生不重视清洗觉得爬下来什么就是什么直接用。真实数据远没有你想的那么干净。我第一次跑完整流程时清洗前的数据有3200多条清洗完只剩2800多条近400条脏数据被去掉如果不清理图表里的结论全是错的。清洗工作主要分五步。第一步去重同一个商品可能出现在多个分类、多个页码里用productId去重是最稳妥的。第二步处理缺失值价格、品牌字段可能为空对于价格为空的数据直接删除对于品牌为空的数据可以填“未知品牌”具体看分析需求。第三步类型转换这是最容易被忽视的接口返回的price可能是字符串“¥299.00”要处理成float销量字段有时候是“1.2万件”这种写法要统一转换为整数。第四步异常值处理折扣力度大于1的数据、价格为0的数据、销量为负的数据基本都是脏的该删就删。第五步标准化比如把品牌名里的空格、全角字符统一处理保证同一个品牌不会出现两种写法。import pandas as pd df pd.read_csv(vip_raw.csv) # 1. 去重 df df.drop_duplicates(subsetproductId, keepfirst) # 2. 删除价格为空的记录 df df.dropna(subset[price, originalPrice]) # 3. 字符串价格转数值 df[price] df[price].str.replace(¥, ).str.replace(,, ).astype(float) df[originalPrice] df[originalPrice].str.replace(¥, ).str.replace(,, ).astype(float) # 4. 销量字段统一 def parse_sales(val): if isinstance(val, str) and 万 in val: return int(float(val.replace(万, )) * 10000) return int(val) df[salesVolume] df[salesVolume].apply(parse_sales) # 5. 过滤异常数据 df df[(df[price] 0) (df[originalPrice] df[price])] df df[df[salesVolume] 0] # 6. 品牌列清洗 df[brand] df[brand].str.strip().str.replace( , ) df.to_csv(vip_clean.csv, indexFalse, encodingutf-8-sig) print(df.info())注意最后存CSV的时候用encodingutf-8-sig否则用Excel打开会出现中文乱码。这是老生常谈的坑但几乎每届学生都会踩一次。3.2 核心分析指标怎么定数据洗完之后就要开始产生“价值”了。这个系统的分析指标不用搞太复杂围绕电商场景做主流的四类分析就够了。第一类是价格分布分析。把商品价格分成几个区间统计每个区间的商品数量再结合折扣信息看哪个价格区间的商品折扣力度更大。第二类是品牌集中度分析。全部商品涉及多少个品牌、每个品牌有多少款商品、平均折扣是多少做一个品牌Top10榜单。第三类是折扣力度分析。折扣当前价格/原价计算每个商品的折扣力度找出“大牌折扣”商品这是电商分析最有看点的部分。第四类是销量与评论分析。统计销量最高的商品、评论数最多的商品做排行榜方便用户在页面上直接看“大家都在买什么”。计算逻辑用Pandas的groupby和自定义函数就能完成完全没有必要上复杂的算法。毕设的重点是分析思路清晰指标有业务含义而不是堆砌高深数学。# 计算折扣力度 df[discountRate] df[price] / df[originalPrice] # 品牌维度聚合 brand_stats df.groupby(brand).agg( 商品数量(productId, count), 平均价格(price, mean), 平均折扣(discountRate, mean), 总销量(salesVolume, sum), ).reset_index() # 价格区间分布 bins [0, 50, 100, 200, 500, 1000, 5000] labels [0-50, 50-100, 100-200, 200-500, 500-1000, 1000以上] df[priceRange] pd.cut(df[price], binsbins, labelslabels) price_dist df[priceRange].value_counts().sort_index().reset_index() price_dist.columns [priceRange, count]3.3 为可视化准备数据格式可视化图表不认DataFrame它认的是JSON结构。ECharts最常见的数据格式是[{name: xxx, value: 100}, ...]这样的列表。所以在做后端接口之前需要先把分析结果转成前端友好的JSON格式。我在项目里习惯用一个简单的做法每个图表需求定义一个函数返回统一的JSON字典。比如价格分布接口返回[{name: 0-50, value: 123}, {name: 50-100, value: 456}]品牌Top10接口返回[{name: 品牌A, value: 89}, ...]。这样前端拿数据后几乎不做任何转换直接塞进ECharts的series.data里就行既省事又不容易出bug。这里再分享一个经验给前端的数据量要克制。比如品牌榜单只取Top10价格分布最多取8个区间数据量太大会让图表变得难读也拖慢页面加载。真正的分析系统不是把数据全部倒给用户而是把最有价值的信息提炼出来。4. Flask可视化展示层把数据变成能看的系统4.1 Flask应用结构与路由设计Flask项目不需要太复杂的目录结构但建议有一个清晰的最小划分一个app.py作为应用入口一个templates目录放HTML模板一个static目录放ECharts库和自定义JavaScript文件一个data目录放采集和分析好的数据文件。路由设计是这个系统的骨架。我惯用的路由有三个。第一个是“/”渲染主页面仪表盘打开系统默认进入的页面。第二个是“/api/summary”返回总览数据包括商品总数、品牌总数、平均折扣、总销量这些KPI数值。第三个是“/api/charts”返回所有图表数据前端一次请求把价格分布、品牌榜单、折扣Top10全拿回来。爬虫的触发也可以单独设一个“/api/update”接口在页面上放一个“更新数据”按钮点击后运行抓取和清洗流程方便演示数据刷新。from flask import Flask, render_template, jsonify import json import pandas as pd app Flask(__name__) app.route(/) def index(): return render_template(dashboard.html) app.route(/api/summary) def api_summary(): df pd.read_csv(data/vip_clean.csv) summary { total_products: int(len(df)), total_brands: int(df[brand].nunique()), avg_discount: round(float(df[discountRate].mean()), 2), total_sales: int(df[salesVolume].sum()), } return jsonify(summary) app.route(/api/charts) def api_charts(): with open(data/chart_data.json, r, encodingutf-8) as f: chart_data json.load(f) return jsonify(chart_data) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)为什么把图表数据预先算好存成JSON而不是每次请求现算因为分析结果在数据没更新之前是固定的没必要重复计算浪费CPU直接读文件速度更快代码也更简单。等到手动触发爬虫更新数据时再同步重新生成chart_data.json。4.2 可视化大屏怎么搭页面布局我推荐用经典的dashboard结构网上很多“可视化大屏”模板都是这个套路。顶部是四个KPI卡片分别展示商品总数、品牌数、平均折扣、总销量让用户进来第一眼就掌握全局。中间主体区域分成四块左侧放品牌Top10柱状图右侧放价格区间分布饼图下方放折扣力度Top10列表再配一个销量排行榜。布局用CSS Grid或者百分比宽度控制保证不同分辨率下不会乱掉。用CDN方式引入ECharts就可以不需要npm和打包工具。图表初始化代码放在window.onload里确保DOM渲染完成后再初始化。每个图表都是一个单独的div设置固定高度ECharts实例用echarts.init()创建然后通过fetch从后端接口拉数据塞进setOption。!DOCTYPE html html langzh head meta charsetUTF-8 title唯品会商品数据分析系统/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script /head body div classkpi-row div classkpi-card idtotal-products--/div div classkpi-card idtotal-brands--/div div classkpi-card idavg-discount--/div div classkpi-card idtotal-sales--/div /div div classchart-row div idbrandChart styleheight: 400px;/div div idpriceChart styleheight: 400px;/div /div /body /html图表配色的建议选一个有品牌科技感的主题色深蓝配亮蓝、青色比默认的红黄蓝好看很多。ECharts的color数组可以直接覆盖默认配色细节决定答辩时演示的观感。4.3 后端如何把数据喂给前端前端和后端的连接点是JSON接口。我建议定一个统一的返回格式成功时返回{code: 0, data: {...}}失败时返回{code: 1, msg: 错误信息}前端统一判断code再处理数据。这样后期的错误提示、加载状态都容易管理。前端用fetch请求接口时注意一个坑本地打开HTML文件直接用file://协议访问fetch请求file协议下的本地文件会受限而且如果你用Flask渲染页面页面源是localhost:5000接口也是localhost:5000不存在跨域问题放心用相对路径“/api/summary”请求就行。数据量方面接口返回的JSON越小越好。我遇到过学生把整张清洗后的DataFrame直接返回一次接口返回几MB数据页面卡到崩溃。记住一点接口只返回图表需要的最小数据集聚合、筛选都在后端完成前端只干渲染的活。4.4 项目运行与依赖管理依赖管理是新手最容易翻车的环节。一定不要只写代码不发requirements.txt否则换一台电脑根本跑不起来。这个项目的核心依赖其实很少Flask、requests、pandas加上可选的数据解析库。用requirements.txt固定版本最稳妥比如pandas2.1.4这种写法避免新版本API变动导致代码报错。启动流程很简单先pip install -r requirements.txt然后运行python app.py看到Running on http://0.0.0.0:5000就说明服务起来了。浏览器访问http://localhost:5000就能看到系统首页。用host0.0.0.0启动可以让同局域网的设备访问你的电脑IP加端口手机演示给同学看非常方便但一定要记得把debugFalse关掉否则调试模式在局域网访问下有风险。5. 常见问题排查与实操避坑实录5.1 刚跑起来最常见的5个问题这里我整理了一份问题速查表全部来自实际带项目的经验不是网上抄来的标准答案。问题现象根本原因解决办法pip安装pandas失败Python版本过低或缺少编译环境使用Python 3.8以上版本Windows用户直接下载whl离线安装爬虫请求返回403请求头不完整或缺少Cookie补全User-Agent、Referer、Accept-Language必要时登录后复制CookieCSV打开中文乱码编码不是utf-8-sig保存时用pd.to_csv(file, encodingutf-8-sig)图表显示空白容器div高度为0或数据格式不对给图表的div设置固定高度console.log打印接口返回数据核对字段Flask网页能开但接口500pandas读取文件路径错误用os.path.abspath打印当前目录检查data文件是否放在正确位置5.2 请求被限制之后的深度排查爬虫遇到拦截图是常态重要的是有清晰的排查顺序。我自己的排查逻辑是先看请求头再降频率最后考虑更重的方案。第一步确认请求头。很多入门代码只带User-Agent真实浏览器会带十多个请求头其中Referer尤其重要它告诉服务器你从哪个页面跳转过来的。第二步降频率。把单次请求间隔调到5秒每次请求之间sleep一下这个办法基本能解决大部分限流。第三步随机化。用fake_useragent库每次生成不同UA请求时随机延时让请求从“机器行为”变成“人类行为”。万一这些都不管用就要检查是不是商品ID需要登录态才能获取或者平台调整了接口规则。再说一遍一定要随时注意返回状态码。看到429就立刻停手等一段时间再继续硬刚的结果通常是IP被临时封禁反而影响后面的开发效率。5.3 可视化页面图表不显示的排查思路图表不显示是前端最常见的故障而且90%的时候不是代码写错而是数据问题。遇到图表空白我建议按下面的顺序排查。先按F12打开开发者工具切换到Console面板看有没有JavaScript报错。如果有报错大概率是变量名拼错或者数据是undefined。再切换到Network面板看/api/charts请求的状态码。如果是500后端报错如果是200点开响应内容看数据格式是不是和图表预期的一致。最常见的问题是后端返回的是字符串而不是数组或者字段名对不上比如接口返回name和value前端却取了label和value。另一个隐蔽问题图表初始化和数据异步加载的时序。如果window.onload还没触发就调用echarts.init容器可能还没渲染完成。稳定做法是把初始化放在window.onload里数据拉取用fetch的then回调保证“先有容器再初始化图表最后填数据”这个顺序严格不变。最后再分享一个我自己的小习惯每完成一个图表就在接口返回处加一行console.log(chartData)先确认数据没问题再怀疑展示层。数据正确的前提下图表渲染问题通常五分钟内就能定位。这套系统做完收获最大的其实不是那几行代码而是完整经历一遍“从数据到决策”整条链路知道数据从哪来、如何变干净、怎么分析才有价值、怎么样才能让人一眼看懂。如果你正在做类似的毕业设计我的建议是先跑通主流程再回头优化细节爬虫数据爬一次就落盘一份副本后续开发全部基于副本进行不要反复爬浪费时间和账号最后在答辩前一定多准备几个场景问题把“业务—技术—成果”串起来讲这套逻辑比单纯演示代码要加分得多。