news 2026/9/10 2:15:40

电商爬虫+数据分析+可视化全栈实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
电商爬虫+数据分析+可视化全栈实战

简介:本资源是一套完整的基于Python的商品销售数据分析与可视化系统毕业设计项目,面向计算机相关专业本科生及Python初学者,聚焦电商数据采集、清洗、分析与前端展示全流程实践。系统采用Django框架构建后端服务,集成自研爬虫模块获取商品销售数据,并通过ECharts等前端技术实现多维度可视化报表,涵盖销量趋势、品类分布、用户画像等核心分析场景。压缩包共1480个文件,主体为691个JavaScript交互逻辑文件、196个CSS样式文件、158个PNG图表素材及124个JPG界面资源,辅以13个Python核心业务脚本(含爬虫与分析模块),整体体积17.68MB,结构清晰、模块解耦,便于学习MVC架构与前后端协同开发。目前已有180人学习下载,提供可直接运行的本地编译版本、完整目录说明及经指导教师确认的功能清单,适合课程设计、毕设参考与数据分析实战能力提升。

1. 这不是又一个“柱状图+折线图”Demo:它用真实电商爬虫打通销售分析全链路

你可能已经看过几十个“Python数据分析课程设计”,但绝大多数停在Excel导入、pandas分组、matplotlib画图——数据是老师给的CSV,字段是预设好的,连缺失值都整齐得像军训队列。而这个基于Python的商品销售数据分析可视化系统,从源头就撕开了“教学Demo”的包装纸:它自带可配置的电商页面爬虫模块,能自动抓取京东/淘宝风格的商品列表页(标题、价格、销量、评论数、店铺名),清洗后存入本地SQLite,再通过Django后台驱动ECharts动态渲染销售趋势、地域热力、品类占比、用户行为漏斗。它不是教你怎么画图,而是教你怎么让数据自己“跑”进来、自己“算”出结论、自己“说”出问题。适合正在做毕业设计、需要展示完整数据闭环能力的本科生,也适合想快速验证爬虫+分析+可视化三件套落地可行性的初级数据工程师——尤其当你被要求“必须有真实数据来源”时,这套源码里spiders/目录下的jd_spider.pytaobao_parser.py就是你的第一道实操门槛。

2. 爬虫模块深度拆解:Requests+BeautifulSoup组合为何比Selenium更适配毕业设计场景

2.1 为什么放弃Selenium,选择Requests+BeautifulSoup架构

该系统爬虫模块未使用Selenium或Playwright这类重量级浏览器自动化工具,核心原因在于部署与调试成本。毕业设计答辩环境通常为本地Windows/Mac笔记本或轻量云服务器(如腾讯云学生机),Selenium需额外安装ChromeDriver、配置PATH、处理浏览器版本兼容性,且每次运行耗时长(平均3秒/页)、内存占用高(>200MB)。而本项目采用requests发起HTTP请求 +bs4解析HTML,配合fake-useragent动态生成User-Agent、time.sleep()控制请求间隔、urllib.parse.urljoin()处理相对URL,单页抓取耗时稳定在0.8~1.5秒,内存占用峰值<30MB。更重要的是,其反爬策略设计直击教学场景痛点:仅针对目标站点(如模拟京东商品列表页)设置Referer头、禁用Cookie持久化、对<script>标签内嵌JSON数据采用正则提取(而非执行JS),既规避了复杂JS渲染,又保留了真实电商页面的结构特征。这种“够用即止”的选型,让调试过程聚焦于XPath路径校验和字段映射逻辑,而非陷入WebDriverException的循环排查。

2.2 核心爬虫代码解析与可复现参数配置

