尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用Python爬虫采集京东商品数据:竞品分析实战指南

用Python爬虫采集京东商品数据:竞品分析实战指南 做竞品分析最烦的就是数据。去年一个做电商运营的朋友找我说想调研某品类在京东上的竞争格局人工去翻页面、记价格、数评价数光几十个SKU就得折腾一两个星期等统计完市场又变了。我当时直接用Python爬虫把京东公开的商品标题、价格、评价数和评论文本批量抓了下来再配合pandas做清洗、可视化出报告整个过程大半天搞定。这篇文章就把这套完整方案写出来从列表页采集到详情页补全再到评论区的数据挖掘全部是可复现的实操流程。适合有Python基础、想用爬虫做真实业务分析的同学参考也适合想系统了解京东页面采集思路的入门者。1. 开搞之前想清楚京东数据能做什么分析也把合规边界划明白1.1 京东商品信息的分析价值拆解先说为什么要盯京东。京东平台的商品数据结构化程度很高商品标题、价格、店铺名称、评价数、好评率这些字段基本都直接渲染在页面上非常便于采集和对比。和淘宝那种千人千面的个性化推荐页面相比京东同一关键词下的搜索结果排序相对稳定干扰因素少做横向竞品分析时数据可比性更强。具体到一个品类下能做的分析维度主要有这几块价格分布把竞品价格全部拉下来看价格带集中在哪个区间找出被市场接受的定价锚点。评价数量评价数直接反映销量热度虽然不等于销量但作为横向对比指标足够说明问题。好评率京东各商品页会显示好评度百分比这是用户口碑的直接量化体现。评论内容这是最有价值的部分。用户会在评论里提到续航不行做工好物流快等细节这些是天然的产品功能反馈池。店铺归属看竞品是自营还是第三方店铺不同店铺类型背后对应的货源和售后策略不同。把这些字段整合起来基本就能回答这个品类里谁在领跑、谁在掉队、用户到底在意什么这三个竞品分析中的核心问题。1.2 采集边界与合规原则公开数据不等于可以乱采动工之前必须先把规矩说清楚。京东页面上的商品信息属于公开数据个人基于学习研究目的做适量采集这类操作本身是行业里比较常见的做法。但这里有几个边界必须遵守只采集商品详情、价格、评论等公开信息不碰用户的个人隐私数据比如订单信息、收货地址、账号信息。不绕过登录验证机制不使用账号Cookie去获取非公开数据。必须控制请求频率不能对目标服务器造成压力。业内比较稳妥的做法是单次请求间隔2到5秒单账号单IP的采集量控制在合理范围内。遵守robots.txt协议精神京东明确禁止的路径不去踩。这篇文章里所有代码都基于模拟普通用户浏览行为这个前提来写。我不会展示任何破解验证码、绕过风控或伪造身份的内容。爬虫只是数据获取工具真正的功夫在于数据清洗和业务分析。1.3 开发环境与依赖安装我用的是Python 3.9版本推荐3.8以上。核心依赖如下库名用途安装命令requests发起HTTP请求pip install requestsbeautifulsoup4解析HTML页面pip install beautifulsoup4lxml解析器速度比html.parser快pip install lxmlpandas数据清洗与分析pip install pandasmatplotlib图表绘制pip install matplotlibjieba中文分词用于评论分析pip install jiebawordcloud生成词云图pip install wordcloud如果你打算把采集结果存到数据库建议装一个SQLitePython标准库自带不用额外安装。后续所有代码我都会给出可以直接复制的片段但请根据自己的需求改字段和参数不要原封不动跑完就完事。提示写爬虫之前先想清楚数据分析要什么字段这会直接影响解析代码怎么写。很多人是爬到一半发现字段不够又回头改白白浪费时间。2. 商品列表页采集从关键词搜索到结构化商品字段2.1 先看一次搜索请求摸清页面返回规律我拿蓝牙耳机这个关键词举例。浏览器打开京东搜索页地址栏里的URL长这样https://search.jd.com/Search?keyword蓝牙耳机encutf-8wq蓝牙耳机这里有个关键点京东搜索列表页的商品数据有两种来源一部分直接渲染在HTML里另一部分是通过异步接口加载的。用requests直接请求这个URL拿回来的HTML里已经包含了前几页的商品标题、店铺和评价数。价格字段比较特殊后面单独讲。实操时我习惯先把首页HTML保存到本地再用文本编辑器打开看数据在哪个标签结构里。这一步很重要因为京东页面改版频率不低我之前写的选择器隔了三个月就失效了重新分析一次页面结构是常态。2.2 构造请求头与搜索参数避开第一波拦截直接裸请求基本都会触发风控第一个要处理的就是请求头。一个最基础但有效的请求头配置import requests headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, Referer: https://www.jd.com/ } def get_page(keyword, page): url https://search.jd.com/Search params { keyword: keyword, enc: utf-8, wq: keyword, page: page } resp requests.get(url, paramsparams, headersheaders, timeout10) resp.encoding utf-8 return resp.text几个容易被忽略的细节User-Agent必须模拟真实浏览器的完整UA字符串不要用默认的python-requests。Referer填京东首页这符合正常浏览路径。timeout一定要设置避免某个请求卡死导致整个采集流程挂掉。编码统一用utf-8页面返回的中文才不会乱码。注意京东搜索页的翻页参数page有个特点第1页对应page1第2页对应page3页码按奇数递增。这个规律是京东页面的老传统但改版后不一定完全一样建议先手动访问第2页看URL参数再确认。2.3 解析列表页的商品字段拿到HTML之后用BeautifulSoup提取商品信息。我的解析思路是先定位每个商品的外层容器再从容器里逐字段抽取from bs4 import BeautifulSoup def parse_list_html(html): soup BeautifulSoup(html, lxml) items [] # 每个商品的容器是 li.gl-item for li in soup.select(li.gl-item): try: sku_id li.get(data-sku) # 标题 title_tag li.select_one(.p-name em) title title_tag.get_text(stripTrue) if title_tag else # 店铺 shop_tag li.select_one(.p-shop a) shop_name shop_tag.get_text(stripTrue) if shop_tag else # 评价数 comment_tag li.select_one(.p-commit a) comment_count comment_tag.get_text(stripTrue) if comment_tag else 0 # 详情页链接 link_tag li.select_one(.p-name a) link https: link_tag.get(href) if link_tag else items.append({ sku_id: sku_id, title: title, shop_name: shop_name, comment_count: comment_count, link: link }) except Exception as e: # 单个商品解析失败不影响整体 continue return items这里我要重点说下选择器的稳定性问题。.p-name em这类class选择器依赖京东前端的CSS类名页面改版后类名很容易变。我在实际项目中总结出一套更抗改版的策略优先用>import time import random def crawl_list(keyword, max_page10): all_items [] for page in range(1, max_page * 2, 2): html get_page(keyword, page) items parse_list_html(html) if not items: # 连续两页无数据就停止 break all_items.extend(items) time.sleep(random.uniform(2, 5)) return all_items第三异常请求要记录。我习惯把每个keyword、page、请求状态码、异常信息写进日志文件方便跑完复盘。这一步看简单排查问题时能省好几小时。3. 商品详情与价格数据处理动态加载的实战经验3.1 详情页URL的拼接规则列表页解析出来的link字段通常是//item.jd.com/100012345.html这种格式补上https:前缀就能访问。详情页URL的规律非常固定只要拿到sku_id就能直接拼出详情页地址def build_detail_url(sku_id): return fhttps://item.jd.com/{sku_id}.html这意味着列表页解析如果拿不到完整链接用sku_id补全路径也一样。实际项目里我基本是两条腿走路列表页先拿sku_id和基础字段详情页再根据sku_id补全价格和好评率。3.2 价格数据的动态加载与JSON接口京东详情页的价格并不直接渲染在HTML里而是通过异步接口加载的。这也是很多初学者卡壳的地方用requests拿到详情页HTML搜索价格两个字发现页面里只有占位符没有实际价格数字。京东价格接口是独立的JSON接口有比较固定的请求格式def get_price(sku_ids): sku_ids: 逗号分隔的多个sku_id例如 100012345,100067890 url https://p.3.cn/prices/mgets params {skuIds: fJ_{sku_ids}} headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://item.jd.com/ } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json() prices {} for item in data: sku item.get(id, ).replace(J_, ) prices[sku] item.get(p) return prices这个接口返回的JSON结构里关键字段是p表示售价还有m表示原价以及op表示历史最低参考价等。实测下来这个接口在低频请求下比较稳定。提示价格接口虽然好用但不要在短时间内高频调用。我测试的时候试过一秒请求一次连续几十次后就会出现返回空数据的情况。加延时、控制调用量是保证采集稳定的基础。3.3 好评率与店铺信息的补充采集好评率数据在详情页HTML里可以直接提取。打开详情页源码搜索好评率附近的内容通常能看到类似span classpercent-con95%/span的结构。用BeautifulSoup解析def parse_detail_html(html): soup BeautifulSoup(html, lxml) result {} # 好评率 percent_tag soup.select_one(.percent-con) if percent_tag: result[good_rate] percent_tag.get_text(stripTrue) # 店铺名称商品页内的店铺信息模块 shop_tag soup.select_one(.shop-name a) if shop_tag: result[shop_name] shop_tag.get_text(stripTrue) return result详情页里还有商品规格参数、品牌、分类等更细的字段。如果你的竞品分析需要这些维度可以在同一个详情页请求中一并解析减少额外的请求量。3.4 数据清洗与存储设计采集到的数据必须规整后才能用于分析。我通常的处理流程是价格字段转数值接口返回的价格可能是字符串需要转成float类型。评价数清理京东评价数经常是10万这种格式要统一转成数字。去重同一个sku可能在多页列表里重复出现采集结束后按sku_id去重。缺失字段处理部分商品可能缺店铺名或好评率先保留空值分析时再决定怎么处理。存储方面我用SQLite做中间存储import sqlite3 conn sqlite3.connect(jd_data.db) conn.execute( CREATE TABLE IF NOT EXISTS products ( sku_id TEXT PRIMARY KEY, title TEXT, shop_name TEXT, price REAL, good_rate TEXT, comment_count TEXT, link TEXT, keyword TEXT, crawled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) )用SQLite的好处是采集过程中断了数据也不会丢而且后续用pandas读取非常方便import pandas as pd df pd.read_sql_query(SELECT * FROM products, conn)这一步做完商品维度的分析数据集就齐了。4. 评论数据采集从JSON接口中挖出竞品口碑4.1 定位评论数据接口商品评论和价格一样也是异步加载的。有个京东评论区通用的JSON接口格式如下https://club.jd.com/comment/productPageComments.action?productId{sku_id}score0sortType5page0pageSize10isShadowSku0fold1参数含义productId商品sku_idscore评分筛选0表示全部1表示差评2表示中评3表示好评5表示推荐sortType排序方式5表示按时间排序page页码从0开始pageSize每页数量京东限制最大10用requests请求这个接口返回的是JSON里面包含三个关键部分评论列表、商品评分摘要、评论标签聚合。4.2 直接解析评论JSON这个接口返回的JSON结构比较清晰可以直接解析def fetch_comments(sku_id, page0): url https://club.jd.com/comment/productPageComments.action params { productId: sku_id, score: 0, sortType: 5, page: page, pageSize: 10, isShadowSku: 0, fold: 1 } headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: fhttps://item.jd.com/{sku_id}.html } resp requests.get(url, paramsparams, headersheaders, timeout10) data resp.json() comments data.get(comments, []) result [] for c in comments: result.append({ sku_id: sku_id, nickname: c.get(nickname, ), content: c.get(content, ), score: c.get(score), product_color: c.get(productColor, ), product_size: c.get(productSize, ), comment_time: c.get(creationTime, ), reply_count: c.get(replyCount, 0), useful_count: c.get(usefulCount, 0) }) return result这里的productColor和productSize字段很有意思可以分析用户购买的商品颜色和规格偏好在SKU级别的竞品分析里很有用。4.3 评论翻页与去重策略评论接口的翻页从page0开始每页最多10条。理论上要抓全一个爆款商品的几千条评论需要请求几百次这个量级在个人学习中不太推荐。我更常用的策略是每个商品采集最近50到100条评论足以捕捉主要口碑趋势。按时间排序优先采集最新评论反映最近的产品质量和用户反馈。采集时把comment_id字段存下来做去重标识。写循环的时候要加两个控制逻辑def crawl_comments(sku_id, max_pages10): all_comments [] seen_ids set() for page in range(max_pages): comments fetch_comments(sku_id, page) if not comments: break for c in comments: c_id c.get(id) if c_id and c_id not in seen_ids: seen_ids.add(c_id) all_comments.append(c) time.sleep(random.uniform(2, 4)) return all_comments注意评论接口对请求频率更敏感我实测过连续快速请求十几页就会触发限制。每个商品间隔拉长到3秒以上整体稳定性会好很多。4.4 基于简单规则的情感倾向初判抓到的评论文本是纯文本如果没做过自然语言处理直接看几百条文本也能判断大致口碑但不够量化。这里介绍一个不用训练模型就能用的方法情感词词典打分法。先准备一个简单的情感词表正向词比如好用、舒服、满意、值得、速度快、清晰、漂亮负向词比如差劲、退货、失望、卡顿、漏发、异味、差评。然后对每条评论做分词统计正向词和负向词出现次数简单打分import jieba positive_words set([好用, 舒服, 满意, 值得, 速度快, 清晰, 漂亮, 稳定, 喜欢, 推荐]) negative_words set([差劲, 退货, 失望, 卡顿, 漏发, 异味, 差评, 坏了, 后悔, 不好]) def sentiment_score(text): words jieba.lcut(text) pos sum(1 for w in words if w in positive_words) neg sum(1 for w in words if w in negative_words) return pos - neg这个方法精度有限但胜在可解释性强、运行快。在竞品分析场景里它已经足够帮你找出口碑明显偏负面的竞品和被用户持续好评的竞品。如果你有足够的评论量和时间可以考虑用更成熟的预训练模型做情感分类精度会更高。但作为日常竞品分析词典法加人工抽验已经能覆盖90%的场景。4.5 把评论变成竞品功能口碑对比评论数据最有价值的用法是把评论文本拆成用户关注点。我常用的方法是先对评论文本做词频统计再用词云可视化快速看出用户关注哪些功能点。from wordcloud import WordCloud import matplotlib.pyplot as plt def generate_wordcloud(comments_text, output_path): wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, # 中文字体路径按自己系统调整 width800, height600, background_colorwhite, max_words100 ) wc.generate( .join(jieba.lcut(comments_text))) wc.to_file(output_path)把A、B、C三个竞品的评论词云放在一起对比差异非常直观。比如A商品词云里续航轻便出现频率高B商品词云里信号售后出现频率高一次对比就能看出两个产品在用户心智中的差异点。我拿这个思路做过一次真实分析发现某个小品牌虽然没有大牌知名度但评论里做工好客服耐心出现频率极高这就是差异化定位的机会点。5. 竞品分析的落地输出从数据库到可视化报告5.1 商品价格带分布与核心SKU对比数据采集结束后先用pandas做基础统计。价格带分布用直方图最直观import pandas as pd import matplotlib.pyplot as plt # 读取数据 df pd.read_sql_query(SELECT * FROM products, conn) # 清洗价格字段 df[price_num] pd.to_numeric(df[price], errorscoerce) # 按价格排序 df_sorted df.dropna(subset[price_num]).sort_values(price_num) plt.figure(figsize(10, 6)) plt.hist(df_sorted[price_num], bins20, edgecolorblack) plt.xlabel(价格区间元) plt.ylabel(商品数量) plt.title(竞品价格带分布) plt.savefig(价格带分布.png, dpi150)这张图能直接回答这个品类主战场在哪个价位段。如果是百元以内的快消品价格带集中在低价区说明用户对价格敏感如果价格带分布分散说明市场还没有出现统治级的价格锚点。核心SKU对比表我通常做成这样商品标题店铺价格元评价数好评率评论情感得分商品AXX旗舰店29910万97%25商品BXX专卖店1592.3万91%-3商品CXX自营4995.8万95%11这张表是竞品分析报告的核心所有结论都围绕它展开。5.2 价格-评价数散点图寻找性价比区间价格和评价数的组合分析特别有意思。我常用散点图把竞品画在二维坐标里横轴是价格纵轴是评价数。散点图右上角代表卖得贵但热度高的商品左下角是既便宜又冷门的商品。真正值得关注的是中间区域评价数高但价格低于同类均值的商品这往往是性价比之王也是潜在威胁最大的竞品。plt.figure(figsize(10, 6)) plt.scatter(df_sorted[price_num], df_sorted[comment_count_num], alpha0.6) plt.xlabel(价格元) plt.ylabel(评价数) plt.title(价格与评价数分布) plt.savefig(价格评价散点图.png, dpi150)做这步分析的时候评价数的单位要统一。京东显示10万这种格式需要先转成100000不然画图时会因为字符串类型报错。5.3 定时采集与增量更新竞品分析不是一次性工作市场变化很快需要定期更新数据。我把上面的采集逻辑封装成脚本后用系统的定时任务来驱动Windows下用任务计划程序Linux下用crontab。设计上要注意增量和全量的选择。我的习惯是商品基础信息标题、店铺全量更新因为SKU变化不大。价格和评价数每次重新抓取这两个字段变化快。评论数据只增量抓取新评论判断依据是评论时间是否已存在于数据库。增量更新的核心代码如下# 获取数据库中已有的评论时间 existing_times set(pd.read_sql_query(SELECT comment_time FROM comments, conn)[comment_time]) new_comments [c for c in crawled_comments if c[comment_time] not in existing_times]加上定时后这套系统就跑成了一个小型数据监测工具每周自动更新一次竞品数据报告也随之刷新。5.4 采集过程中的常见问题与我的应对方式跑了几个月几个高频问题值得列出来问题一列表页商品数量变少。大概率是触发了频率限制。我的处理方式是立即停止采集休息5到10分钟再继续同时降低单次任务的采集页数上限。问题二评论接口返回空数据。可能原因有两个一是请求频率过高二是该商品确实没有评论。判断方法是手动在浏览器里访问评论接口URL看返回内容。浏览器能打开、脚本请求为空就是频率问题。问题三价格接口返回的JSON字段缺失。上游更新导致字段名变化需要在解析代码里加个容错用get()方法配合默认值而不是直接下标取值。问题四中文字体乱码。绘制图表和词云时如果系统没装中文字体保存图片会出现方框。解决方案是下载一个中文字体文件如simhei.ttf在代码里指定字体路径。这些坑我全踩过现在新写的采集脚本都会把这些容错逻辑默认带上。写爬虫的人得有一个意识页面结构和接口永远在变代码必须写得足够健壮异常要能自动跳过日志要能定位问题数据要能恢复重跑。6. 从这套方案延伸出去的几个想法单纯把数据抓下来只是第一步真正考验功力的是怎么把数据变成业务建议。我举个例子。之前分析某个品牌的竞品时发现某款竞品的好评率只有89%明显低于同品类平均水平。我去翻它的评论高频词汇集中在发热掉电快售后慢。这几个关键词直接指出了竞品的软肋。后来这个结论被我朋友用在了产品宣传策略里主打低温运行和续航持久这两个差异点效果相当明显。类似的分析思路还可以继续延伸评论的时间序列分析按月统计竞品评论量的变化可以推测竞品的推广节奏。价格波动跟踪记录竞品价格的历史变化判断竞品的促销周期和调价策略。评论标签聚合京东每条评论都附带用户选择的标签如性价比高功能强大外观漂亮直接统计标签占比比纯文本分析更快出结论。如果你打算把这套方案做得更工程化可以引入Scrapy框架管理请求调度或者用Playwright做浏览器渲染来应对复杂页面。但个人做竞品分析的话requests加BeautifulSoup这套组合轻量、可控、容易排查问题已经能覆盖绝大部分需求没必要一开始就上重型框架。最后再分享一个实用技巧采集完成后把所有数据导出成CSV或Excel文件用Excel的数据透视表也能做一部分分析不一定每个问题都要写代码。把技术工具和业务分析结合好才是这套方案真正的价值所在。
返回列表