☰
京东商品数据采集可视化平台实战:Python+Django+Selenium+Echarts
2026/9/30 3:09:12 网站建设 项目流程

每年三四月份,后台咨询毕业设计选题的总会扎堆出现。电商数据方向基本是每年都有人问的热门,而"京东商品数据采集分析可视化平台"这个题目,又是其中最有代表性的一类:Python+Django+Selenium爬虫+Echarts,四个关键词放在一起,爬虫、后端、数据库、可视化全都要沾,对课程设计来说难度适中,对毕业设计来说又能撑起完整的功能闭环。这篇就按我做过的同类项目的经验,把这个平台的每个模块怎么设计、为什么这么设计、实际踩过哪些坑,完整拆开讲一遍。如果你打算做这类题目,或者已经在写代码但遇到一堆报错,建议先收藏再慢慢看。

1. 选题拆解:这个毕设题目到底在考察哪几项能力

1.1 从题目反推评分点

拿到毕设题目的第一步不是急着打开IDE,而是先把题目里的每个技术名词拆开,反推评委想看什么。Python对应的是编程基本功,包括类的组织、函数划分、异常处理、代码风格;Django对应的是后端框架掌握程度,包括模型设计、ORM操作、视图函数、URL路由、模板渲染;Selenium对应的是动态页面抓取能力,包括元素定位、浏览器自动化、登录态维护、常见反爬应对;Echarts对应的则是前端数据可视化能力,包括图表选型、JSON数据格式转换、页面布局和交互配置。

说白了,这套题就是在考察一条完整的数据链路:采集、清洗、存储、分析、展示。你只要把这条链路走通,每个环节都有东西可写、有图表可看,论文和答辩就都不愁没内容。反过来,很多同学只盯着爬虫部分,把数据抓下来之后随便导个Excel就结束了,Django和Echarts沦为摆设,那这道题的价值就丢掉了一半——评委一眼就能看出来你是在凑工作量,还是在认真做一个完整的平台。

1.2 为什么是Python+Django+Selenium+Echarts这个组合

有人会问,爬虫用Scrapy不是更专业吗?为什么毕业设计偏偏爱用Selenium?我先说答案:因为毕业设计评的是"过程",而不只是"结果"。Scrapy基于异步网络请求,性能确实高,但代码抽象层次也高,中间件、管道、选择器全是自己的体系,新手调起错来容易懵。Selenium走的是浏览器自动化路线,打开真实浏览器、定位元素、提取文本、点击翻页,每一步都能看到实际效果,代码逻辑非常直白。

再从京东这个具体目标看。京东的商品列表页有大量数据是靠JavaScript动态渲染的,你直接用requests去请求HTML源码,拿到的可能是一堆空的占位标签,商品标题和价格根本不在里面。Selenium驱动的是真实浏览器,页面脚本会正常执行,渲染完的数据自然能拿到。对不熟悉前端渲染机制的同学来说,这是最不容易翻车的选型。再加上Django负责建模、ORM、后台管理、API接口,Echarts负责把数据变成柱状图、饼图、折线图,整条链路顺滑、演示效果好,老师一眼就能看到你的工作量分布。

1.3 一个可以直接落地的功能清单

我建议把平台拆成下面几个功能模块,既可以当开发计划,也可以直接当论文的章节骨架:

  • 商品数据采集:输入搜索关键词,自动抓取京东搜索结果里的商品标题、价格、店铺、评论数、商品链接
  • 数据持久化:把抓取结果写入SQLite或MySQL,用商品链接做唯一标识,实现去重
  • 数据管理后台:用Django Admin直接查看和维护采集结果,支持删除脏数据
  • 统计接口:后端按分类、价格区间、评论数量做聚合统计,输出JSON
  • 可视化大屏:Echarts展示分类占比饼图、价格分布柱状图、评论量排行条形图、价格走势折线图
  • 定时更新(可选):用APScheduler定期触发采集,让平台有时间序列的"活数据"

优先级从前往后,时间不够就砍掉最后一项,但核心链路仍然是完整的。我自己带人做项目时,从来都是先保证这条链路全通,再谈锦上添花的模块。

2. 采集层落地:Selenium抓取京东商品数据的完整思路

2.1 先观察页面,再写定位代码

写爬虫最忌讳上来就写脚本。正确做法是先手动打开页面,按F12把DOM结构看清楚再动手。京东的流程是:访问主站,搜索框输入关键词回车,进入搜索结果页,商品列表是class为gl-item的li标签,每个li包含一张商品卡。

