简介:基于Python与ECharts的京东手机销售数据分析与可视化技术论文PDF,面向电商从业者、数据分析人员及计算机专业学生与研究人员。论文完整介绍从京东官网爬取手机销售数据(含参数与用户评价),利用Pandas进行清洗去重,再迁移至MySQL存储,并通过Flask+ECharts构建可视化界面的系统实现过程。资源为单个PDF文档,共2.01MB,包含摘要、系统框架、功能模块设计、数据清洗与可视化流程等核心章节。目前已有265人学习,可用于电商平台选品分析、消费者购机决策参考和企业营销策略制定等场景,适合需要快速了解电商数据采集、清洗、存储与可视化闭环的读者参考。通过阅读,可以掌握爬虫应对反爬机制的策略、Pandas常用数据处理函数、数据库迁移方案,以及前端图表与后端Flask联动的具体做法,其中对反爬细节与数据清洗步骤的说明尤其具备实践参考价值,对独立搭建类似数据分析项目有直接帮助。
1. 京东手机销售数据分析:一套能跑通的 Python+Flask+ECharts 完整项目
做电商数据分析的人,手里最缺的不是算法,而是一套“从数据到图表”能完整跑通的样板。这个基于 Python 和 ECharts 的京东手机销售数据分析与可视化项目,正好把这条链路补全了:爬虫抓京东手机参数和用户评价,Pandas 清洗,MySQL 存储,最后用 Flask+ECharts 拼出一个可视化大屏。它不是零散的代码片段,而是一个带登录、带图表联动、带词频统计的完整系统。适合三类人:刚学完 Python 基础想练手的学生,需要做电商数据报表的运营,以及想快速搭建内部数据看板的开发。我拆完这套资源后最大的感受是:技术栈不新,但胜在链路完整,尤其翻页 URL 规律的挖掘和异常数据的清洗规则,都是可以直接抄作业的硬通货。
2. 系统框架与数据流:从京东页面到可视化大屏的完整管线
2.1 三段式架构:爬虫独立、清洗独立、展示独立
整个系统分成三个松耦合模块:数据爬虫模块、数据清洗模块和数据可视化模块。这种拆分不是拍脑袋定的,而是考虑到三个环节的失败模式完全不同——爬虫可能被封 IP,清洗可能遇到脏数据,可视化可能遇到图表不渲染。三者独立后,哪个环节出问题就单独排查哪个,不至于牵一发动全身。
数据流向是单向的:爬虫把京东手机详情页的数据写入 CSV 文件作为过渡存储,Pandas 读取 CSV 做清洗和格式化,清洗后的数据通过 pymysql 迁移进 MySQL 的三张表,最后 Flask 后端从 MySQL 查询数据并转成 JSON 返回给前端,前端页面里的 ECharts 拿到 JSON 数据渲染图表。这套流程里 CSV 其实是个“后悔药”设计——清洗脚本跑挂了,或者清洗规则改动了,直接从 CSV 重新跑就行,不用重新爬一遍网站,这个习惯值得保留。
2.2 MySQL 表设计:四张表撑起整个业务
系统设计了四张表:手机详情信息表(p_info)、用户评价标签表(p_tag)、用户评论内容表(p_comment)和用户注册表(user)。前两张表的结构设计得比较典型,直接决定后续统计 SQL 好不好写。p_info 表的核心字段包括 id、brand、shop、title、os、价格、上市年份、上市月份、机身重量、厚度、长度、摄像头数量等 30 个参数;p_tag 表则包含 id、brand、shop、title、tag 和 num,其中 tag 存评价标签文本,num 存该标签的统计数量。这种设计的好处是:品牌维度统计直接 group by brand,评价标签的词频直接 sum(num),不需要在查询时做复杂的字符串解析。
MySQL 连接串是标准写法,注意 charset 必须指定 utf8,否则中文写入会变乱码。连接参数建议单独维护在一个配置文件中,不要写死在业务代码里,后面换库或者调连接池都方便。
2.3 Flask+ECharts 的分工边界
Flask 在这里只做两件事:提供登录注册接口,以及把 MySQL 查询结果转成 JSON 返回给前端。图表渲染完全交给 ECharts 在前端完成,这种分工能减少后端压力,也让图表交互(比如 tooltip 悬停、图例筛选)不依赖网络请求。前端使用 HTML、CSS、JavaScript 和 JQuery,通过 JQuery 的 ajax 从 Flask 接口拉数据,拿到数据后再用 ECharts 的 setOption 填充图表。
有个细节值得注意:Flask 返回 JSON 时,如果数据是 numpy 类型(比如用 pandas 的 groupby 算出来的结果),直接 jsonify 会报错。需要先用 tolist() 或 astype 转成 Python 原生类型,这是最常见的翻车点。
3. 爬虫实现细节:京东翻页 URL 规律与反爬应对
3.1 前 30 条和后 30 条商品的不同请求方式
京东手机展示页的 URL 规则是这套系统的核心资产。每一页的商品展示分为前 30 条和后 30 条,前 30 条的请求地址是 list.html,后 30 条是 listNew.php,两者的翻页参数规则完全不同。前 30 条的 URL 结构为:
url = 'https://list.jd.com/list.html?cat=9987%2C653%2C655&ev=exbrand_' + brand + '&page=' + str(i) + '&s=' + str(j) + '&click=0'其中 brand 是品牌名称,i 是页码(步长为 2),j 是偏移量(步长为 60)。为什么 i 的步长是 2?因为京东列表页每翻一页实际加载两页的内容,第一次请求 page=1、s=1,第二次请求 page=3、s=61,以此类推。s 参数决定从第几条记录开始展示,步长 60 正好对应每页 30 条商品的两倍。如果只改 page 不改 s,你会拿到重复的数据。
后 30 条的结构稍有不同:
url1 = 'https://list.jd.com/listNew.php?cat=9987%2C653%2C655&ev=exbrand_' + t + '&page=' + str(k) + '&s=' + str(z) + '&scrolling=y'这里 k 取 range(2, 4, 2) 即值为 2,z 取 range(27, 87, 60) 即值为 27。也就是说后 30 条的 page 从 2 开始,s 从 27 开始。循环用 zip 函数把两个序列配对,代码更简洁。cat 参数 9987%2C653%2C655 是京东手机品类的分类 ID,这个值是从京东页面源码里挖出来的,不同品类这个值不一样,如果要爬别的品类需要重新抓取。
3.2 代理 IP 和睡眠时间:应对反爬的基本操作
京东的反爬机制会让频繁请求的 IP 直接失效,返回的页面里没有商品数据。系统采用了两层应对:设置代理 IP 池和每次请求后睡眠。代理 IP 放在 requests 的 proxies 参数里,建议维护一个 IP 列表,每次请求随机取一个。睡眠时间不是固定值,而是用 random.uniform(1, 3) 生成随机延时,避免被识别出固定节奏的机器人行为。
关于评论数据,京东对部分商品的评论详情页做了限制——只有前几页有完整评论内容。所以这里只抓取每件商品的前 10 页评论,超过的放弃。不要试图死磕把全部评论抓完,抓取成本远大于分析价值。还要注意设置 User-Agent,模拟浏览器的请求头,不要暴露 Python 默认的 urllib 标识。
3.3 字段采集与 CSV 落盘
抓下来的数据先写入 CSV 文件,不直接入库。原因是采集下来的数据格式差异很大:有的商品详情页没有上市年份,有的没有机身重量,字段缺失情况参差不齐。先落 CSV 再做清洗,比直接入库再改表结构灵活得多。写入 CSV 用 pandas 的 to_csv 方法,注意 encoding 参数设为 utf-8-sig,否则 Excel 打开会乱码。
4. 数据清洗与 MySQL 入库:Pandas 操作与入库规则
4.1 清洗规则:哪些数据必须删,哪些必须填
数据清洗是本系统的核心环节,直接决定后续可视化的准确性。项目里制定的清洗规则可以总结为四类:去重、去异常、填空值、统一格式。去重使用 drop_duplicates 函数,按商品 ID 去重;去异常是删除明显不合理的数据,比如价格小于 100 元的手机(大概率是配件或空壳链接)、机身重量小于等于 100g、机身长度小于 131mm,这些数据明显不符合手机的基本物理属性。
空值处理上有个值得借鉴的决定:不直接删除含空值的行,而是把空值填充为“其他”。原因在原文里写得很清楚——数据有限,删行会损失样本量。这个思路在真实项目里很实用:如果数据量不大,删行的代价可能比填充更大。品牌名的中英文不统一问题,用 replace 函数做映射替换。比如英文品牌名替换成中文常用名,这一步不做的话后续 group by 会把同一个品牌拆成两个统计项。
4.2 清洗代码实现与参数说明
import pandas as pd import pymysql # 连接 MySQL,charset 必须指定 utf8 否则中文乱码 con = 'mysql+pymysql://root:123@localhost:3306/jdsystem?charset=utf8' conn = pymysql.connect(host='localhost', user='root', password='123', db='jdsystem', charset='utf8') cur = conn.cursor() def clean_info(): # 读取爬虫落盘的 CSV 文件 df = pd.read_csv('../crawl/data/jdInfo.csv') # 去除重复值,subset 指定按商品 ID 判断重复 df = df.drop_duplicates(subset=['id']) # 删除不合理数据:价格小于100元、重量小于等于100g、 # 长度小于13.1cm的数据直接扔掉 df = df.drop(df[(df['price'] < 100) | (df['weight'] <= 100) | (df['length'] < 131)].index) # 空值填充为“其他”,不删行避免样本量损失 df = df.fillna('其他') # 品牌中英文统一替换,比如 'HUAWEI' -> '华为' df['brand'] = df['brand'].replace({'HUAWEI': '华为', 'Xiaomi': '小米'}) # 清洗后的数据写回 MySQL for row in df.itertuples(): sql = "INSERT INTO p_info (brand, shop, title, os, price) VALUES (%s, %s, %s, %s, %s)" cur.execute(sql, (row.brand, row.shop, row.title, row.os, row.price)) conn.commit()drop 函数里传入的是一个布尔索引,这个写法比逐行判断高效得多。注意 drop 之后要重新赋值给 df,否则原数据不会变。fillna 的参数除了固定值,也可以用 method='ffill' 做前向填充,但这里的场景用固定值“其他”更合适。写入 MySQL 用参数化查询(%s 占位符),一定不要用字符串拼接,否则 SQL 注入风险和一个引号错位就能让入库脚本挂掉。
4.3 结巴分词:评论内容的前置处理
对用户评论内容做词云之前,必须经过结巴分词和词频统计。这里需要加载中文停用词表,把“的”“了”“是”这类无意义词过滤掉,否则词云里全是没有信息量的虚词。加载停用词表的常见做法:
import jieba stopwords = set() with open('../data/stopwords.txt', 'r', encoding='utf-8') as f: for line in f: stopwords.add(line.strip()) def tokenize(text): words = jieba.lcut(text) # 过滤停用词、单字词和纯数字 return [w for w in words if w not in stopwords and len(w) > 1 and not w.isdigit()]jieba.lcut 返回的是分词后的列表。过滤条件里的 len(w) > 1 是为了去掉单字词,分词结果里大量单字词往往是噪声。停用词表需要自己维护,不同场景的词表差异很大——做手机评价分析,可以把“手机”也加进停用词,不然这个词的词频会碾压其他一切有意义的关键词。
5. 可视化实现与 Flask 数据对接:ECharts 图表配置实战
5.1 图表选型:条形图、饼图、柱状图、词云图的场景匹配
项目根据数据特点做了图表选型:统计店铺拥有手机数量用条形图,因为条形图适合展示排名型数据;统计手机上市年份用折线图,展示趋势变化;统计摄像头数量用饼图,展示占比结构;统计屏幕刷新率分布和运行内存用饼图;统计充电器功率、屏幕材质类型用柱状图。这个选型逻辑是通用的:排名看条形图、趋势看折线图、占比看饼图、对比看柱状图。
5.2 Flask 后端:把 MySQL 查询结果变成 JSON
Flask 后端的工作量不大,但有几个坑要提醒。先写一个查询函数从 MySQL 提取统计数据,再通过 Flask 路由返回 JSON。比如统计各品牌手机数量:
from flask import Flask, jsonify, request import pymysql app = Flask(__name__) @app.route('/api/brand_count', methods=['GET']) def brand_count(): conn = pymysql.connect(host='localhost', user='root', password='123', db='jdsystem', charset='utf8') cur = conn.cursor() cur.execute("SELECT brand, COUNT(*) FROM p_info GROUP BY brand ORDER BY COUNT(*) DESC") rows = cur.fetchall() # 拆成两个列表,方便 ECharts 的 xAxis 和 series 直接使用 brands = [r[0] for r in rows] counts = [r[1] for r in rows] return jsonify({'brand': brands, 'brandNum': counts})这里把查询结果拆成两个平行列表返回,而不是返回字典列表,是为了前端 ECharts 的 option 配置更方便——xAxis 的 data 直接接收 brands,series 的 data 直接接收 counts。如果返回字典列表,前端还要再做一层 map 转换,纯属浪费。Flask 路由的 methods 参数明确指定 GET,避免不必要的 POST 请求误入。
5.3 ECharts 前端配置:条形图的 option 详解
品牌手机数量条形图的 ECharts 配置是整个项目里最标准的图表模板,可以直接复用:
var option1 = { title: { top: 7, left: 20, text: '品牌拥有手机数量排行' }, tooltip: { trigger: 'axis', axisPointer: { type: 'none' } }, legend: { left: 500, top: 10, data: ['数量'] }, toolbox: { show: true, feature: { saveAsImage: { show: true }, dataView: { show: true } } }, xAxis: { type: 'category', axisLine: { show: false }, data: json_data['brand'] }, yAxis: { type: 'value', axisLabel: { show: true } }, series: [{ name: '数量', type: 'bar', data: json_data['brandNum'], itemStyle: { barBorderRadius: 5 } }] }; myChart1.setOption(option1);tooltip 的 trigger 设为 'axis' 意味着鼠标悬停时按坐标轴维度显示提示,比默认的 item 更适合条形图这种按分类展示的场景。legend 的 left 设 500 是因为大屏布局中多个图表共存,需要手动调整位置避免重叠;如果单图展示,left 用 'center' 即可。itemStyle 的 barBorderRadius 是 ECharts 5 里圆角柱子的配置,属 UI 细节但很影响观感。axisLine 设为 false 隐藏 x 轴的轴线,视觉上更干净。
5.4 词云图实现:ECharts 的词云需要额外插件
系统要展示用户评价标签和评论内容的词云图,但原生 ECharts 不带词云图组件,需要引入 echarts-wordcloud 插件。词云图的数据源是结巴分词后统计的词频,格式为 [{ name: '华为', value: 123 }, ...]。词云的渲染效果受两个参数影响:shape 决定词云的轮廓形状(circle、diamond 等),sizeRange 控制字号范围。字号大小与词频正相关,所以词频统计的准确性直接影响视觉效果——这也是停用词表要维护好的原因。
6. 避坑指南:爬虫到可视化全链路常见问题排查
6.1 爬虫请求返回空白页面
现象:requests 请求京东列表页,返回的 HTML 里没有任何商品数据,打印出来的内容长度不到 1KB。
原因:京东对高频请求做了 IP 封禁或验证码拦截,裸 requests 请求很容易被识别。也可能是 URL 参数顺序不对,京东对参数顺序敏感。
解决:先检查 User-Agent,必须在请求头里伪装浏览器。然后加随机睡眠时间,用 time.sleep(random.uniform(1, 3))。如果还不行,启用代理 IP。验证 URL 是否正确的办法是手动在浏览器打开同样的 URL,如果能正常显示页面,说明 URL 没问题,问题在请求头或 IP。
6.2 清洗后的数据写入 MySQL 报错
现象:pymysql 写入时报错 "Incorrect string value: '\xE5\x8D\x8E...' for column 'brand'",中文数据写不进去。
原因:表的字符集不是 utf8,或者连接字符串的 charset 参数没设置。京东采集的数据里有大量中文品牌名和商品名,字符集不匹配就会报这个错。
解决:建表时指定 DEFAULT CHARSET=utf8,连接字符串里加上 charset='utf8'。已经建错的表用 ALTER TABLE p_info CONVERT TO CHARACTER SET utf8mb4 修改。注意 utf8mb4 比 utf8 更全,能存下 emoji 表情,如果评论区有特殊符号建议直接用 utf8mb4。
6.3 ECharts 图表不显示,控制台报错
现象:页面加载后图表区域空白,浏览器控制台提示 "Cannot read property 'getContext' of null"。
原因:ECharts 初始化时传入的 DOM 容器还没渲染完成,或者容器的 id 对不上。最常见的是把初始化脚本放在 body 前面,此时容器元素还不存在。
解决:把初始化脚本放在页面底部( 之前),或者用 window.onload 包一层。检查容器 div 是否有明确的宽度和高度,ECharts 默认宽高为 0 时不会渲染任何内容。
6.4 Flask 返回 JSON 时出现 TypeError
现象:jsonify 报错 "Object of type 'int64' is not JSON serializable"。
原因:pandas 的 groupby 结果里数据类型是 numpy.int64,不是 Python 原生的 int,jsonify 无法序列化。
解决:在返回前做类型转换,用 int() 或 float() 包裹数值,或者用 df 的 tolist() 方法把整列转成 Python 列表。这个坑几乎每个用 pandas 接 Flask 的项目都会踩,可以在工具函数里统一做一次转换。
6.5 爬取过程中断后重新跑,数据重复
现象:第一次爬了 3000 条数据,程序中断后重跑,数据库里出现 6000 条,其中一半重复。
原因:没有去重机制。重跑爬虫会重新写入同样的数据,而 p_info 表的 id 字段没有设置唯一约束。
解决:两个层面处理。一是给 MySQL 表的 id 字段加 PRIMARY KEY 或 UNIQUE 约束,重复写入会直接报错;二是清洗脚本里的 drop_duplicates 指定 subset 参数,按业务唯一键(比如商品链接 URL)去重。建议两个层面都做,数据库约束兜底,pandas 清洗保证业务正确。
7. 进阶技巧:词云图参数调优与图表主题风格定制
词云图是这套系统里最出效果的图表,也是参数调整空间最大的地方。做用户评价词云时,shape 参数我用过 'circle' 和 'diamond',实际效果 circle 更适合商品评价场景,视觉焦点居中,高频词向中心聚拢;diamond 适合大屏装饰性展示。更重要的是 minRotation 和 maxRotation 这对参数,控制词云里文字的旋转角度范围,我通常设为 0 和 90,只保留横排和竖排两种方向,避免出现 30 度、45 度这种看着杂乱的角度。还有一个容易忽略的点:词云图的文字颜色建议用 ECharts 的 visualMap 组件根据词频做渐变映射,高频词用深色,低频词用浅色,层次感会出来。
图表主题风格可以统一处理。项目里多个图表共存,每个都单独写 option 会越来越难维护。我一般抽一个 commonTheme 对象,把 fontFamily、colorPalette、tooltip 的通用样式放进去,每个图表用 ECharts 的 merge 机制覆盖自己的差异项。大屏的配色建议控制在三到四种主色内,不要每个图表用不同色系,否则视觉上会乱。
我自己在跑这套系统时,最有挫败感的一步不是爬虫不是清洗,而是花了半小时查 ECharts 图表不渲染的原因——最后发现是容器 div 的 height 写成了百分比但父级没有高度。从那以后我每次写可视化前端,都强制先检查容器宽高、再检查数据格式,最后才看 option 配置,这个排查顺序成了固定习惯。做数据可视化系统,链路越长越要培养这种“先定位再修改”的思维,希望这套项目笔记能帮你在自己的数据可视化项目里少走几步弯路。
本文还有配套的精品资源,点击获取