Python实战:京东商品SKU数据采集与处理全流程解析
1. 项目概述:从零开始,用Python搞定京东商品SKU数据
最近在帮一个做电商的朋友分析市场,他上来就问:“能不能把京东上某个品类的所有商品信息,包括不同颜色、尺寸对应的价格和库存都弄下来?” 我一听就明白了,这不就是典型的商品SKU数据采集需求嘛。对于刚接触Python数据处理的朋友来说,这其实是一个绝佳的练手项目。它串联了网络请求、数据解析、数据清洗和结构化存储这几个核心环节,而且结果非常直观——你能立刻看到一堆原始网页变成一张规整的Excel表格。
所谓SKU,你可以把它理解为一个商品的最小库存单位。比如同一款手机,256G黑色和512G白色就是两个不同的SKU,它们有独立的货号、价格和库存状态。在京东这样复杂的电商页面上,采集完整的SKU数据,意味着你不仅要拿到商品标题、主图这些基础信息,更要精准地提取出每一个规格组合(如“颜色:星际黑,内存:12GB+256GB”)对应的实时价格、促销信息、是否有货等动态参数。这比单纯抓取一个静态页面要更有挑战,也更有价值。
这个教程,就是带你一步步实现这个目标。即使你是零基础,只要跟着操作,也能在半天内跑通整个流程。我们会用到 requests 库去模拟浏览器访问,用 parsel 或 BeautifulSoup 来解析网页里藏着的那些数据,最后用 pandas 把杂乱的信息整理得清清楚楚。过程中你会遇到反爬虫、数据动态加载、JSON结构嵌套这些“坑”,别担心,我会把每个坑怎么绕过去都讲明白。最终目的不仅是拿到数据,更是让你理解一套通用的、能复用到其他网站的数据采集与处理思路。
2. 核心思路与工具选型:为什么是这套组合拳?
面对一个电商数据采集任务,新手最容易犯的错就是拿起锤子看什么都像钉子,直接莽上去写代码。我们先花点时间,把任务拆解清楚,并给每个环节选择合适的“工具”。
2.1 任务拆解:一个SKU数据的诞生之旅
一次完整的SKU数据采集,可以分解为以下四个核心步骤,它们环环相扣:
- 网络请求(敲门) :让我们的Python程序能够像浏览器一样,向京东的服务器索要目标商品页面的HTML代码。这是所有数据之源。
- 数据解析(寻宝) :服务器返回的HTML是一大坨混合了内容、样式和脚本的代码。我们需要从中精准地定位并提取出我们关心的SKU信息,比如商品ID、规格列表、价格数据等。
- 数据清洗与结构化(整理) :提取出来的数据往往是杂乱无章的,比如价格可能带着“¥”符号,库存状态是“有货”或“无货”这样的中文,多个规格选项混杂在一起。我们需要把它们清洗、分割,转换成程序容易处理的结构(比如字典、列表),最终形成行(每个SKU)列(每个属性)分明的表格。
- 数据存储(入库) :将整理好的结构化数据永久保存下来,通常选择CSV或Excel文件,方便后续用Excel、Python或BI工具进行分析。
2.2 工具选型:轻量、高效、易上手
基于以上步骤,我选择了下面这套工具链。它可能不是唯一解,但绝对是兼顾学习成本、开发效率和稳定性的“黄金组合”。
-
请求库:
requests- 为什么选它?
requests库是Python HTTP客户端库的绝对标杆,其API设计极其人性化,几行代码就能完成复杂的网络请求。对于京东这类主要依靠HTTP协议传输数据的网站,它完全够用。相比于更底层的urllib,它代码更简洁;相比于异步框架aiohttp,它学习曲线更平缓,更适合入门。 - 核心用途 :发送GET/POST请求,添加请求头(Headers)模拟真实浏览器,处理Cookies,设置超时和重试。
- 为什么选它?
-
解析库:
parsel或BeautifulSoup+lxml-
parsel:这是Scrapy框架内置的解析库,但它可以独立使用。它同时支持CSS选择器和XPath两种定位方式,非常灵活。特别是对于京东这种页面结构复杂、数据可能嵌套在<script>标签的JSON字符串里的情况,parsel的.re()(正则表达式)方法往往能派上大用场。 本教程将主要使用parsel。 -
BeautifulSoup:外号“美味汤”,是最著名的HTML/XML解析库,语法直观,容错性好。如果你对CSS选择器更熟悉,用起来会很顺手。通常配合lxml解析引擎以获得更快的速度。 - 如何选择? 两者都能胜任。
parsel在应对动态数据嵌入时稍显方便,BeautifulSoup的文档和社区资源更丰富。你可以根据喜好选择。
-
-
数据处理与存储:
pandas- 为什么选它? 在数据处理领域,
pandas是Python的“瑞士军刀”。它提供的DataFrame数据结构,本质上就是一个功能超级强大的内存电子表格。我们可以轻松地将提取出来的字典列表转换成DataFrame,然后进行数据清洗(去重、填充空值、类型转换)、计算,并一键导出为CSV或Excel。手动拼接字符串写文件的方式在它面前显得原始而低效。
- 为什么选它? 在数据处理领域,
-
环境与辅助
- 开发环境 : VS Code 或 PyCharm 。VS Code轻量灵活,配合Python插件体验很好;PyCharm是专业的Python IDE,调试功能强大。任选其一即可。
- 包管理 :使用
pip安装上述库。建议先创建一个虚拟环境,避免包版本冲突。 - 浏览器开发者工具 :这是最重要的“侦察兵”。我们所有数据定位的工作,都依赖于在浏览器中按F12打开开发者工具,使用“元素(Elements)”面板查看HTML结构,使用“网络(Network)”面板观察数据请求(特别是XHR/Fetch请求),找到真正的数据来源。
注意 :请务必遵守目标网站的
robots.txt协议,并合理设置请求频率(例如在每个请求间添加time.sleep(2)),避免对对方服务器造成压力,这既是道德要求,也能有效防止你的IP被封锁。
3. 实战第一步:环境搭建与页面侦察
理论说再多,不如动手做一遍。让我们从最基础的环境准备开始,并亲自去京东页面上“侦察”一番。
3.1 一步到位的环境配置
打开你的命令行(终端),依次执行以下命令。如果你还没安装Python,请先去Python官网下载3.7及以上版本的安装程序,安装时务必勾选“Add Python to PATH”。
# 1. 升级pip(Python包管理工具)到最新版,确保安装顺利
python -m pip install --upgrade pip
# 2. 一次性安装我们需要的所有核心库
pip install requests parsel pandas
# 可选:如果你更喜欢BeautifulSoup,也可以安装它
# pip install beautifulsoup4 lxml
安装完成后,可以在Python交互环境里快速测试一下:
import requests, parsel, pandas as pd
print(“所有库已就绪!”)
如果没有报错,说明环境配置成功。
3.2 深度侦察:京东SKU数据藏在哪里?
这是最关键的一步,决定了后续代码怎么写。我们以京东上任意一个商品为例(比如搜索“手机”选一个)。
1. 分析页面结构(静态HTML): 打开商品详情页,按F12打开开发者工具,切换到“元素(Elements)”面板。使用左上角的箭头工具,点击页面上的商品标题、价格、颜色选择按钮。你会发现,商品的基本信息(标题、主图、店铺名)通常直接存在于HTML中。但是,当你点击不同颜色或内存时,价格和库存会实时变化,这说明这些数据 不是 直接写在初始HTML里的。
2. 寻找动态数据源(XHR请求): 保持开发者工具打开,切换到“网络(Network)”面板。刷新页面,然后在面板里筛选“XHR”或“Fetch”类型的请求。现在,你去点击商品页面的不同规格(颜色、尺寸)。仔细观察“网络”面板,你会看到有新的请求出现。这些请求就是浏览器在背后向京东服务器请求新规格数据时发出的。
点击其中一个请求,查看它的“响应(Response)”选项卡。你大概率会看到一种结构清晰的数据格式—— JSON 。这就是我们梦寐以求的、结构化的SKU数据!它通常包含了所有规格组合的ID、价格、库存状态、促销信息等。记下这个请求的 URL 和 请求头(Headers) ,特别是里面的 User-Agent 、 Referer 等字段,我们的Python程序需要模拟这些信息才能成功获取数据。
3. 定位数据在HTML中的痕迹: 有时候,所有SKU数据会作为一个大的JSON对象,直接嵌入在初始页面的某个 <script> 标签的JavaScript变量里。你可以在“元素”面板里按 Ctrl+F 搜索 sku 、 colorSize 、 item.jd.com 等关键词,可能会找到类似 var skuInfo = {...} 这样的代码块。这也是一个重要的数据来源。
经过这番侦察,我们明确了主攻方向: 优先从动态的XHR请求中获取纯净的JSON数据,如果不行,再尝试从HTML的 <script> 标签中解析。
4. 核心代码实现:构建你的数据采集器
侦察完毕,开始写代码。我们将按照请求、解析、清洗、存储的流程,构建一个完整的脚本。
4.1 模拟请求,获取原始数据
首先,我们需要伪装成一个正常的浏览器去请求数据。这里以从XHR请求获取数据为例。
import requests
import time
import json
def fetch_sku_data_by_api(product_id):
"""
通过模拟XHR请求获取SKU数据
:param product_id: 京东商品ID (例如,商品链接中的 ‘10012345678’)
:return: 返回JSON格式的响应数据,如果失败返回None
"""
# 1. 构建一个非常重要的请求头,这是反爬虫的第一道关卡
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‘,
’Referer‘: f’https://item.jd.com/{product_id}.html‘, # 告诉服务器你是从哪个页面跳转过来的
’Accept‘: ’application/json, text/javascript, */*; q=0.01‘, # 声明客户端希望接收JSON数据
’Accept-Language‘: ’zh-CN,zh;q=0.9‘,
’Connection‘: ’keep-alive‘,
}
# 2. 构建请求URL。这个URL需要根据我们之前侦察到的实际请求来修改。
# 京东的SKU接口地址可能会变化,以下是一个常见的模式,可能需要调整参数。
api_url = f’https://item.jd.com/component/desc-ajax?productId={product_id}&callback=fetchJSON‘
# 或者可能是另一个地址,如涉及价格库存的接口,需要你在网络面板里具体分析。
# price_api_url = f’https://p.3.cn/prices/mgets?skuIds=J_{product_id}‘
try:
# 3. 发送GET请求,并设置一个合理的超时时间
response = requests.get(api_url, headers=headers, timeout=10)
response.raise_for_status() # 如果状态码不是200,抛出异常
# 4. 处理响应。京东的接口返回的可能是JSONP格式(如`fetchJSON(...)`),需要清理。
raw_text = response.text
# 尝试去除JSONP包装
if raw_text.startswith(‘fetchJSON‘) or raw_text.startswith(‘callback‘):
# 找到第一个左括号和最后一个右括号
json_str = raw_text[raw_text.find(‘(‘)+1:raw_text.rfind(‘)‘)]
data = json.loads(json_str)
else:
# 直接就是JSON
data = response.json()
print(f“成功获取商品 {product_id} 的API数据”)
return data
except requests.exceptions.RequestException as e:
print(f“网络请求失败: {e}”)
return None
except json.JSONDecodeError as e:
print(f“JSON解析失败,原始响应: {response.text[:200]}”)
return None
finally:
# 礼貌地等待一下,避免请求过快
time.sleep(2)
# 测试一下,替换成你侦察到的真实商品ID
# sku_json_data = fetch_sku_data_by_api(‘10012345678’)
4.2 解析HTML,应对无API情况
如果找不到清晰的API,我们就需要从初始的HTML页面中“挖”数据。这里展示用 parsel 从 <script> 标签提取的方法。
import re
from parsel import Selector
def parse_sku_from_html(html_content):
"""
从商品详情页HTML中解析SKU信息
:param html_content: 网页的HTML文本
:return: 解析出的SKU相关数据字典
"""
selector = Selector(text=html_content)
sku_info = {}
# 方法1:尝试寻找页面中内嵌的JSON数据(非常常见)
# 在‘元素’面板里搜索‘sku’,找到类似`var skuInfo = {...};`的脚本
script_texts = selector.xpath(‘//script/text()‘).getall()
for script in script_texts:
if ‘skuId‘ in script or ‘colorSize‘ in script:
# 使用正则表达式匹配JSON对象
# 这个模式尝试匹配 `var xxx = { ... };` 中的花括号内容
json_pattern = re.compile(r‘var\s+\w+\s*=\s*({.*?})\s*;‘, re.DOTALL)
match = json_pattern.search(script)
if match:
try:
json_str = match.group(1)
# 处理可能存在的JavaScript不兼容语法,如末尾逗号
json_str = re.sub(r‘,\s*}‘, ‘}‘, json_str)
json_str = re.sub(r‘,\s*]‘, ‘]‘, json_str)
data = json.loads(json_str)
# 通常我们需要的数据在data的某个深层key里,需要根据实际情况调整
# 例如: sku_info[‘base’] = data.get(‘product’)
print(“从HTML脚本中解析到SKU数据”)
return data
except json.JSONDecodeError as e:
print(f“内嵌JSON解析失败: {e}”)
continue
# 方法2:提取基础信息(如果动态数据获取失败,至少拿到这些)
sku_info[‘title‘] = selector.xpath(‘//div[@class=“sku-name”]/text()‘).get(‘’).strip()
sku_info[‘price‘] = selector.xpath(‘//span[@class=“p-price”]/span[2]/text()‘).get(‘’).strip()
# 注意:这里的XPath需要根据京东页面实际结构调整,以上仅为示例
print(“从HTML中解析到基础信息”)
return sku_info
# 假设我们已经通过requests获取了html_content
# html = requests.get(‘https://item.jd.com/10012345678.html‘, headers=headers).text
# sku_info = parse_sku_from_html(html)
4.3 数据清洗与结构化:从JSON到表格
无论通过哪种方式拿到了原始数据(假设是 raw_data ),它通常是一个嵌套很深的字典/列表结构。我们的任务就是把它“拍平”,变成一行行规整的数据。
import pandas as pd
def clean_and_structure_sku_data(raw_data):
"""
清洗和结构化SKU原始数据
:param raw_data: 从API或HTML解析得到的原始数据
:return: 一个由字典组成的列表,每个字典代表一个SKU
"""
sku_list = []
# 这是一个示例逻辑,实际结构需要根据你抓取到的raw_data进行调整
# 假设 raw_data 的结构是 {‘skus‘: [{‘skuId‘: ‘123‘, ‘price‘: ‘2999‘, ‘spec‘: ‘颜色:黑|内存:128G‘, ‘stock‘: ‘1‘}, ...]}
# 1. 定位到SKU列表
skus = raw_data.get(‘skus‘, [])
if not skus:
# 可能数据结构不同,尝试其他常见key
skus = raw_data.get(‘data‘, {}).get(‘skus‘, [])
for sku in skus:
sku_item = {}
# 2. 提取核心字段
sku_item[‘sku_id‘] = sku.get(‘skuId‘, ‘’)
sku_item[‘price‘] = sku.get(‘price‘, ‘0‘) # 价格可能为字符串
# 库存状态,1通常代表有货,0代表无货
stock_state = sku.get(‘stockState‘, 0)
sku_item[‘stock‘] = ‘有货‘ if stock_state == 1 else ‘无货‘
# 3. 处理规格字符串(如“颜色:黑|内存:128G”)
spec_str = sku.get(‘spec‘, ‘’)
if ‘|‘ in spec_str:
specs = spec_str.split(‘|‘)
for spec in specs:
if ‘:‘ in spec:
key, value = spec.split(‘:‘, 1)
sku_item[f’spec_{key.strip()}‘] = value.strip()
else:
sku_item[‘spec‘] = spec_str
# 4. 可以在这里添加更多字段,如促销信息、重量等
# sku_item[‘promotion‘] = ‘,‘.join(sku.get(‘promotionText‘, []))
sku_list.append(sku_item)
# 5. 如果没有提取到动态SKU列表,则使用基础信息构造一条
if not sku_list:
base_info = {
‘sku_id‘: raw_data.get(‘productId‘, ‘N/A‘),
‘title‘: raw_data.get(‘title‘, ‘N/A‘),
‘price‘: raw_data.get(‘price‘, ‘N/A‘),
‘stock‘: ‘未知‘,
}
sku_list.append(base_info)
return sku_list
# 假设我们通过前面的函数拿到了raw_data
# structured_skus = clean_and_structure_sku_data(raw_data)
4.4 存储与导出:生成最终报告
数据处理完后,用 pandas 可以非常优雅地保存。
def save_to_excel(sku_list, filename=‘jd_sku_data.xlsx‘):
"""
将SKU列表保存为Excel文件
:param sku_list: 清洗后的SKU字典列表
:param filename: 输出文件名
"""
if not sku_list:
print(“没有数据可保存!”)
return
# 将列表转换为DataFrame
df = pd.DataFrame(sku_list)
# 数据清洗增强:处理价格字段,转为数值类型
if ‘price‘ in df.columns:
# 移除‘¥‘、‘,‘等非数字字符
df[‘price‘] = df[‘price‘].astype(str).str.replace(‘¥‘, ‘’).str.replace(‘,‘, ‘’)
# 尝试转换为浮点数,错误则置为NaN
df[‘price‘] = pd.to_numeric(df[‘price‘], errors=‘coerce‘)
# 重命名列,使其更易读
df.rename(columns={
‘sku_id‘: ‘SKU编码‘,
‘price‘: ‘价格(元)‘,
‘stock‘: ‘库存状态‘,
‘title‘: ‘商品标题‘,
}, inplace=True)
# 保存到Excel
try:
df.to_excel(filename, index=False, engine=‘openpyxl‘) # 需要安装openpyxl库
print(f“数据已成功保存到 {filename}, 共 {len(df)} 条记录。”)
# 也可以保存为CSV: df.to_csv(‘jd_sku_data.csv‘, index=False, encoding=‘utf-8-sig‘)
except Exception as e:
print(f“保存文件时出错: {e}”)
# 整合以上所有步骤的主函数
def main(product_id):
print(f“开始采集商品 {product_id} 的SKU数据...”)
# 策略1:优先尝试API接口
raw_data = fetch_sku_data_by_api(product_id)
# 策略2:如果API失败,则退回到HTML解析
if not raw_data:
print(“API获取失败,尝试从HTML解析...”)
headers = {‘User-Agent‘: ’Mozilla/5.0...‘} # 复用之前的headers
html_url = f’https://item.jd.com/{product_id}.html‘
try:
resp = requests.get(html_url, headers=headers, timeout=10)
resp.raise_for_status()
raw_data = parse_sku_from_html(resp.text)
except Exception as e:
print(f“HTML获取与解析也失败: {e}”)
return
# 清洗结构化数据
sku_list = clean_and_structure_sku_data(raw_data)
# 保存数据
if sku_list:
save_to_excel(sku_list, f’jd_sku_{product_id}.xlsx‘)
else:
print(“未能提取到有效的SKU数据。”)
# 运行示例
if __name__ == ‘__main__‘:
# 替换为你想采集的真实商品ID
main(‘10012345678‘)
5. 避坑指南与高级技巧
走通了基本流程,但想稳定、高效地采集数据,下面这些经验之谈能让你少走很多弯路。
5.1 常见问题与排查清单
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
请求返回 403 Forbidden 或 404 |
1. 请求头(Headers)不完整或不对。 2. IP被暂时封锁。 3. 请求的URL已失效。 |
1. 核对Headers :确保 User-Agent 、 Referer 、 Cookie (如需)与浏览器一致。使用开发者工具直接复制整个请求头。 2. 添加延迟 :在请求间增加 time.sleep(random.uniform(1, 3)) ,模拟真人操作。 3. 更换IP/使用代理 :对于大规模采集,考虑使用优质代理IP池。 4. 验证URL :重新在“网络”面板查看最新的请求地址。 |
| 能获取HTML但找不到SKU数据 | 1. 数据通过JavaScript动态渲染。 2. 数据在另一个独立的API接口中。 3. 页面结构已更新,XPath/CSS选择器失效。 |
1. 检查XHR请求 :这是最可能的来源,仔细查看点击规格时触发的请求。 2. 搜索隐藏的JSON :在HTML全文搜索 skuId 、 price 等关键词,可能在 <script> 标签内。 3. 更新解析器 :使用浏览器检查元素功能,重新定位元素并更新代码中的选择器路径。 |
| 解析JSON时出错 | 1. 返回的是JSONP格式(被函数包裹)。 2. JSON中包含不标准的格式(如末尾逗号)。 3. 编码问题。 |
1. 清理JSONP :使用字符串切片或正则去除函数包装,如 json.loads(text[text.find(‘(‘)+1:text.rfind(‘)‘)]) 。 2. 使用 json5 库 : json5 能解析更宽松的JSON格式。或者用正则预处理字符串。 3. 指定编码 : response.encoding = ‘utf-8‘ 。 |
| 数据保存到Excel乱码 | Excel默认可能不识别UTF-8编码的CSV。 | 1. 保存为Excel(.xlsx)格式 :使用 pandas 的 to_excel 方法,并安装 openpyxl 引擎。 2. 保存CSV时指定编码 : df.to_csv(‘file.csv‘, index=False, encoding=‘utf-8-sig‘) , utf-8-sig 会添加BOM头,Excel可正确识别。 |
| 程序运行慢 | 1. 网络请求是同步的,单线程等待。 2. 没有处理请求失败重试。 |
1. 考虑异步 :对于大量商品采集,学习使用 aiohttp + asyncio 进行异步请求。 2. 添加重试机制 :使用 requests 的适配器或 tenacity 库,在请求失败时自动重试几次。 |
5.2 提升稳定性的高级技巧
-
会话保持(Session) :使用
requests.Session()对象发起所有请求。Session会自动管理Cookies,在连续访问同一网站时能保持登录状态或会话信息,更接近真实浏览器行为。session = requests.Session() session.headers.update({‘User-Agent‘: ’your_ua_string‘}) response = session.get(url) -
处理重定向与登录 :有些商品信息可能需要登录后才能查看(如会员价)。你可以先用浏览器登录,然后从开发者工具的“网络”面板中复制
Cookie字符串,将其添加到请求头中。但请注意,Cookie会过期。 -
解析动态规格 :京东的规格选择有时是级联的(选完颜色才出现内存选项)。这意味着你需要先获取所有颜色SKU的ID,然后用这些ID再去请求对应的价格库存API。这需要更复杂的逻辑来模拟用户点击序列。
-
错误日志记录 :将程序运行中的错误信息(如失败的URL、异常信息)记录到日志文件中,便于后期排查和修复。
import logging logging.basicConfig(filename=‘spider.log‘, level=logging.ERROR) try: # 你的请求代码 except Exception as e: logging.error(f“Failed to fetch {url}: {e}”) -
遵守规则,设定间隔 :这是最重要的。在你的循环请求中,务必加入随机延时,
time.sleep(random.uniform(2, 5))。过于频繁的请求是导致IP被封的最主要原因。
6. 项目扩展与思路延伸
掌握了基础的单品SKU采集,你的数据工具箱还可以继续升级:
- 从单品到列表 :修改代码,先抓取商品搜索列表页或分类页,提取出多个商品的ID,然后循环调用上面的单品采集函数,实现批量采集。
- 定时监控与比价 :将脚本设置为定时任务(如使用系统的
crontab或Python的schedule库),每天定点采集目标商品的价格和库存,生成价格趋势图,用于监控竞品或把握促销时机。 - 数据可视化 :将采集到的价格数据用
matplotlib或pyecharts绘制成折线图,直观展示价格波动。 - 构建简单数据库 :使用
sqlite3(Python内置)或pymysql连接MySQL,将采集的数据存入数据库,便于进行更复杂的查询和分析。 - 应对更复杂的反爬 :如果遇到验证码、滑块验证等,可能需要引入图像识别库(如
ddddocr)或考虑使用更专业的浏览器自动化工具(如selenium、playwright)来模拟真人操作,但这会大幅增加复杂度和资源消耗。
这个项目就像一把钥匙,帮你打开了Python网络数据采集与处理的大门。过程中遇到的每一个错误和解决它的过程,都比顺利运行一遍代码更有价值。我刚开始的时候,也经常被反爬机制搞得焦头烂额,但每次通过分析请求、调整参数最终拿到数据时,那种成就感是无与伦比的。动手去试,遇到问题就按上面提供的排查思路一步步来,你很快就能从“入门”走向“熟练”。
更多推荐
所有评论(0)