京东的 item_get_app
接口是用于获取商品原始详情数据的核心接口,与普通的 item_get
接口相比,它返回的数据更贴近京东 APP 端展示的原始结构,包含更丰富的字段和细节信息。这些原始数据对于深度电商分析、竞品监控、价格追踪等场景具有重要价值。
一、接口核心特性分析
- 接口功能与定位
-
核心功能:获取京东商品的原始详情数据,包括商品基础信息、价格体系、库存状态、规格参数、促销活动等完整字段
-
数据特点:
- 与京东 APP 端数据结构一致,保留原始字段名和层级关系
- 包含普通接口不返回的内部标识和计算参数
- 实时性高,反映当前商品的最新状态
- 字段丰富,包含算法推荐、权重参数等内部信息
-
应用场景:
- 深度电商数据分析系统
- 商品全生命周期监控
- 价格变动实时追踪
- 竞品全方位对比分析
- 电商大数据挖掘与研究
- 认证机制
京东开放平台采用 appkey + access_token
的认证方式:
- 开发者在京东开放平台注册应用,获取
appkey
和appsecret
- 通过
appkey
和appsecret
获取access_token
(通常有效期为 24 小时) - 每次接口调用需在请求参数中携带有效
access_token
item_get_app
属于高级接口,需要单独申请权限
- 核心参数与响应结构
请求参数
参数名 | 类型 | 是否必填 | 说明 |
---|---|---|---|
sku_id |
String | 是 | 商品 SKU ID,京东商品的唯一标识 |
access_token |
String | 是 | 访问令牌 |
platform |
String | 否 | 平台类型,如 "android"、"ios",默认 "android" |
fields |
String | 否 | 需要返回的字段,默认返回全部字段 |
area |
String | 否 | 地区编码,用于获取区域化价格和库存 |
响应核心字段
- 商品基础信息:SKU ID、商品名称、品牌信息、分类信息等
- 价格信息:基准价、促销价、会员价、活动价、价格计算规则等
- 库存信息:实时库存、区域库存、库存状态、限购信息等
- 规格参数:SKU 规格、属性组合、规格图片等
- 促销信息:活动列表、优惠券、满减规则、赠品信息等
- 服务信息:售后政策、配送服务、安装服务等
- 多媒体信息:图片、视频、3D 模型等资源链接
二、Python 脚本实现
以下是调用京东 item_get_app
接口的完整 Python 实现,包含令牌获取、接口调用、数据解析等功能: import requests import time import json import logging import hashlib from typing import Dict, Optional, List from requests.exceptions import RequestException
配置日志
logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" )
class JDItemGetAppAPI: def init (self, appkey: str, appsecret: str): """ 初始化京东商品详情原数据API客户端 :param appkey: 京东开放平台appkey :param appsecret: 京东开放平台appsecret """ self.appkey = appkey self.appsecret = appsecret self.base_url = "api.jd.com" self.access_token = None self.token_expires_at = 0 # token过期时间戳 self.session = requests.Session() self.session.headers.update({ "Content-Type": "application/json", "User-Agent": "JDApp;android;9.5.4;android 10;scale/3.0" # 模拟京东APP请求头 })
python
def _get_access_token(self) -> Optional[str]:
"""获取访问令牌"""
# 检查token是否有效
if self.access_token and self.token_expires_at > time.time() + 60:
return self.access_token
logging.info("获取新的access_token")
url = f"{self.base_url}/oauth2/token"
params = {
"grant_type": "client_credentials",
"appkey": self.appkey,
"appsecret": self.appsecret
}
try:
response = self.session.get(url, params=params, timeout=10)
response.raise_for_status()
result = response.json()
if "access_token" in result:
self.access_token = result["access_token"]
self.token_expires_at = time.time() + result.get("expires_in", 86400) # 默认为24小时
return self.access_token
else:
logging.error(f"获取access_token失败: {result.get('error_description', '未知错误')}")
return None
except RequestException as e:
logging.error(f"获取access_token请求异常: {str(e)}")
return None
def get_item_raw_data(self,
sku_id: str,
platform: str = "android",
area: str = "1_72_2799_0", # 默认地区编码:北京
fields: Optional[str] = None) -> Optional[Dict]:
"""
获取商品原始详情数据
:param sku_id: 商品SKU ID
:param platform: 平台类型
:param area: 地区编码
:param fields: 需要返回的字段
:return: 商品原始数据
"""
# 验证参数
valid_platforms = ["android", "ios"]
if platform not in valid_platforms:
logging.error(f"无效的平台类型: {platform},支持: {valid_platforms}")
return None
# 获取有效的access_token
if not self._get_access_token():
return None
url = f"{self.base_url}/item/get_app"
# 构建请求参数
params = {
"sku_id": sku_id,
"access_token": self.access_token,
"platform": platform,
"area": area,
"timestamp": int(time.time() * 1000) # 时间戳,毫秒级
}
# 添加字段筛选
if fields:
params["fields"] = fields
# 生成签名(部分接口需要)
params["sign"] = self._generate_sign(params)
try:
response = self.session.get(url, params=params, timeout=20)
response.raise_for_status()
result = response.json()
# 检查响应状态
if result.get("code") == 200:
# 格式化原始数据
return self._process_raw_data(result.get("data", {}))
else:
logging.error(f"获取商品数据失败: {result.get('message', '未知错误')} (错误码: {result.get('code')})")
return Non
except RequestException as e:
logging.error(f"获取商品数据请求异常: {str(e)}")
return None
except json.JSONDecodeError:
logging.error(f"商品数据响应解析失败: {response.text[:200]}...")
return None
def _generate_sign(self, params: Dict) -> str:
"""生成签名(部分京东接口需要)"""
# 按参数名排序
sorted_params = sorted(params.items(), key=lambda x: x[0])
# 拼接参数
sign_str = self.appsecret
for k, v in sorted_params:
if k != "sign":
sign_str += f"{k}{v}"
sign_str += self.appsecret
# SHA256加密
return hashlib.sha256(sign_str.encode()).hexdigest().upper()
def _process_raw_data(self, raw_data: Dict) -> Dict:
"""处理原始数据,提取关键信息并格式化"""
# 基础信息提取
base_info = {
"sku_id": raw_data.get("skuId"),
"spu_id": raw_data.get("spuId"),
"item_id": raw_data.get("itemId"),
"title": raw_data.get("skuName"),
"sub_title": raw_data.get("skuSubName"),
"brand": {
"id": raw_data.get("brandId"),
"name": raw_data.get("brandName")
},
"category": {
"cid1": raw_data.get("cid1"),
"cid2": raw_data.get("cid2"),
"cid3": raw_data.get("cid3"),
"name1": raw_data.get("cateName1"),
"name2": raw_data.get("cateName2"),
"name3": raw_data.get("cateName3")
},
"url": f"https://item.jd.com/{raw_data.get('skuId')}.html" if raw_data.get('skuId') else None
}
# 价格信息提取
price_info = {
"jd_price": self._safe_float(raw_data.get("jdPrice", {}).get("p")), # 京东价
"market_price": self._safe_float(raw_data.get("marketPrice", {}).get("p")), # 市场价
"vip_price": self._safe_float(raw_data.get("vipPrice", {}).get("p")), # 会员价
"plus_price": self._safe_float(raw_data.get("plusPrice", {}).get("p")), # PLUS会员价
"promotion_price": self._safe_float(raw_data.get("promotionPrice", {}).get("p")), # 促销价
"price_history": raw_data.get("priceHistory"), # 价格历史
"price_tags": raw_data.get("priceTags", []) # 价格标签
}
# 库存信息提取
stock_info = {
"stock_num": self._safe_int(raw_data.get("stock", {}).get("stockNum")), # 库存数量
"stock_state": raw_data.get("stock", {}).get("stockState"), # 库存状态
"stock_state_name": raw_data.get("stock", {}).get("stockStateName"), # 库存状态名称
"limit_buy": self._safe_int(raw_data.get("limitBuy")), # 限购数量
"area_stock": raw_data.get("areaStock") # 区域库存信息
}
# 规格信息提取
sku_info = {
"total_sku": self._safe_int(raw_data.get("totalSku")), # 总规格数量
"sku_list": self._format_sku_list(raw_data.get("skuList", [])), # 规格列表
"spec_list": self._format_spec_list(raw_data.get("specList", [])) # 规格参数
}
# 促销信息提取
promotion_info = {
"promotions": raw_data.get("promotion", {}).get("promotionList", []), # 促销活动列表
"coupons": raw_data.get("couponList", []), # 优惠券列表
"gift_list": raw_data.get("giftList", []), # 赠品列表
"seckill_info": raw_data.get("seckillInfo") # 秒杀信息
}
# 图片信息提取
image_info = {
"main_images": self._extract_images(raw_data.get("mainImgList", [])), # 主图列表
"detail_images": self._extract_images(raw_data.get("detailImgList", [])), # 详情图列表
"video_url": raw_data.get("videoInfo", {}).get("videoUrl") # 视频URL
}
# 服务信息提取
service_info = {
"after_sale_service": raw_data.get("afterSaleService"), # 售后服务
"delivery_service": raw_data.get("deliveryService"), # 配送服务
"install_service": raw_data.get("installService"), # 安装服务
"warranty": raw_data.get("warranty") # 保修信息
}
return {
"base_info": base_info,
"price_info": price_info,
"stock_info": stock_info,
"sku_info": sku_info,
"promotion_info": promotion_info,
"image_info": image_info,
"service_info": service_info,
"raw_data": raw_data # 保留原始数据
}
def _safe_float(self, value) -> float:
"""安全转换为float"""
try:
return float(value) if value is not None else 0.0
except (ValueError, TypeError):
return 0.0
def _safe_int(self, value) -> int:
"""安全转换为int"""
try:
return int(value) if value is not None else 0
except (ValueError, TypeError):
return 0
def _format_sku_list(self, sku_list: List[Dict]) -> List[Dict]:
"""格式化SKU列表"""
formatted = []
for sku in sku_list:
formatted.append({
"sku_id": sku.get("skuId"),
"name": sku.get("name"),
"price": self._safe_float(sku.get("jdPrice", {}).get("p")),
"stock_num": self._safe_int(sku.get("stockNum")),
"specs": sku.get("specs"),
"image_url": sku.get("imgUrl"),
"status": sku.get("status")
})
return formatted
def _format_spec_list(self, spec_list: List[Dict]) -> List[Dict]:
"""格式化规格参数列表"""
formatted = []
for spec in spec_list:
formatted.append({
"name": spec.get("name"),
"values": [
{
"name": val.get("name"),
"image_url": val.get("imgUrl"),
"selected": val.get("selected", False)
} for val in spec.get("valueList", [])
]
})
return formatted
def _extract_images(self, image_list: List[Dict]) -> List[str]:
"""提取图片URL列表"""
images = []
for img in image_list:
if isinstance(img, dict):
url = img.get("url")
if url:
images.append(url)
elif isinstance(img, str):
images.append(img)
return images
def compare_prices(self, current_data: Dict, previous_data: Dict) -> Dict:
"""比较两个时间点的价格差异"""
if not current_data or not previous_data:
return {}
price_changes = {}
price_types = ["jd_price", "market_price", "vip_price", "plus_price", "promotion_price"]
for price_type in price_types:
current = current_data["price_info"][price_type]
previous = previous_data["price_info"][price_type]
if current != previous:
change = current - previous
change_percent = (change / previous) * 100 if previous != 0 else 0
price_changes[price_type] = {
"current": current,
"previous": previous,
"change": round(change, 2),
"change_percent": round(change_percent, 2)
}
return {
"has_change": len(price_changes) > 0,
"changes": price_changes,
"compared_at": time.strftime("%Y-%m-%d %H:%M:%S")
}
示例调用 if name == "main": # 替换为实际的appkey和appsecret(从京东开放平台获取) APPKEY = "your_appkey" APPSECRET = "your_appsecret" # 替换为目标商品SKU ID SKU_ID = "100012345678"
python
# 初始化API客户端
api = JDItemGetAppAPI(APPKEY, APPSECRET)
# 获取商品原始数据
item_data = api.get_item_raw_data(
sku_id=SKU_ID,
platform="android",
area="1_72_2799_0" # 北京地区编码
# fields="skuId,skuName,jdPrice,stock" # 可选,指定需要的字段
)
if item_data:
print(f"=== 京东商品详情 (SKU: {SKU_ID}) ===")
print(f"商品名称: {item_data['base_info']['title']}")
print(f"品牌: {item_data['base_info']['brand']['name']}")
print(f"分类: {item_data['base_info']['category']['name1']} > {item_data['base_info']['category']['name2']} > {item_data['base_info']['category']['name3']}")
print(f"京东价: {item_data['price_info']['jd_price']}元")
if item_data['price_info']['promotion_price'] and item_data['price_info']['promotion_price'] < item_data['price_info']['jd_price']:
print(f"促销价: {item_data['price_info']['promotion_price']}元")
if item_data['price_info']['vip_price'] and item_data['price_info']['vip_price'] < item_data['price_info']['jd_price']:
print(f"会员价: {item_data['price_info']['vip_price']}元")
print(f"库存状态: {item_data['stock_info']['stock_state_name']}")
print(f"规格数量: {item_data['sku_info']['total_sku']}")
print(f"主图数量: {len(item_data['image_info']['main_images'])}")
print(f"促销活动数量: {len(item_data['promotion_info']['promotions'])}")
# 打印前3个规格信息
if item_data['sku_info']['sku_list']:
print("\n规格信息:")
for i, sku in enumerate(item_data['sku_info']['sku_list'][:3], 1):
print(f" {i}. {sku['name']}: {sku['price']}元 (库存: {sku['stock_num']})")
# 打印主要促销活动
if item_data['promotion_info']['promotions']:
print("\n促销活动:")
for i, promo in enumerate(item_data['promotion_info']['promotions'][:3], 1):
print(f" {i}. {promo.get('title')}")
三、接口调用注意事项
- 调用限制与规范
- 权限要求 :
item_get_app
属于高级接口,需要向京东开放平台单独申请权限 - QPS 限制:该接口 QPS 限制通常较低(1-5 次 / 秒),需严格控制调用频率
- 数据缓存:建议缓存获取的数据(缓存时间 30-60 分钟),减少重复调用
- 地区编码:不同地区可能有不同的价格和库存,需正确设置 area 参数
- 字段筛选:不需要全部字段时,通过 fields 参数指定所需字段,提高响应速度
- 常见错误及解决方案
错误码 | 说明 | 解决方案 |
---|---|---|
401 | 未授权或 token 无效 | 重新获取 access_token,检查权限是否正确 |
403 | 权限不足 | 申请item_get_app 接口的访问权限 |
404 | 商品不存在 | 确认 sku_id 是否正确有效 |
429 | 调用频率超限 | 降低调用频率,实现请求限流 |
500 | 服务器内部错误 | 实现重试机制,最多 3 次,间隔指数退避 |
10002 | 参数错误 | 检查地区编码等参数是否正确 |
- 数据解析要点
- 价格字段:价格相关字段可能以字符串形式返回,需转换为数值类型
- 库存状态:库存状态有多种编码,需结合状态名称理解实际含义
- 规格结构:规格数据结构复杂,需递归解析多层规格组合
- 促销信息:促销活动有多种类型,需分别处理不同的促销规则
- 图片 URL:部分图片 URL 需要拼接域名才能访问
四、应用场景与扩展建议
典型应用场景
- 商品全信息监控系统:实时监控商品所有属性的变化
- 价格趋势分析工具:追踪商品价格历史,预测价格变化
- 竞品深度分析平台:全方位对比分析竞品的各项参数和策略
- 库存预警系统:基于原始库存数据设置预警阈值
- 电商大数据分析平台:挖掘商品数据中的关联关系和规律
扩展建议
- 实现价格变化监控:定期获取数据,对比价格变化并记录
- 开发库存趋势分析:跟踪库存数量变化,预测库存耗尽时间
- 构建商品特征向量:将原始数据转换为特征向量,用于机器学习
- 实现多地区数据对比:获取不同地区的价格和库存,分析区域差异
- 开发数据导出功能:支持将原始数据和解析后的数据导出为 CSV/Excel
- 构建商品相似度计算:基于原始属性计算商品间的相似度 通过合理使用京东
item_get_app
接口,开发者可以获取最全面的商品原始数据,为深度电商分析和决策支持提供有力保障。使用时需遵守京东开放平台的相关规定,确保数据的合法使用。