
简介基于Python与ECharts的京东手机销售数据分析与可视化技术论文PDF面向电商从业者、数据分析人员及计算机专业学生与研究人员。论文完整介绍从京东官网爬取手机销售数据含参数与用户评价利用Pandas进行清洗去重再迁移至MySQL存储并通过FlaskECharts构建可视化界面的系统实现过程。资源为单个PDF文档共2.01MB包含摘要、系统框架、功能模块设计、数据清洗与可视化流程等核心章节。目前已有265人学习可用于电商平台选品分析、消费者购机决策参考和企业营销策略制定等场景适合需要快速了解电商数据采集、清洗、存储与可视化闭环的读者参考。通过阅读可以掌握爬虫应对反爬机制的策略、Pandas常用数据处理函数、数据库迁移方案以及前端图表与后端Flask联动的具体做法其中对反爬细节与数据清洗步骤的说明尤其具备实践参考价值对独立搭建类似数据分析项目有直接帮助。1. 京东手机销售数据分析一套能跑通的 PythonFlaskECharts 完整项目做电商数据分析的人手里最缺的不是算法而是一套“从数据到图表”能完整跑通的样板。这个基于 Python 和 ECharts 的京东手机销售数据分析与可视化项目正好把这条链路补全了爬虫抓京东手机参数和用户评价Pandas 清洗MySQL 存储最后用 FlaskECharts 拼出一个可视化大屏。它不是零散的代码片段而是一个带登录、带图表联动、带词频统计的完整系统。适合三类人刚学完 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 FlaskECharts 的分工边界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?cat9987%2C653%2C655evexbrand_ brand page str(i) s str(j) click0其中 brand 是品牌名称i 是页码步长为 2j 是偏移量步长为 60。为什么 i 的步长是 2因为京东列表页每翻一页实际加载两页的内容第一次请求 page1、s1第二次请求 page3、s61以此类推。s 参数决定从第几条记录开始展示步长 60 正好对应每页 30 条商品的两倍。如果只改 page 不改 s你会拿到重复的数据。后 30 条的结构稍有不同url1 https://list.jd.com/listNew.php?cat9987%2C653%2C655evexbrand_ t page str(k) s str(z) scrollingy这里 k 取 range(2, 4, 2) 即值为 2z 取 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 # 连接 MySQLcharset 必须指定 utf8 否则中文乱码 con mysqlpymysql://root:123localhost:3306/jdsystem?charsetutf8 conn pymysql.connect(hostlocalhost, userroot, password123, dbjdsystem, charsetutf8) 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 的参数除了固定值也可以用 methodffill 做前向填充但这里的场景用固定值“其他”更合适。写入 MySQL 用参数化查询%s 占位符一定不要用字符串拼接否则 SQL 注入风险和一个引号错位就能让入库脚本挂掉。4.3 结巴分词评论内容的前置处理对用户评论内容做词云之前必须经过结巴分词和词频统计。这里需要加载中文停用词表把“的”“了”“是”这类无意义词过滤掉否则词云里全是没有信息量的虚词。加载停用词表的常见做法import jieba stopwords set() with open(../data/stopwords.txt, r, encodingutf-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 查询结果变成 JSONFlask 后端的工作量不大但有几个坑要提醒。先写一个查询函数从 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(hostlocalhost, userroot, password123, dbjdsystem, charsetutf8) 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 直接接收 brandsseries 的 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 CHARSETutf8连接字符串里加上 charsetutf8。已经建错的表用 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 原生的 intjsonify 无法序列化。解决在返回前做类型转换用 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 配置这个排查顺序成了固定习惯。做数据可视化系统链路越长越要培养这种“先定位再修改”的思维希望这套项目笔记能帮你在自己的数据可视化项目里少走几步弯路。本文还有配套的精品资源点击获取