☰
Python数据分析实战:京东手机数据采集清洗可视化全流程
2026/9/26 4:34:51 网站建设 项目流程

想用Python做数据分析练手,很多人第一反应就是拿电商平台开刀。京东手机这个品类是我个人认为最适合新手当作分析系统的切入场景:SKU足够多、价格梯度清晰、品牌格局稳定、评价数据也丰富,一套流程跑下来,基本能把Python数据分析的常见环节全过一遍。

这篇文章我就把整个系统的落地过程拆开讲清楚,从数据怎么来、怎么洗,到怎么算、怎么画,再到过程中踩过的坑,全部按实际开发顺序摊开说,适合有一定Python基础、想完整走一遍数据分析项目的人参考。

1. 项目概述与目标拆解

1.1 为什么选择京东手机这个场景

做数据分析系统,最难的不是写代码,是选数据源。选错了数据源,后面所有环节都会卡住。京东手机这个场景有三个优势,几乎是为练手量身定做的。

第一,数据维度完整。手机商品的详情页、评价区、价格曲线、品牌专营店的店铺信息,加在一起能覆盖“价格、销量、口碑、品牌”这四个最典型的分析维度,不会出现想分析评价却没有评价数据这种尴尬。

第二,品类结构稳定。手机不像服装那样款式极度过剩,也不是生鲜那种价格剧烈波动的品类。它的品牌梯队、价位带分布都比较清晰,做出来分析结论有可解释性,不会算出一堆“不知道为什么”的数字。

第三,数据量适中。单页30个商品、几十页左右的样本量,对个人电脑的内存和处理速度非常友好,不需要上分布式计算,一台普通笔记本就能跑完整个流程。

说白了,这个项目练的不只是爬虫,而是把“采集—清洗—分析—可视化”这个完整链路打通的能力。这套方法论学会之后,换成别的平台、别的品类,只需要改字段映射和分析维度,大体框架直接复用。

1.2 系统整体架构与模块划分

整个系统我按职责拆成四层,层与层之间通过CSV文件或DataFrame传递数据,不搞复杂的消息队列,避免过度设计。

  • 采集层:负责从京东商品列表页、详情页获取商品名称、价格、店铺、评价数、好评率等原始字段。这一层是最容易出问题的,也是耗时最长的部分。
  • 清洗层:负责把采集到的脏数据整理成规范格式,包括字段去重、缺失值处理、价格字符串转数值、品牌信息提取等。
  • 分析层:负责核心指标计算,包括价格区间分布、销量区间分布、品牌集中度、评价口碑对比等。
  • 展示层:负责把分析结果制作成图表和简易报告。优先使用Pyecharts生成HTML页面,方便直接打开查看。

开发环境方面,我使用的是Python 3.9版本,IDE用的PyCharm。如果电脑上还没装好Python,建议先完成基础环境配置,再跑下面的代码,避免把时间浪费在环境问题上。

这个分层设计的核心原则是“每层只干一件事”。比如采集层不需要关心数据怎么分析,展示层不需要关心数据从哪来,这样任何一个环节出问题,都能快速定位。

2. 数据采集层:拿到第一手手机销售数据

2.1 采集方案的选型思路

拿到需求之后,先别急着写代码。我当时对比了三套方案:直接调京东官方API、用Selenium渲染浏览器、用Requests模拟HTTP请求。

官方API这条路最正规,但京东开放平台对个人开发者门槛比较高,需要企业资质,个人项目基本走不通。Selenium方案能完美绕过大多数反爬限制,因为它的行为就是真实用户在操作浏览器,但缺点是速度慢、资源占用高,抓几十页数据要跑很久。Requests方案速度最快、代码最简洁,但需要手动处理UA(User-Agent)伪造、Cookie、请求频率限制等反爬策略。

最终的选型是用Requests作为主力,配合一套可控的请求头伪装和延时策略。这样既保证了速度,又不会因为请求过快触发风控。这种方案适合中小体量的采集任务,也是目前个人数据分析项目里最常见的做法。

2.2 核心采集流程与关键代码

采集流程大体上分三步。第一步,先访问商品列表页,拿到当前页面的商品ID列表;第二步,根据商品ID访问详情页,提取完整字段;第三步,翻页循环,直到采集到预设的商品数量。

核心代码如下:

import requests import time import random from bs4 import BeautifulSoup import pandas as pd HEADERS = { "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", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Referer": "https://www.jd.com/" } def fetch_page(page_num): url = f"https://search.jd.com/Search?keyword=手机&page={page_num * 2 - 1}" resp = requests.get(url, headers=HEADERS, timeout=10) resp.encoding = 'utf-8' soup = BeautifulSoup(resp.text, 'lxml') items = [] for li in soup.select('.gl-item'): try: item_id = li.get('data-sku') name = li.select_one('.p-name a em').text.strip() price = li.select_one('.p-price i').text.strip() comment = li.select_one('.p-commit a').text.strip() shop = li.select_one('.p-shop a').text.strip() if li.select_one('.p-shop a') else '' items.append([item_id, name, price, comment, shop]) except Exception as e: print(f"解析商品失败: {e}") continue return items def collect_data(page_start, page_end): all_data = [] for page in range(page_start, page_end + 1): print(f"正在采集第 {page} 页...") page_data = fetch_page(page) all_data.extend(page_data) time.sleep(random.uniform(1.5, 3.5)) df = pd.DataFrame(all_data, columns=['商品ID', '名称', '价格', '评价数', '店铺']) df.to_csv('jd_phone_raw.csv', index=False, encoding='utf-8-sig') print(f"采集完成,共 {len(df)} 条记录")

这段代码里有几个细节值得说明。首先,京东搜索页的翻页规则是页码参数为一个奇数序列,需要在请求时做换算。其次,每个商品项的结构都包在.gl-item这个CSS类下面,所以直接select这个类就能遍历到全部商品。

采集频率控制是最关键的一环。我在每页之间加了一个随机延时,范围在1.5秒到3.5秒之间,这样做的目的是让请求间隔没有规律,降低被识别为爬虫的概率。实测下来,连续采集20页左右比较安全,如果一次性拉太猛,很容易触发滑块验证。

2.3 反爬应对与合规边界

京东的反爬策略在各大平台里属于中等偏上的水平,新手最容易踩的坑是请求头不完整。只带UA不带Referer,或者UA是Python默认的python-requests/2.31.0,基本上一眼就会被识别。

遇到滑块验证时,我个人的处理原则是“先冷却,再扩容”。冷却指的是停止当前采集任务,等待10到15分钟再进行,同时降低后续采集频率。扩容指的是增加IP代理池,但我认为个人学习项目完全没必要上付费代理,控制频率、尊重目标网站就是最合理的方案。

这里必须提醒一点,任何采集行为都要在合规边界内进行。个人学习用途的爬虫数据量控制在合理范围内,不要并发抢数据,不要恶意请求,不要将采集结果用于商业目的。做数据分析系统的价值在于掌握分析方法和流程,而不是破坏平台秩序。

3. 数据清洗与预处理:垃圾进,垃圾出

3.1 数据字段设计

采集下来的原始字段只有5个:商品ID、名称、价格、评价数、店铺。要支撑后续分析,字段数量明显不够,需要做二次加工。

我在清洗层增加了一个字段派生步骤。原始名称字段是一长串文本,比如“Apple iPhone 15 (A3092) 128GB 蓝色 支持移动联通电信5G”,这里面包含了品牌、系列、存储版本、颜色、网络制式多个维度的信息。通过关键词匹配的方式,我提取了品牌和存储容量这两个最核心的派生字段。

import re import pandas as pd def clean_price(price_str): match = re.search(r'[\d.]+', str(price_str)) return float(match.group()) if match else None def parse_brand(name): brand_list = ['Apple', '华为', '小米', 'OPPO', 'vivo', '荣耀', '三星', '一加', '魅族', 'realme'] for brand in brand_list: if brand.lower() in str(name).lower(): return brand return '其他' def parse_storage(name): match = re.search(r'(\d+)GB', str(name)) return (int(match.group(1)) + 'GB') if match else None df = pd.read_csv('jd_phone_raw.csv') df['价格数值'] = df['价格'].apply(clean_price) df['品牌'] = df['名称'].apply(parse_brand) df['存储'] = df['名称'].apply(parse_storage) df = df.dropna(subset=['价格数值'])

3.2 清洗规则的取舍逻辑

清洗环节最重要的任务有两个:去掉无效数据,纠正异常数据。

评价数这个字段有坑。有的商品评价数是“1.2万+”这种带量的表达,有的直接是“0条”,还有极少数会是“暂无评价”。统一转换成数字时,要单独写一个解析函数处理“万”这个单位。另一个典型问题是价格字段偶尔会有区间价格,比如“2999.00”和“4199.00”并存,原因是一个SKU下有多个版本,列表页显示的是最低价。我当时的处理策略是取区间下限作为分析用价格,因为大多数人关注的其实是起售价格。

数据清洗这层看似技术含量低,实际上影响整个分析结论的可靠性。我自己真实的体会是,这几行清洗代码花了半天时间调试,而写分析代码只用了不到半小时。建议做数据分析的朋友把清洗规则当成独立模块来维护,随着采样的数据量增大,很多没预见到的脏数据会出现,集中管理清洗逻辑能节省大量返工时间。

4. 销售数据分析核心逻辑

4.1 价格分层与销量分布

价格是手机销售最敏感的因素,所以我第一件事就是构建价格区间分布。不需要复杂的数学公式,直接对价格做分箱统计,就能直接反映一个平台商品供给结构是否健康。

分箱的操作我用的是pandas的cut方法,价格区间按照主流价位带划分:1000元以下、1000-2000元、2000-3000元、3000-5000元、5000元以上五档。

关于“销量”,列表页没有直接暴露销量数字,只有评价数。所以这里需要用评价数作为销量代理变量,这个替换逻辑需要在结论里明说,不能直接把评价数当销量来汇报。评价数越高,通常代表历史销量越大,这是一个行业里常用的近似处理。

import pandas as pd import numpy as np df['价格区间'] = pd.cut(df['价格数值'], bins=[0, 1000, 2000, 3000, 5000, float('inf')], labels=['千元以下', '1000-2000', '2000-3000', '3000-5000', '5000以上']) price_dist = df.groupby('价格区间', observed=False).size().reset_index(name='商品数量') price_dist['占比'] = price_dist['商品数量'] / price_dist['商品数量'].sum() * 100 print(price_dist)

跑完这个分组统计,能很直观看出京东手机商品供给集中在哪个价位带。我这次的数据里,2000-3000元区间的商品数量占比最高,符合目前国内手机市场走量机型扎堆的真实状态。这种结论不需要复杂的模型,一张分组表就能带来有效洞察。

4.2 品牌竞争格局与集中度分析

品牌分析是手机类目最有看点的地方。分析的维度有两个:品牌在售商品数和品牌平均价格。

在售商品数反映品牌的SKU丰富度,平均价格反映品牌主打价位带。把这两个指标放在一起看,能快速画出一张品牌竞争位势图。

brand_stats = df.groupby('品牌').agg( 商品数=('商品ID', 'count'), 平均价格=('价格数值', 'mean'), 总评价数=('评价数数值', 'sum') ).sort_values('总评价数', ascending=False) brand_stats['价格位置'] = np.where(brand_stats['平均价格'] > 4000, '高端线', np.where(brand_stats['平均价格'] > 2000, '中高端线', '性价比线')) print(brand_stats.head(10))

为了衡量品牌集中度,我还计算了CR4,也就是排名前四品牌的评价总数占总评价数的比例。CR4超过70%的数据表现,说明这个市场的头部效应非常明显,留给小品牌的份额已经很小。这个指标对于理解市场竞争结构非常直观。

我自己在做这个分析的时候,最大的收获不是得出哪个品牌强,而是理解了“平均价格”这个指标会被低价机型带偏。用中位数来代替平均数会更稳健,尤其在手机这种一个品牌横跨从千元机到万元机的场景里,平均值容易被极端值拉高,中位数更能代表品牌的主流价格定位。所以如果数据量允许,建议同时看均值和分位数。

4.3 评价数据的情感与口碑洞察

光看商品数量和价格还不够,评价数据里藏着更有价值的信息。通过评价数和好评率,能构建一个简单的口碑矩阵。

好评率高、评价数多的商品是明星机型;好评率低、评价数多的商品是问题机型,消费者避雷主要看这类;好评率中等、评价数少的商品则是边缘型号,关注度有限。

df['好评率数值'] = df['商品好评率'].str.replace('%', '').astype(float) / 100 hot_models = df[df['评价数数值'] > df['评价数数值'].quantile(0.9)] problem_models = hot_models[hot_models['好评率数值'] < 0.95].sort_values('好评率数值') print("重点追踪的问题机型:") print(problem_models[['名称', '价格数值', '评价数数值', '好评率数值']].head(20))

这个分析维度的价值在于,它把“卖得多”和“口碑差”这两个矛盾特征放在一起暴露出来,是普通单品观察看不到的视角。整个分析系统的核心意义就在于此:把零散的商品信息汇总成结构性判断,把直觉用数据验证。

5. 可视化展示与分析报告生成

5.1 可视化选型:为什么我用Pyecharts

Python做可视化的常见选项有Matplotlib、Seaborn、Plotly和Pyecharts。Matplotlib是经典工具,适合出版物级别的静态图表,但是交互性太弱;Plotly交互性强,但是配置繁琐;Pyecharts是我在这个项目里的主力,因为它生成的图表是HTML格式,可以直接在浏览器打开,页面自带缩放、悬停提示等交互功能,而且中文显示和主题配色都比较符合日常业务汇报场景。

用Pyecharts生成了三个核心图表:价格区间的饼图、品牌评价数对比的横向柱状图、品牌价格分布的箱线图。

from pyecharts import options as opts from pyecharts.charts import Pie, Bar, Boxplot # 价格区间饼图 pie = ( Pie() .add("", [list(z) for z in zip(price_dist['价格区间'].astype(str), price_dist['商品数量'])]) .set_global_opts(title_opts=opts.TitleOpts(title="京东手机价格区间分布")) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {d}%")) ) pie.render('price_pie.html')

柱状图那里需要注意一个细节:品牌名称有的很长,横向柱状图比纵向更适合展示长名称,避免文字重叠。箱线图则用于展示品牌价格中位数和离散程度,我这里是先手动分组再传给组件生成。

Pyecharts版本之间有较大的API差异,从0.x升到1.x之后接口风格变成了链式调用。网上不少教程还在用旧版写法,实际跑起来会报一堆错。建议直接装最新稳定版,pip install pyecharts装完之后用pyecharts.__version__确认一下版本,再决定代码写法。

5.2 一键生成报告的思路

图表是零散的,报告才是系统的输出形式。我写了一个简单的HTML模板,将统计表和图表嵌入到一个页面中,按“市场规模概览 → 价格分布 → 品牌格局 → 口碑追踪 → 结论建议”的顺序排布,生成一个完整的数据分析报告文件。

这个做法的好处是项目成果可交付、可分享,领导者或者客户不需要跑代码,打开HTML就能浏览分析结论。所有图表和表格都以静态页面形式嵌入,不用起服务,不用装环境,Windows自带浏览器直接打开即可。

6. 常见问题与排查技巧实录

6.1 列表页结构与参数变动

京东的搜索列表页改版不频繁,但偶尔会遇到商品结构变动,导致之前写好的CSS选择器失效。有一次跑采集,发现.p-price i这个选择器选不到任何元素,页面上所有商品都解析失败。排查过程比较曲折,最终确认是页面改版后价格元素的位置变了,价格被放到了.p-price下的另外一个子节点。

这类问题没有一劳永逸的解法,只能靠编写选择器时增加健壮性。我通常是在解析之前先对整页做个打印检查,确认节点结构,再写选择器。如果真的遇到变化,用XPath写成相对路径比CSS选择器容错性更高。

6.2 CSV文件中文乱码的根源与处理

采集的数据里有大量中文,如果直接用to_csv保存,Excel打开会显示乱码,因为Python默认编码是UTF-8,而Windows的Excel默认用GBK读取CSV文件。解决办法是我在代码里已经体现的:保存时指定encoding='utf-8-sig',这个编码会在文件开头加一个BOM头,Excel就能正确识别UTF-8了。

另外,读取CSV文件时同样要明确编码参数。我见过很多同学写出pd.read_csv('data.csv')然后报错的情况,多数都是因为没加encoding参数,文件编码和系统默认编码不一致导致的。

6.3 指标计算偏差问题

分析阶段最容易犯的错误,是拿原始数据直接用,不做口径统一。比如有的价格字段是含“¥”符号的字符串,有的评价数字段是“2.3万”,这些不经过清洗直接汇总,最后的统计结果一定是错的。

我在项目里特别设置了“数据质量检查”步骤,每次分析前先执行一个简单的df.info()和df.describe(),确认数据量、缺失值情况、数值类型无误后再进入核心计算。这套校验流程看起来机械,实际能拦截绝大部分低级错误。

6.4 内存与性能优化

单个数据集只有几千条记录,性能压力通常不大。但一旦把评价数文本字段保留在DataFrame里,再反复做字符串匹配,速度就会明显下降。我的优化方法是:在清洗阶段就把用不到的原始长文本字段直接删掉,只保留派生后的数值字段。这样后面所有分组、聚合操作都走纯数值计算,速度会快很多。

还有一个小技巧是给groupby加上observed=False参数,避免分箱产生的空类别参与计算,这在统计占比时会减少很多干扰。

7. 项目复盘与个人经验

这套系统从零到一跑通,前后大概花了两天时间。写代码本身并不难,难的是数据采集阶段的耐心和数据分析阶段的思考深度。

我自己的一个体会是,数据分析项目的价值不在于代码量多庞大,而在于每个环节能不能给出一个可信的结论。爬虫拉了数据、清洗逻辑处理了脏数据、分析模块输出了分组统计、可视化呈现了结论,这四个环节环环相扣,任何一步偷懒都会让最终结果失去说服力。

给后续想复现这个项目的人一个建议:不要一上来就追求大而全,不要试图把所有品牌、所有价位、所有评价都抓下来。先把20页数据跑通、画出3-5张核心图表、得出几个能说清楚的结论,这套流程的价值就已经实现了。数据量可以在后续逐步扩大,业务流程先稳定才是最重要的。

如果你打算往这个方向继续深入,可以尝试把这个系统扩展成定时采集任务,用Python的schedule库每天固定时间抓一次数据,再把历史数据存进SQLite,这样就能做价格走势分析和涨跌监控。那种“几天后价格降了多少”的分析,就是在这个基础上延伸出来的。

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

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

立即咨询