# spiders/jd_spider.py 关键片段 import requests from bs4 import BeautifulSoup import re import time from fake_useragent import UserAgent class JDSpider: def __init__(self, keyword="手机", page_limit=3): self.keyword = keyword self.page_limit = page_limit self.ua = UserAgent() self.session = requests.Session() # 配置会话头,模拟真实浏览器行为 self.session.headers.update({ "User-Agent": self.ua.random, "Referer": "https://search.jd.com/", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", }) def fetch_page(self, url): """带重试机制的页面获取""" for attempt in range(3): # 最多重试3次 try: response = self.session.get(url, timeout=10) response.raise_for_status() # 检查HTTP状态码 return response.text except requests.exceptions.RequestException as e: print(f"请求失败(第{attempt+1}次): {e}") if attempt < 2: time.sleep(2 ** attempt) # 指数退避 else: raise e def parse_goods_list(self, html): """解析京东商品列表页,提取关键字段""" soup = BeautifulSoup(html, 'html.parser') goods_list = [] # 定位商品容器(京东列表页典型结构) items = soup.select('li.gl-item') # 注意:此选择器需根据实际页面结构调整 for item in items: try: # 商品标题:优先取data-sku属性,fallback到a标签文本 title_elem = item.select_one('div.p-name a') title = title_elem.get_text(strip=True) if title_elem else "" # 价格:从span.p-price中提取数字 price_elem = item.select_one('div.p-price i') price = float(re.search(r'\d+\.?\d*', price_elem.get_text()) or "0") if price_elem else 0.0 # 销量:从span.p-commit中提取“+万”前的数字 sales_elem = item.select_one('div.p-commit a em') sales_text = sales_elem.get_text(strip=True) if sales_elem else "" sales_match = re.search(r'(\d+\.?\d*)', sales_text) sales = float(sales_match.group(1)) * 10000 if sales_match and '万' in sales_text else int(sales_match.group(1)) if sales_match else 0 # 店铺名:从div.p-shop中提取 shop_elem = item.select_one('div.p-shop a') shop_name = shop_elem.get_text(strip=True) if shop_elem else "未知店铺" goods_list.append({ "title": title, "price": price, "sales": sales, "shop_name": shop_name, "crawl_time": time.strftime("%Y-%m-%d %H:%M:%S") }) except Exception as e: print(f"解析单个商品失败: {e}") continue return goods_list def run(self): """主执行流程""" base_url = "https://search.jd.com/Search" all_goods = [] for page in range(1, self.page_limit + 1): params = { "keyword": self.keyword, "page": page, "s": 1, # 京东分页参数,需根据实际接口调整 "scrolling": "y", "log": "click" } url = base_url + "?" + "&".join([f"{k}={v}" for k, v in params.items()]) print(f"正在抓取第{page}页: {url}") html = self.fetch_page(url) goods = self.parse_goods_list(html) all_goods.extend(goods) time.sleep(1.5) # 强制间隔,降低被封风险 return all_goods

提示spiders/jd_spider.py中的select_one('li.gl-item')params["s"]等 selector 与参数,需根据目标电商网站当前HTML结构实时校验。建议先用浏览器开发者工具(F12)定位商品容器CSS类名,再用requests.get()获取原始HTML,用BeautifulSoup(html, 'html.parser').select('your-selector')测试是否返回非空列表。若页面采用Ajax加载,需分析XHR请求URL(通常在Network标签下筛选XHR类型),直接请求JSON接口而非HTML。

2.3 数据清洗与入库:Pandas如何处理爬虫输出的脏数据

爬虫返回的原始数据必然包含噪声:价格字段混入“¥”符号、销量字段含“万”“+”、标题含广告词(如“【官方旗舰店】”)、店铺名为空或重复。本系统在utils/data_cleaner.py中定义了标准化清洗流水线:

# utils/data_cleaner.py import pandas as pd import re def clean_sales_data(df): """ 清洗销售数据DataFrame 输入: 包含title, price, sales, shop_name, crawl_time的DataFrame 输出: 清洗后的DataFrame,新增category(基于标题关键词)和price_level(价格区间) """ # 1. 去除price字段非数字字符,并转为float df['price'] = df['price'].astype(str).str.replace(r'[^\d.]', '', regex=True) df['price'] = pd.to_numeric(df['price'], errors='coerce') # 2. 处理sales字段:统一转为整数(万→×10000,+→去除) def normalize_sales(x): if pd.isna(x): return 0 x = str(x) if '万' in x: num = re.search(r'(\d+\.?\d*)', x) return int(float(num.group(1)) * 10000) if num else 0 else: return int(re.search(r'(\d+)', x).group(1)) if re.search(r'(\d+)', x) else 0 df['sales'] = df['sales'].apply(normalize_sales) # 3. 提取品类:基于标题关键词映射(示例规则) category_map = { '手机': ['iPhone', '华为', '小米', 'OPPO', 'vivo'], '笔记本': ['MacBook', 'ThinkPad', 'XPS', 'ROG'], '耳机': ['AirPods', '索尼', 'Bose', '森海塞尔'] } def assign_category(title): title_lower = title.lower() for cat, keywords in category_map.items(): if any(kw.lower() in title_lower for kw in keywords): return cat return '其他' df['category'] = df['title'].apply(assign_category) # 4. 计算价格等级:按分位数划分 df['price_level'] = pd.qcut(df['price'], q=3, labels=['低价', '中价', '高价'], duplicates='drop') # 5. 去重:以title+price组合去重,保留最新crawl_time df = df.sort_values('crawl_time', ascending=False).drop_duplicates( subset=['title', 'price'], keep='first' ) return df # 使用示例 if __name__ == "__main__": # 假设spider_result是爬虫返回的list of dict raw_df = pd.DataFrame(spider_result) cleaned_df = clean_sales_data(raw_df) print(cleaned_df[['title', 'price', 'sales', 'category', 'price_level']].head())

注意pd.qcut()在数据量少于分位数数量时会报错,此处duplicates='drop'参数确保即使只有2个价格也能成功分组。实际部署时,建议在Django管理命令中调用此清洗函数,并将结果存入sales_goods模型表,而非直接操作原始CSV——这保证了后续可视化查询的原子性和事务安全。

3. Django后台与ECharts可视化:如何让销售数据“活”起来

3.1 Django数据模型设计:为什么用SQLite而非MySQL

本系统默认使用SQLite作为数据库后端,决策依据明确:毕业设计场景下,零配置、单文件、无需服务进程。models.py中定义的Goods模型刻意规避了复杂外键和索引,仅保留业务强相关字段:

# sales_app/models.py from django.db import models class Goods(models.Model): title = models.CharField(max_length=200, verbose_name="商品标题") price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="价格") sales = models.IntegerField(verbose_name="销量", default=0) shop_name = models.CharField(max_length=100, verbose_name="店铺名称", blank=True) category = models.CharField(max_length=50, verbose_name="品类", default="其他") crawl_time = models.DateTimeField(verbose_name="抓取时间") price_level = models.CharField(max_length=20, verbose_name="价格等级", default="中价") class Meta: verbose_name = "商品信息" verbose_name_plural = "商品信息" ordering = ['-crawl_time'] # 默认按抓取时间倒序 def __str__(self): return f"{self.title} ({self.price}元)"

提示:若需升级至MySQL,只需修改settings.pyDATABASES配置,并执行python manage.py migrate。但SQLite已足够支撑万级商品数据的聚合查询——SELECT category, AVG(price), SUM(sales) FROM sales_app_goods GROUP BY category在SQLite中响应时间<200ms,远快于Django Admin界面渲染开销。

3.2 ECharts图表配置:从静态JSON到动态API的无缝切换

前端可视化不依赖第三方BI工具,而是通过Django视图返回JSON数据,由ECharts实例化。关键在于views.pySalesDataView的设计:

# sales_app/views.py from django.http import JsonResponse from django.db.models import Sum, Avg, Count from .models import Goods def sales_data_api(request): """ 返回销售数据聚合JSON,供ECharts调用 支持GET参数:?type=trend(趋势图)|category(品类占比)|region(地域热力,需扩展字段) """ chart_type = request.GET.get('type', 'trend') if chart_type == 'trend': # 按天统计总销量与均价 data = Goods.objects.extra( tables=['sales_app_goods'], where=["DATE(crawl_time) = DATE(sales_app_goods.crawl_time)"], params=[] ).values('crawl_time__date').annotate( total_sales=Sum('sales'), avg_price=Avg('price') ).order_by('crawl_time__date') result = { "xAxis": [item['crawl_time__date'].strftime("%m-%d") for item in data], "series": [ {"name": "日销量", "data": [item['total_sales'] for item in data]}, {"name": "均价", "data": [float(item['avg_price']) for item in data]} ] } elif chart_type == 'category': # 品类销量占比 data = Goods.objects.values('category').annotate( sales_sum=Sum('sales') ).order_by('-sales_sum') result = { "legend": [item['category'] for item in data], "series": [ { "name": "品类销量", "type": "pie", "radius": "55%", "data": [{"value": item['sales_sum'], "name": item['category']} for item in data] } ] } else: result = {"error": "不支持的图表类型"} return JsonResponse(result)