以"机械键盘"搜索结果为例,核心字段的CSS选择器如下:

  • 商品标题:li.gl-item > .p-name em
  • 价格:.p-price i
  • 店铺名称:.p-shop
  • 评论数:.p-commit strong的title属性或文本,常见"200万+"
  • 商品链接:.p-img a的href属性

注意,这些选择器对应的页面结构京东偶尔会调整,正式写代码之前一定要在DevTools里用querySelector验证一遍。另外,任何元素查找都不能裸写find_element后立刻.text,页面滚动加载、图片加载慢都会导致元素还没出现就报NoSuchElementException。稳妥做法是用WebDriverWait显式等待,等元素可交互之后再操作。

2.2 登录态与Cookie处理

京东搜索页不登录也能看,但连续翻页多了容易弹登录框和验证码。最省事的方案是手动登录一次,把Cookie持久化保存,之后每次采集自动加载。具体操作分两步:

第一次登录时,用Selenium打开京东首页,在代码里暂停,手动扫码登录,登录成功后抓取Cookie存成JSON文件。第二次运行,先访问一次京东域名建立会话,再把Cookie逐条写回浏览器,最后刷新页面即可。

import json from selenium import webdriver driver = webdriver.Chrome() # 首次登录:手动扫码后保存 cookie driver.get("https://www.jd.com") input("登录完成后按回车继续...") with open("jd_cookies.json", "w", encoding="utf-8") as f: json.dump(driver.get_cookies(), f) # 再次运行:加载 cookie driver.get("https://www.jd.com") with open("jd_cookies.json", "r", encoding="utf-8") as f: cookies = json.load(f) for cookie in cookies: driver.add_cookie(cookie) driver.refresh()

这里有个很容易踩的坑:add_cookie之前必须先让浏览器访问一次该域名,否则会报invalid cookie domain。我自己第一次写的时候就没注意,纠结了好半天才发现是调用顺序的问题。

2.3 ChromeOptions反检测参数配置

Selenium启动的浏览器有一个明显特征:window.navigator.webdriver属性为true。京东这类大站对自动化特征识别很敏感,访问频率一旦上去,滑块验证码就来了。我常用的处理方式是在启动参数里做几层处理,最核心的一段如下:

from selenium.webdriver.chrome.options import Options options = Options() options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("useAutomationExtension", False) options.add_argument("--window-size=1920,1080") options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") driver = webdriver.Chrome(options=options) driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); window.navigator.chrome = {runtime: {}}; """ })

第一行的AutomationControlled参数把"正在受自动化控制"的提示关掉,下面几行去掉浏览器右上角的自动化测试标签,最后用CDP在每次新页面加载前把webdriver属性改回undefined。这套组合是网上广泛验证过的通用做法,能解决绝大多数站点的基础特征检测。

但比参数更重要的,是访问节奏。Selenium翻页间隔一定要随机抖动,建议每页间隔2到6秒,翻几页后再随机停一下,模拟真人查找商品的操作。毕业设计演示场景下,几十条数据的采集量完全没必要追求速度,慢一点、稳一点,反而不容易触发验证码。

2.4 商品信息提取与清洗规则

抓到的原始文本几乎不能直接入库存:标题里混着换行和空格,价格文本可能带着"促销价""满减"等干扰信息,评论数显示成"200万+"。我习惯把所有清洗逻辑抽到一个独立函数模块里,保持主流程干净:

import re def clean_title(title): return re.sub(r"\s+", "", title) def clean_price(price_text): match = re.search(r"\d+(\.\d+)?", price_text.replace(",", "")) return float(match.group()) if match else 0.0 def clean_comment_count(text): text = text.strip() if "万" in text: return int(float(text.replace("万", "")) * 10000) match = re.search(r"\d+", text.replace(",", "")) return int(match.group()) if match else 0

清洗完的数据先写CSV,再批量导入Django的ORM模型。先落CSV的好处是:即使后面Django改模型字段,原始数据还在,不用重新爬一遍。我自己做项目时CSV文件一直保留到答辩结束,中间改过两轮模型,全靠这份备份兜底。

Selenium脚本的主函数大致长这样:接收关键词和页数参数,循环翻页,每页提取商品卡片,清洗后追加到结果列表,最后统一返回。记得用try/finally确保driver.quit()一定执行,不然浏览器进程会一直占着内存,下次启动时就报"process already running"。

3. Django后端设计:数据模型、采集命令与图表接口

3.1 三张核心表的设计

先用django-admin startproject和startapp把工程建好,我的项目结构习惯是这样:

jd_platform/ ├── manage.py ├── jd_platform/ # 项目配置目录 └── analyzer/ # 采集与统计主应用 ├── models.py ├── views.py ├── management/ │ └── commands/ │ └── fetch_jd.py # 采集命令 ├── spider/ │ ├── jd_spider.py # Selenium采集逻辑 │ └── cleaners.py # 清洗函数 └── templates/ └── dashboard.html # 可视化大屏

数据模型我建议至少建三张表,表之间用外键串联:

from django.db import models class Category(models.Model): name = models.CharField(max_length=50, unique=True) class Product(models.Model): title = models.CharField(max_length=255) url = models.URLField(unique=True) price = models.FloatField(default=0.0) shop = models.CharField(max_length=100, blank=True) comment_count = models.IntegerField(default=0) category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True) created_at = models.DateTimeField(auto_now_add=True) class PriceRecord(models.Model): product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="price_records") price = models.FloatField() record_time = models.DateTimeField(auto_now_add=True)

Product表存商品当前快照,PriceRecord表存不同批次的价格,折线图就靠它来画。url字段加unique约束天然防重复,重复抓取时用get_or_create或先查后插都可以。Category单独拆一张表是为了分类统计方便,也符合关系型数据库的规范设计。表建好后记得依次执行python manage.py makemigrations和python manage.py migrate。

3.2 采集逻辑与Django的集成方式

很多同学纠结Selenium脚本和Django项目怎么合并。最简单的想法是把采集逻辑直接写进视图函数,点击页面按钮触发,但这样有个明显问题:Django开发服务器默认同步处理请求,采集几十页数据需要几分钟,浏览器页面会一直转圈,体验非常差。

我的做法是把采集逻辑写成Django的自定义管理命令。这样命令行python manage.py fetch_jd 机械键盘 --pages 3就能独立触发采集,不占用Web请求线程,也方便答辩时手动演示。命令文件放在analyzer/management/commands/fetch_jd.py里,核心结构如下:

from django.core.management.base import BaseCommand from analyzer.spider.jd_spider import fetch_jd_products class Command(BaseCommand): help = "采集京东商品数据" def add_arguments(self, parser): parser.add_argument("keyword", type=str) parser.add_argument("--pages", type=int, default=2) def handle(self, *args, **options): count = fetch_jd_products(options["keyword"], options["pages"]) self.stdout.write(self.style.SUCCESS(f"采集完成,共 {count} 条商品"))

如果你想做定时采集,就在这个命令外面加APScheduler,每天固定时间跑一次,配合PriceRecord表自然形成价格时间序列数据。这一步是很好的加分项,答辩时可以专门提一句。

3.3 给Echarts喂数据的接口约定

可视化部分需要的是结构化JSON,而不是HTML。在views.py里写几个聚合接口,统一返回JsonResponse,前端用fetch或axios拉取就行。最常用的分类统计接口长这样:

from django.http import JsonResponse from django.db.models import Count, Avg, Sum from analyzer.models import Product def category_stats(request): data = list(Product.objects.values("category__name").annotate( count=Count("id"), avg_price=Avg("price"), total_comments=Sum("comment_count") )) return JsonResponse({"data": data})

类似的接口还可以写价格区间分布、评论数TOP10商品、指定商品的价格时间序列。接口返回字段统一用英文命名,前端取值不容易出错,这是我在多个项目里养成的习惯。写完后直接用浏览器访问接口地址,确认JSON结构符合预期,再去做前端图表,不然等页面写完才发现接口字段对不上,排查成本会翻倍。

4. Echarts可视化大屏:图表选型、数据对接与常用配置

4.1 先给每个图表定一个业务问题

Echarts图表类型很多,但可视化不是堆得越多越好。我的习惯是先列业务问题,再选图表:

  • 分类占比:哪个品类的商品数量最多?用饼图
  • 价格区间分布:商品价格集中在哪个区间?用柱状图
  • 评论量排行:哪件商品讨论热度最高?用横向条形图
  • 价格走势:某几件商品价格随时间怎么变化?用折线图
  • 价格与评论量关系:价格高的商品评论一定更多吗?用散点图

一张大屏安排四到六个图表就足够撑起演示。页面最顶部可以做几个统计卡片,显示总商品数、平均价格、最高评论数,这些数据从同一个接口拿,前端算一下就行。图表之间最好有联动,比如点击饼图某个分类,右侧柱状图只展示该分类下的价格分布,这种交互在答辩时非常加分,Echarts的dispatchAction事件可以实现,代码量不大。

4.2 Django向Echarts传数据的两种方式

第一种是模板直渲染:Django视图把聚合结果转成JSON字符串放进context,模板里通过safe过滤器直接注入JavaScript变量。

