×

京东 item_get_app 接口深度分析及 Python 实现

万邦科技Lex 万邦科技Lex 发表于2025-09-06 10:43:12 浏览272 评论0

抢沙发发表评论

             注册账号免费测试京东API接口

京东的 item_get_app 接口是用于获取商品原始详情数据的核心接口,与普通的 item_get 接口相比,它返回的数据更贴近京东 APP 端展示的原始结构,包含更丰富的字段和细节信息。这些原始数据对于深度电商分析、竞品监控、价格追踪等场景具有重要价值。

一、接口核心特性分析

1. 接口功能与定位

  • 核心功能:获取京东商品的原始详情数据,包括商品基础信息、价格体系、库存状态、规格参数、促销活动等完整字段

  • 数据特点

    • 与京东 APP 端数据结构一致,保留原始字段名和层级关系

    • 包含普通接口不返回的内部标识和计算参数

    • 实时性高,反映当前商品的最新状态

    • 字段丰富,包含算法推荐、权重参数等内部信息

  • 应用场景

    • 深度电商数据分析系统

    • 商品全生命周期监控

    • 价格变动实时追踪

    • 竞品全方位对比分析

    • 电商大数据挖掘与研究

2. 认证机制

京东开放平台采用 appkey + access_token 的认证方式:


  • 开发者在京东开放平台注册应用,获取 appkey 和 appsecret

  • 通过 appkey 和 appsecret 获取 access_token(通常有效期为 24 小时)

  • 每次接口调用需在请求参数中携带有效 access_token

  • item_get_app 属于高级接口,需要单独申请权限

3. 核心参数与响应结构

请求参数

参数名类型是否必填说明
sku_idString商品 SKU ID,京东商品的唯一标识
access_tokenString访问令牌
platformString平台类型,如 "android"、"ios",默认 "android"
fieldsString需要返回的字段,默认返回全部字段
areaString地区编码,用于获取区域化价格和库存

响应核心字段

  • 商品基础信息: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 = "https://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请求头
        })

    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 None
                
        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"
    
    # 初始化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')}")

三、接口调用注意事项

1. 调用限制与规范

  • 权限要求item_get_app 属于高级接口,需要向京东开放平台单独申请权限

  • QPS 限制:该接口 QPS 限制通常较低(1-5 次 / 秒),需严格控制调用频率

  • 数据缓存:建议缓存获取的数据(缓存时间 30-60 分钟),减少重复调用

  • 地区编码:不同地区可能有不同的价格和库存,需正确设置 area 参数

  • 字段筛选:不需要全部字段时,通过 fields 参数指定所需字段,提高响应速度

2. 常见错误及解决方案

错误码说明解决方案
401未授权或 token 无效重新获取 access_token,检查权限是否正确
403权限不足申请item_get_app接口的访问权限
404商品不存在确认 sku_id 是否正确有效
429调用频率超限降低调用频率,实现请求限流
500服务器内部错误实现重试机制,最多 3 次,间隔指数退避
10002参数错误检查地区编码等参数是否正确

3. 数据解析要点

  • 价格字段:价格相关字段可能以字符串形式返回,需转换为数值类型

  • 库存状态:库存状态有多种编码,需结合状态名称理解实际含义

  • 规格结构:规格数据结构复杂,需递归解析多层规格组合

  • 促销信息:促销活动有多种类型,需分别处理不同的促销规则

  • 图片 URL:部分图片 URL 需要拼接域名才能访问

四、应用场景与扩展建议

典型应用场景

  • 商品全信息监控系统:实时监控商品所有属性的变化

  • 价格趋势分析工具:追踪商品价格历史,预测价格变化

  • 竞品深度分析平台:全方位对比分析竞品的各项参数和策略

  • 库存预警系统:基于原始库存数据设置预警阈值

  • 电商大数据分析平台:挖掘商品数据中的关联关系和规律

扩展建议

  • 实现价格变化监控:定期获取数据,对比价格变化并记录

  • 开发库存趋势分析:跟踪库存数量变化,预测库存耗尽时间

  • 构建商品特征向量:将原始数据转换为特征向量,用于机器学习

  • 实现多地区数据对比:获取不同地区的价格和库存,分析区域差异

  • 开发数据导出功能:支持将原始数据和解析后的数据导出为 CSV/Excel

  • 构建商品相似度计算:基于原始属性计算商品间的相似度


通过合理使用京东 item_get_app 接口,开发者可以获取最全面的商品原始数据,为深度电商分析和决策支持提供有力保障。使用时需遵守京东开放平台的相关规定,确保数据的合法使用。

群贤毕至

访客