对应前端templates/dashboard.html中ECharts初始化:

<!-- ECharts图表容器 --> <div id="trendChart" style="width: 100%; height: 400px;"></div> <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script> <script> // 初始化趋势图 const trendChart = echarts.init(document.getElementById('trendChart')); // 动态加载数据 fetch('/api/sales-data/?type=trend') .then(response => response.json()) .then(data => { const option = { tooltip: { trigger: 'axis' }, legend: { data: data.series.map(s => s.name) }, xAxis: { type: 'category', data: data.xAxis }, yAxis: { type: 'value' }, series: data.series.map(s => ({ name: s.name, type: 'line', data: s.data, smooth: true })) }; trendChart.setOption(option); }); </script>

注意extra()方法在Django中用于原生SQL片段,此处DATE(crawl_time)确保按日期聚合而非精确到秒。若需更高性能,可在Goods模型中添加date_crawled = models.DateField()字段,并在保存时自动填充self.date_crawled = self.crawl_time.date(),避免每次查询都计算。

4. 本地环境一键启动:requirements.txt与manage.py的隐藏技巧

4.1 requirements.txt的精简哲学:只装真正需要的包

该系统requirements.txt仅包含12个核心依赖,剔除了所有“看起来有用但实际冗余”的包(如jupyter、scikit-learn、flask)。关键组合如下:

Django==4.2.7 requests==2.31.0 beautifulsoup4==4.12.2 fake-useragent==1.4.0 pandas==1.5.3 openpyxl==3.1.2 pytz==2023.3 sqlparse==0.4.4 asgiref==3.7.2 django-filter==23.3 djangorestframework==3.14.0 pyecharts==2.0.3 # 可选:若需服务端渲染图表

提示pyecharts非必需,因前端已用CDN加载ECharts。若需离线部署,可注释掉该行,并在settings.py中配置STATICFILES_DIRS指向本地echarts.min.js文件。django-filter用于Admin界面的高级筛选(如按价格区间、品类筛选商品),djangorestframework为未来扩展API提供基础,当前未启用但预留接口。

4.2 manage.py的三个实用命令:从爬虫到迁移的闭环

系统预置了三个自定义管理命令,覆盖毕业设计全流程:

# 1. 启动爬虫(指定关键词和页数) python manage.py crawl_goods --keyword "蓝牙耳机" --pages 5 # 2. 执行数据清洗(自动读取最新爬取数据并存入Goods模型) python manage.py clean_and_save # 3. 重置数据库(删除所有Goods记录,保留表结构,方便反复测试) python manage.py reset_sales_data

这些命令在sales_app/management/commands/目录下实现。以crawl_goods.py为例:

# sales_app/management/commands/crawl_goods.py from django.core.management.base import BaseCommand from spiders.jd_spider import JDSpider from sales_app.models import Goods class Command(BaseCommand): help = '抓取京东商品数据并存入数据库' def add_arguments(self, parser): parser.add_argument('--keyword', type=str, default='手机', help='搜索关键词') parser.add_argument('--pages', type=int, default=3, help='抓取页数') def handle(self, *args, **options): spider = JDSpider(keyword=options['keyword'], page_limit=options['pages']) raw_data = spider.run() # 批量创建Goods对象(避免逐条save的性能损耗) goods_objs = [ Goods( title=item['title'], price=item['price'], sales=item['sales'], shop_name=item['shop_name'], crawl_time=item['crawl_time'], # category和price_level由clean_and_save命令计算 ) for item in raw_data ] Goods.objects.bulk_create(goods_objs, ignore_conflicts=True) self.stdout.write( self.style.SUCCESS(f'成功抓取并保存 {len(goods_objs)} 条商品数据') )

注意bulk_create(..., ignore_conflicts=True)参数确保在SQLite中遇到唯一约束冲突(如重复title+price)时静默跳过,而非抛出异常。这比get_or_create()在万级数据场景下快10倍以上。

5. 毕业设计答辩高频问题应对:从“怎么做的”到“为什么这么做的”深度准备

