商品详情页实时数据采集与对接演示:京东 API 接口开发全流程

在电商数据驱动决策的场景中,商品详情页数据的实时性与准确性直接影响业务效果。京东提供的标准化 API 接口,为合规采集商品数据提供了可靠通道。本文将从开发准备、接口调试到数据对接的全流程进行实战演示,完整呈现京东商品详情页实时数据采集与业务系统对接的技术实现。

一、全流程架构设计与开发准备

1.1 全流程架构

商品详情页数据采集与对接的完整流程可分为「接口层 - 解析层 - 存储层 - 对接层」四个核心环节,各环节职责如下:

  • 接口层:负责京东 API 的认证、请求发送与响应接收
  • 解析层:将 API 返回的原始数据转换为结构化业务数据
  • 存储层:将结构化数据存入数据库,并通过缓存提升访问效率
  • 对接层:提供标准化接口供业务系统(如 ERP、BI 工具)调用

1.2 开发环境准备

  • 开发语言:Python 3.10(推荐,支持异步特性与类型注解)
  • 依赖库:requests(HTTP 请求)、pymysql(MySQL 交互)、redis(缓存)、fastapi(对接层 API 服务)
  • 数据库:MySQL 8.0(存储结构化数据)、Redis 6.2(缓存热点数据)
  • 工具:Postman(API 调试)、PyCharm(开发 IDE)

1.3 京东开放平台配置

  1. 注册开发者账号,获取 api_key 和 api_secret(接口调用凭证)
  2. 申请「商品详情查询」接口权限,记录接口文档中的字段说明
  3. 确认接口调用限制(如 QPS=10,日调用量 = 10 万次),避免触发限流

二、接口层开发:京东 API 调用实现

2.1 签名生成与参数处理

京东 API 采用签名验证机制,需按规则生成 sign 参数以确保请求合法性:

import hashlib
import time
from urllib.parse import urlencode
from typing import Dict


class JDAPIAuth:
    """京东API认证工具类,负责签名生成与参数处理"""
    @staticmethod
    def generate_sign(params: Dict[str, str], app_secret: str) -> str:
        """
        生成签名:按参数名ASCII升序排序→拼接→MD5加密
        :param params: 接口请求参数(不含sign)
        :param app_secret: 应用密钥
        :return: 签名字符串
        """
        # 1. 按参数名ASCII升序排序
        sorted_params = sorted(params.items(), key=lambda x: x[0])
        # 2. 拼接为key=value&key=value格式
        param_str = urlencode(sorted_params)
        # 3. 追加app_secret并MD5加密(转大写)
        sign_str = f"{param_str}{app_secret}"
        return hashlib.md5(sign_str.encode("utf-8")).hexdigest().upper()

    @staticmethod
    def get_timestamp() -> str:
        """生成符合格式的时间戳(yyyy-MM-dd HH:mm:ss)"""
        return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())

2.2 API 调用核心类

封装商品详情页数据采集的核心逻辑,支持超时重试与错误处理:

import requests
import json
from typing import Optional, Dict
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry


class JDGoodsAPIClient:
    """京东商品详情API客户端"""
    def __init__(self, app_key: str, app_secret: str):
        self.app_key = app_key
        self.app_secret = app_secret
        self.api_endpoint = "https://api.jd.com/routerjson"  # API网关地址
        self.auth = JDAPIAuth()
        self.session = self._init_session()  # 初始化带重试的会话

    def _init_session(self) -> requests.Session:
        """初始化HTTP会话,配置重试策略"""
        session = requests.Session()
        retry_strategy = Retry(
            total=3,  # 最大重试次数
            backoff_factor=0.5,  # 重试间隔(0.5, 1, 2秒)
            status_forcelist=[429, 500, 502, 503, 504]  # 需要重试的状态码
        )
        adapter = HTTPAdapter(max_retries=retry_strategy)
        session.mount("https://", adapter)
        return session

    def fetch_goods_detail(self, ware_id: str) -> Optional[Dict]:
        """
        调用API获取商品详情原始数据
        :param ware_id: 商品ID(从商品URL提取,如100012345678)
        :return: 原始数据字典,失败返回None
        """
        # 1. 组装基础参数
        base_params = {
            "method": "jingdong.ware.detail.get",  # 接口方法名
            "app_key": self.app_key,
            "timestamp": self.auth.get_timestamp(),
            "v": "2.0",  # 接口版本
            "format": "json",  # 返回格式
            "ware_id": ware_id  # 商品ID
        }

        # 2. 生成签名并添加到参数
        base_params["sign"] = self.auth.generate_sign(base_params, self.app_secret)

        # 3. 发送POST请求
        try:
            response = self.session.post(
                url=self.api_endpoint,
                data=base_params,
                timeout=10  # 超时时间10秒
            )
            response.raise_for_status()  # 抛出HTTP错误(如401、500)
        except requests.exceptions.RequestException as e:
            print(f"API请求失败:{str(e)}")
            return None

        # 4. 解析响应数据
        try:
            result = json.loads(response.text)
            # 京东API返回格式:{"jingdong_ware_detail_get_response": {"result": {...}}}
            response_key = "jingdong_ware_detail_get_response"
            if response_key in result:
                return result[response_key].get("result", {})
            else:
                print(f"API返回格式异常:{result}")
                return None
        except json.JSONDecodeError as e:
            print(f"JSON解析失败:{str(e)}")
            return None

三、解析层开发:原始数据结构化处理

API 返回的原始数据字段繁多,需提取核心业务字段并转换为结构化格式:

from typing import Dict, List, Optional


class GoodsDataParser:
    """商品数据解析器,将原始API数据转换为业务结构化数据"""
    @staticmethod
    def parse(raw_data: Dict) -> Optional[Dict]:
        """
        解析原始数据,提取核心字段
        :param raw_data: API返回的原始数据
        :return: 结构化商品信息
        """
        if not raw_data:
            return None

        # 提取基础信息
        base_info = raw_data.get("base", {})
        # 提取价格信息(支持多价格类型)
        price_info = raw_data.get("price", {})
        # 提取库存信息
        stock_info = raw_data.get("stock", {})
        # 提取SKU信息
        sku_list = raw_data.get("skuList", [])

        # 结构化转换
        return {
            "goods_id": base_info.get("wareId", ""),  # 商品ID
            "goods_name": base_info.get("wareName", ""),  # 商品名称
            "brand": base_info.get("brandName", ""),  # 品牌名称
            "main_image": base_info.get("mainImgUrl", ""),  # 主图URL
            "jd_price": price_info.get("jdPrice", {}).get("price", 0.0),  # 京东价
            "market_price": price_info.get("marketPrice", {}).get("price", 0.0),  # 市场价
            "stock_num": stock_info.get("stockNum", 0),  # 库存数量
            "is_in_stock": stock_info.get("isStock", False),  # 是否有货
            "category": base_info.get("category", {}).get("categoryName", ""),  # 所属分类
            "sku_count": len(sku_list),  # SKU数量
            "skus": [  # SKU列表
                {
                    "sku_id": sku.get("skuId", ""),
                    "sku_name": sku.get("skuName", ""),
                    "sku_price": sku.get("jdPrice", {}).get("price", 0.0)
                } for sku in sku_list
            ],
            "update_time": JDAPIAuth.get_timestamp()  # 数据更新时间
        }

四、存储层开发:数据持久化与缓存

4.1 数据库表设计

创建 MySQL 表存储商品详情数据(示例 SQL):