import json from django.shortcuts import render from analyzer.models import Product def dashboard(request): chart_data = json.dumps( list(Product.objects.values("category__name").annotate(count=Count("id"))), ensure_ascii=False ) return render(request, "dashboard.html", {"chart_data": chart_data})

模板侧就是一行代码:

<script> const rawData = {{ chart_data|safe }}; </script>

第二种是AJAX异步拉取:页面加载后,用fetch分别请求分类统计、价格区间等接口,拿到数据再调chart.setOption更新图表。这种方式和3.3小节的接口天然搭配,首屏渲染更快,也方便做联动筛选。

我的建议是两者结合:首屏直渲染关键数据,保证一打开就有图;交互联动部分走AJAX。纯靠AJAX有个小风险,如果接口报错页面就是一片空白,答辩时不好解释。

4.3 大屏布局与常用配置

Echarts图表容器高度必须显式设置,这是新手最常踩的坑。容器div只有宽度没有高度,图表根本画不出来。布局上我用最简单的CSS Grid,把大屏分成上下两行,每行两个图表:

<div class="grid"> <div id="pie-category" style="height: 320px;"></div> <div id="bar-price" style="height: 320px;"></div> <div id="bar-comments" style="height: 320px;"></div> <div id="line-trend" style="height: 320px;"></div> </div>

常用配置里,柱状图可以加渐变色让视觉效果更好,饼图通过label的formatter显示百分比,折线图别忘了在xAxis里设置boundaryGap: false,否则折线图两头会多出一截空白:

chart.setOption({ tooltip: { trigger: 'axis' }, xAxis: { type: 'category', boundaryGap: false, data: dates }, yAxis: { type: 'value' }, series: [{ type: 'line', data: prices, smooth: true }] });

Echarts建议直接用官方CDN的4.x版本,文档多、资料全,不需要npm构建流程,对毕设项目来说最省心。加载方式就是script标签引用,再配合一个简单的初始化函数就能跑起来。

5. 环境配置与排错:从零跑通整套项目的全过程

5.1 环境清单与版本匹配

版本匹配是这套技术栈里最容易被忽视的部分。我把推荐版本整理成一张表,照着装就行:

组件推荐版本说明
Python3.10 / 3.113.12 以上部分依赖可能还没适配
Django4.2 LTS稳定,资料多,教程好找
Selenium4.x新API,不推荐再用老式driver路径写法
Chrome 浏览器最新稳定版与 ChromeDriver 版本必须严格对应
Echarts4.9.0 CDN兼容性好,功能够用

安装命令就三条:

python -m venv venv venv\Scripts\activate # Windows 环境 pip install django selenium webdriver_manager

重点工作在ChromeDriver上。之前手动下载驱动容易版本不匹配,现在直接推荐用webdriver_manager,它能根据你本机Chrome版本自动下载对应驱动:

from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

如果你本机不方便装Chrome,也可以用headless无头模式跑,但毕业设计演示时我强烈建议保留有头模式,让浏览器窗口在投影上自动打开、自动翻页、自动提取数据,这个视觉冲击力比任何截图都强,评委对"爬虫到底做了什么"的理解会直接上一个台阶。

5.2 按顺序跑通项目的路径

第一次跑项目,我建议按下面这个顺序来,不要一上来就写完整代码:

  1. 先写最小Selenium脚本,能打开京东搜索页并打印前十条商品标题
  2. 再补清洗逻辑,输出到CSV文件,人工检查数据质量
  3. 建Django工程和数据模型,把CSV数据导入数据库
  4. 写聚合接口,用浏览器访问接口确认JSON结构正确
  5. 写前端页面接入Echarts,确认每个图表都能正常渲染
  6. 把Selenium整合成管理命令,联调完整采集链路
  7. 最后加分项:定时任务、数据筛选、导出功能

每一层跑通再往下一层走,报错范围会小很多。我最常看到的情况是:同学一口气写完所有代码,然后面对一整屏Traceback无从下手,不得不从第一个报错开始倒退排查,效率极低。分层开发虽然看起来慢,实际上是最快的方式。

5.3 新手最容易卡的几个报错

下面这几个坑是我带人做项目时几乎每组都会遇到的,提前看一遍能省很多时间:

报错/现象原因处理方式
WebDriverException: 'chromedriver' executable needs to be in PATH驱动没装或路径没配置改用 webdriver_manager 自动处理
SessionNotCreatedExceptionChrome 和 ChromeDriver 版本不匹配更新 Chrome 或更换驱动版本
NoSuchElementException元素还没加载完就定位用 WebDriverWait 显式等待
ElementClickInterceptedException按钮被登录弹层或悬浮层盖住先关闭弹层再执行点击
中文乱码控制台默认编码问题设 PYTHONIOENCODING=utf-8
采集到一半弹验证码频率太高或浏览器特征被识别加随机延迟、检查Cookie、核对反检测参数

排错方法论只有一条:先看异常类型,再定位到具体行号,不要整段代码瞎猜。Selenium的报错信息其实很友好,只要你养成分段调试的习惯,绝大多数坑十分钟内能解决。遇到不确定的元素问题,直接driver.save_screenshot("debug.png")截图看现场,比自己脑补页面状态靠谱得多。

6. 答辩演示与代码讲解:让评委跟着你的思路走

6.1 演示前一天要做的三件事

第一,数据库里预置一批完整数据,保证断网也能演示。很多同学只准备了一条实时采集的演示路径,结果现场网络波动或者验证码弹窗,整个答辩就卡壳了。正确做法是提前用脚本采集200条以上商品数据入库,演示时先打开大屏展示已有的可视化效果,再现场触发一次小规模采集,展示从抓取到入库的完整流程。

第二,把演示用的采集参数固定好。比如python manage.py fetch_jd 机械键盘 --pages 1,一次只爬一两页,现场三十秒内能完成,节奏刚好。千万别在答辩时爬十页数据,所有人干等两分钟,气氛会非常尴尬。

第三,准备一条"代码讲解路线图"。老师通常会让你讲爬虫实现,这时候光说"这就是爬虫代码"等于白说。要能按"打开浏览器—搜索页面—定位商品块—提取字段—清洗数据—ORM入库—聚合统计—Echarts渲染"这条线,把代码串着讲一遍。你讲得有条理,评委自然觉得你对自己的项目有完整理解。

6.2 现场采集翻车时的应急预案

现场最怕的不是代码bug,而是环境问题。我见过好几次因为演示前忘了启动Django服务,打开浏览器一片白的情况。建议在家把完整流程演练至少三遍,把启动命令写成一个bat或sh脚本,一键执行。

如果现场真的采集失败,别慌,按下面顺序处理:

  • 立即切到数据库里已有的数据,告诉评委"这是之前采集入库的历史批次"
  • 检查浏览器窗口是否弹了验证码,如果是,退出后重新加载Cookie再试
  • 如果页面整个打不开,直接切回代码讲解,说明相关模块在干净环境下的运行逻辑

大部分评委在乎的是你是否理解自己的项目,而不是现场零失误。你能把报错原因讲清楚,比对着代码手足无措强得多。我见过一个学弟现场遇到驱动版本报错,他直接说"这是ChromeDriver版本和浏览器不匹配导致的,用webdriver_manager可以自动解决",然后手改两行代码重跑成功,评委反而给了很高评价。

6.3 容易被追问的五个问题及回答思路

  1. 为什么选Selenium而不是Scrapy?回答思路:京东列表页依赖JavaScript动态渲染,requests拿到的静态HTML不完整;Selenium驱动真实浏览器,能处理JS渲染、登录态和验证码,虽然性能不如Scrapy,但在这个场景下稳定性和可调试性更高。

  2. 反爬怎么处理的?回答思路:随机延时、Cookie持久化、隐藏webdriver特征、控制采集频率。重点强调"低频、学习用途"这个边界,态度要坦然。

  3. 数据准确吗?回答思路:数据以页面展示为准,评论数里的"万+"做了单位换算,价格清洗掉了促销文案干扰,趋势分析层面的误差可控。如果评委较真,可以补充说这是演示级数据,不做商业用途。

  4. 能不能扩展成分布式爬虫?回答思路:可以,采集层替换成Scrapy或集成Celery异步任务,数据层加Redis做队列和缓存,这套架构是现成的扩展路径,说明自己了解行业主流方案即可。

  5. 钱和精力花在哪了?回答思路:把时间分配讲清楚,比如40%在采集与清洗,30%在Django接口,20%在可视化,10%在排错,让评委感受到工作量实打实。

最后说一个我自己的习惯:项目做完,不要急着交掉就完事。把采集脚本、清洗函数、接口文档各自整理成独立模块,留下清晰的注释,这对你后续做其他数据类项目来说就是现成的工具箱。答辩前多跑几遍,把每个报错截图存下来整理成一页排错笔记,演示时一旦出问题,翻笔记比现场搜索高效得多。祝看到这里的同学答辩顺利,项目一把过。

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

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

立即咨询