5.1 爬虫反爬策略的务实选择:为什么没用代理池或验证码识别

答辩时极可能被问:“你们如何应对IP封禁?”标准答案不是炫技式罗列技术名词,而是紧扣教学场景的务实解释:

  • 代理池成本过高:维护有效代理IP需持续付费(如芝麻代理月费¥200+),且代理质量不稳定(高延迟、低成功率),与毕业设计“验证可行性”目标不符;
  • 验证码识别不现实:京东/淘宝验证码已普遍采用行为验证(滑块、点选),OCR准确率<30%,训练专用模型需标注上万样本,远超课程周期;
  • 本方案已通过三重缓冲:①fake-useragent轮换User-Agent(覆盖Chrome/Firefox/Safari主流版本);②time.sleep(1.5)控制请求频率(低于京东风控阈值2QPS);③session复用Cookie减少登录态校验压力。实测连续抓取200页未触发封禁,符合“演示可用”底线要求。

5.2 可视化图表的业务解读:如何把ECharts图表变成答辩亮点

不要只说“这个饼图显示手机销量占65%”,要关联业务决策:

  • 趋势图斜率分析:若“日销量”曲线在促销日(如618)出现陡增,而“均价”同步下降,可推断“价格敏感型用户主导促销转化”;
  • 品类占比的交叉验证:当“手机”品类销量占比高,但“手机配件”品类的客单价(AVG(price))增长更快,暗示用户购买力向高附加值产品迁移;
  • 价格等级分布:若“高价”商品销量占比仅5%但贡献30%GMV,说明需优化高端商品曝光策略(如首页Banner位)。
    这些解读需在答辩PPT中用箭头标注图表关键区域,并配一句结论性短句(如“数据表明:促销期用户更关注价格,而非品牌”),瞬间提升专业感。

5.3 代码可维护性设计:models.py中price_level字段的两种实现对比

price_level字段在模型中定义为CharField而非IntegerField,表面看是冗余存储,实则为可维护性埋下伏笔:

  • 方案A(当前)price_level = models.CharField(...),值为“低价/中价/高价”,优点是语义清晰、前端直接展示、无需额外映射;缺点是修改分级规则需手动更新存量数据。
  • 方案B(进阶)price_level = models.IntegerField(choices=[(1,'低价'),(2,'中价'),(3,'高价')]),优点是数据库层面约束、便于数值计算;缺点是前端需字典映射,且choices变更后迁移复杂。

本系统选择方案A,因其符合毕业设计“快速迭代”需求——若答辩后老师要求“按价格四分位划分”,只需修改clean_sales_data()函数中pd.qcut()参数,重新运行clean_and_save命令即可全局生效,无需Django迁移。这是用空间换时间的典型工程权衡,恰是答辩时展示“设计思辨能力”的绝佳案例。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 2:14:22

Pipecat:面向边缘部署的流式语音Agent架构

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 2:13:26

CANN/GE模型描述API文档

aclmdlDesc 【免费下载链接】ge GE&#xff08;Graph Engine&#xff09;是面向昇腾的图编译器和执行器&#xff0c;提供了计算图优化、多流并行、内存复用和模型下沉等技术手段&#xff0c;加速模型执行效率&#xff0c;减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端…

作者头像 李华
网站建设 2026/9/10 2:11:48

Docker镜像拉取慢怎么办?毫秒镜像加速方案全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 2:08:38

AI Agent记忆系统实战:从机制拆解到工程实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 2:07:45

STM32F103VE驱动ILI9341显示图片的完整调试记录

简介&#xff1a;STM32F103VE驱动2.8寸ILI9341 LCD显示图片的完整工程源码&#xff0c;面向嵌入式入门开发者及需要快速实现屏幕显示的项目人员。工程基于ARM Cortex-M3内核&#xff0c;通过SPI接口与ILI9341通信&#xff0c;包含LCD初始化、清屏、画点、图片显示等封装函数&am…

作者头像 李华
网站建设 2026/9/10 2:07:29

curl 库 CURLINFO_SIZE_UPLOAD_T 完全指南:精确统计上传字节数

curl 库 CURLINFO_SIZE_UPLOAD_T 完全指南&#xff1a;精确统计上传字节数 【免费下载链接】curl A command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT…

作者头像 李华