CREATE TABLE `jd_goods_detail` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `goods_id` varchar(20) NOT NULL COMMENT '商品ID',
  `goods_name` varchar(255) NOT NULL COMMENT '商品名称',
  `brand` varchar(100) DEFAULT '' COMMENT '品牌名称',
  `main_image` varchar(512) DEFAULT '' COMMENT '主图URL',
  `jd_price` decimal(10,2) DEFAULT 0.00 COMMENT '京东价',
  `market_price` decimal(10,2) DEFAULT 0.00 COMMENT '市场价',
  `stock_num` int DEFAULT 0 COMMENT '库存数量',
  `is_in_stock` tinyint(1) DEFAULT 0 COMMENT '是否有货(1是0否)',
  `category` varchar(100) DEFAULT '' COMMENT '所属分类',
  `sku_count` int DEFAULT 0 COMMENT 'SKU数量',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_goods_id` (`goods_id`) COMMENT '商品ID唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='京东商品详情表';

4.2 数据存储与缓存实现

import pymysql
import redis
import json
from typing import Dict, Optional
from pymysql.cursors import DictCursor


class GoodsDataStorage:
    """商品数据存储管理器,负责数据库存储与缓存"""
    def __init__(self, mysql_config: Dict, redis_config: Dict):
        # 初始化MySQL连接
        self.mysql_conn = pymysql.connect(
            host=mysql_config["host"],
            user=mysql_config["user"],
            password=mysql_config["password"],
            database=mysql_config["db"],
            port=mysql_config.get("port", 3306),
            cursorclass=DictCursor
        )
        # 初始化Redis连接
        self.redis_client = redis.Redis(
            host=redis_config["host"],
            port=redis_config.get("port", 6379),
            password=redis_config.get("password", ""),
            db=redis_config.get("db", 0),
            decode_responses=True  # 自动解码为字符串
        )

    def save_to_mysql(self, data: Dict) -> bool:
        """
        保存结构化数据到MySQL(存在则更新,不存在则插入)
        :param data: 结构化商品数据
        :return: 操作是否成功
        """
        if not data.get("goods_id"):
            print("商品ID为空,跳过存储")
            return False

        sql = """
        INSERT INTO jd_goods_detail (
            goods_id, goods_name, brand, main_image, jd_price, market_price,
            stock_num, is_in_stock, category, sku_count, update_time
        ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
        ON DUPLICATE KEY UPDATE
            goods_name = VALUES(goods_name),
            brand = VALUES(brand),
            main_image = VALUES(main_image),
            jd_price = VALUES(jd_price),
            market_price = VALUES(market_price),
            stock_num = VALUES(stock_num),
            is_in_stock = VALUES(is_in_stock),
            category = VALUES(category),
            sku_count = VALUES(sku_count),
            update_time = VALUES(update_time)
        """

        try:
            with self.mysql_conn.cursor() as cursor:
                cursor.execute(sql, (
                    data["goods_id"],
                    data["goods_name"],
                    data["brand"],
                    data["main_image"],
                    data["jd_price"],
                    data["market_price"],
                    data["stock_num"],
                    data["is_in_stock"],
                    data["category"],
                    data["sku_count"],
                    data["update_time"]
                ))
                self.mysql_conn.commit()
            print(f"商品[{data['goods_id']}]已保存到MySQL")
            return True
        except Exception as e:
            self.mysql_conn.rollback()
            print(f"MySQL存储失败:{str(e)}")
            return False

    def cache_to_redis(self, goods_id: str, data: Dict, expire=3600) -> bool:
        """
        缓存数据到Redis(默认1小时过期)
        :param goods_id: 商品ID
        :param data: 结构化商品数据
        :param expire: 过期时间(秒)
        :return: 操作是否成功
        """
        try:
            self.redis_client.set(
                f"jd_goods:{goods_id}",
                json.dumps(data, ensure_ascii=False),
                ex=expire
            )
            print(f"商品[{goods_id}]已缓存到Redis")
            return True
        except Exception as e:
            print(f"Redis缓存失败:{str(e)}")
            return False

    def close(self):
        """关闭数据库连接"""
        self.mysql_conn.close()
        self.redis_client.close()

五、对接层开发:业务系统接口服务

使用 FastAPI 提供标准化接口,供业务系统(如 ERP、数据分析平台)调用:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional, Dict

# 初始化FastAPI应用
app = FastAPI(title="京东商品详情对接API")

# 全局存储实例(实际应用中建议用依赖注入)
storage = None  # 需在启动时初始化:storage = GoodsDataStorage(...)
api_client = None  # 需在启动时初始化:api_client = JDGoodsAPIClient(...)
parser = GoodsDataParser()


# 数据模型定义(API返回格式)
class GoodsDetailResponse(BaseModel):
    code: int = 200
    message: str = "success"
    data: Optional[Dict] = None


@app.get("/goods/detail", response_model=GoodsDetailResponse)
def get_goods_detail(ware_id: str):
    """
    业务系统对接接口:获取商品详情(优先从缓存获取,缓存失效则调用API)
    :param ware_id: 商品ID
    :return: 结构化商品数据
    """
    # 1. 尝试从Redis缓存获取
    cached_data = storage.redis_client.get(f"jd_goods:{ware_id}")
    if cached_data:
        return {"data": json.loads(cached_data)}

    # 2. 缓存失效,调用京东API获取
    raw_data = api_client.fetch_goods_detail(ware_id)
    if not raw_data:
        raise HTTPException(status_code=500, detail="获取商品数据失败")

    # 3. 解析数据
    parsed_data = parser.parse(raw_data)
    if not parsed_data:
        raise HTTPException(status_code=500, detail="数据解析失败")

    # 4. 存储并缓存
    storage.save_to_mysql(parsed_data)
    storage.cache_to_redis(ware_id, parsed_data)

    return {"data": parsed_data}


# 启动命令:uvicorn main:app --host 0.0.0.0 --port 8000

六、全流程演示与测试

6.1 流程串联执行

def run_full_process(ware_id: str):
    """执行商品数据采集-解析-存储全流程"""
    # 配置参数(替换为实际值)
    config = {
        "app_key": "your_app_key",
        "app_secret": "your_app_secret",
        "mysql": {
            "host": "localhost",
            "user": "root",
            "password": "123456",
            "db": "jd_goods_db"
        },
        "redis": {
            "host": "localhost",
            "port": 6379
        }
    }

    # 初始化组件
    api_client = JDGoodsAPIClient(config["app_key"], config["app_secret"])
    storage = GoodsDataStorage(config["mysql"], config["redis"])

    # 1. 调用API获取原始数据
    raw_data = api_client.fetch_goods_detail(ware_id)
    if not raw_data:
        print("流程终止:API获取数据失败")
        return

    # 2. 解析数据
    parsed_data = GoodsDataParser.parse(raw_data)
    if not parsed_data:
        print("流程终止:数据解析失败")
        return

    # 3. 存储与缓存
    storage.save_to_mysql(parsed_data)
    storage.cache_to_redis(ware_id, parsed_data)

    # 4. 关闭连接
    storage.close()
    print("全流程执行完成")


if __name__ == "__main__":
    # 测试商品ID(替换为实际存在的商品ID)
    test_ware_id = "100012345678"
    run_full_process(test_ware_id)

6.2 测试验证

  1. 接口调用测试:运行 run_full_process 函数,检查控制台输出是否显示 “全流程执行完成”
  2. 数据库验证:登录 MySQL,查询 jd_goods_detail 表,确认存在测试商品 ID 的数据
  3. 缓存验证:使用 redis-cli 执行 GET jd_goods:100012345678,确认返回 JSON 数据
  4. 对接接口测试:启动 FastAPI 服务,访问 http://localhost:8000/goods/detail?ware_id=100012345678,验证返回数据是否正确

七、总结与扩展建议

本文完整演示了京东商品详情页实时数据采集与对接的全流程,从 API 调用、数据解析、存储到业务对接,覆盖了技术实现的核心环节。关键技术点包括:

  • 基于签名的 API 身份认证确保请求安全
  • 结构化解析实现原始数据到业务数据的转换
  • 数据库 + 缓存的存储策略平衡实时性与性能
  • 标准化对接接口降低业务系统集成成本

扩展建议

  1. 增加定时任务(如 APScheduler),定期更新商品数据
  2. 实现批量采集功能,支持多商品 ID 并发处理
  3. 增加监控告警(如失败次数超限通知)
  4. 对接消息队列(如 Kafka),实现数据异步处理

通过该方案,企业可合规、高效地获取京东商品详情页实时数据,为电商运营决策提供数据支撑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值