一、京东API接入方案概览
京东提供两类商品数据获取途径:
| 方案 | 适用场景 | 特点 | 门槛 |
|---|---|---|---|
| 京东宙斯开放平台(JOS) | 京东商家、ISV服务商 | 官方接口,数据全面,需入驻 | 需企业资质,审核周期 |
| 第三方数据服务API | 开发者快速接入 | 简化封装,即开即用 | 付费,需注意合规性 |
| 京东联盟API | 推广者获取商品信息 | 侧重佣金、推广链接 | 需开通京东联盟账号 |
⚠️ 合规提示:非京东商家爬取商品数据需遵守《京东开放平台服务协议》,个人开发者建议使用京东联盟或正规第三方数据服务。
二、方案一:京东宙斯开放平台(JOS)官方接入
2.1 入驻与权限申请流程
plain
1. 注册京东开放平台账号(open.jd.com)
↓
2. 企业实名认证(营业执照、对公账户)
↓
3. 创建应用,申请"商品API"权限(jd.item.get)
↓
4. 获取 AppKey / AppSecret
↓
5. 沙箱测试 → 上线审核2.2 核心API说明
jd.item.get - 获取单个商品详情| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
skuId | Long | 是 | 京东商品SKU编号 |
fields | String | 否 | 指定返回字段,逗号分隔 |
常用返回字段:
skuId,name(商品名称)price,marketPrice(售价/市场价)imageUrl(主图)stock(库存状态)category(类目信息)detailUrl(PC详情页)mobileDetailUrl(无线详情页)
2.3 Java SDK集成实战
Maven依赖引入:
xml
<!-- 京东宙斯SDK --><dependency>
<groupId>com.jd</groupId>
<artifactId>jos-sdk</artifactId>
<version>2.0.20231215</version></dependency><!-- JSON处理 --><dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.43</version></dependency><!-- HTTP客户端 --><dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>5.8.22</version></dependency>核心实现类:
java
import com.jd.open.api.sdk.DefaultJdClient;import com.jd.open.api.sdk.JdClient;import com.jd.open.api.sdk.JdException;import com.jd.open.api.sdk.request.item.ItemGetRequest;import com.jd.open.api.sdk.response.item.ItemGetResponse;import com.alibaba.fastjson2.JSON;import com.alibaba.fastjson2.JSONObject;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;@Slf4j@Servicepublic class JdItemService {
@Value("${jd.app.key}")
private String appKey;
@Value("${jd.app.secret}")
private String appSecret;
@Value("${jd.server.url:https://api.jd.com/routerjson}")
private String serverUrl;
@Value("${jd.access.token}")
private String accessToken; // 需通过OAuth2授权获取
private JdClient jdClient;
@PostConstruct
public void init() {
// 初始化京东客户端
this.jdClient = new DefaultJdClient(serverUrl, accessToken, appKey, appSecret);
log.info("京东JOS客户端初始化完成");
}
/**
* 获取商品详情(官方API)
*/
public JdItemDTO getItemDetail(Long skuId) {
try {
ItemGetRequest request = new ItemGetRequest();
request.setSkuId(skuId);
// 指定返回字段,减少数据传输
request.setFields("skuId,name,price,marketPrice,imageUrl," +
"stock,category,detailUrl,mobileDetailUrl," +
"brandName,upc,weight");
ItemGetResponse response = jdClient.execute(request);
if (!"0".equals(response.getCode())) {
log.error("京东API调用失败: code={}, msg={}",
response.getCode(), response.getMsg());
throw new BizException("获取商品信息失败: " + response.getMsg());
}
return convertToDTO(response.getItem());
} catch (JdException e) {
log.error("京东SDK异常: skuId={}", skuId, e);
throw new BizException("京东服务异常", e);
}
}
/**
* 批量获取商品(使用批量API)
*/
public List<JdItemDTO> batchGetItems(List<Long> skuIds) {
// 京东限制单次最多20个SKU
List<JdItemDTO> result = new ArrayList<>();
List<List<Long>> partitions = Lists.partition(skuIds, 20);
for (List<Long> batch : partitions) {
// 使用 jd.item.sku.get 批量接口
// 注意:批量接口需单独申请权限
result.addAll(batchGetByIds(batch));
}
return result;
}
private JdItemDTO convertToDTO(ItemGetResponse.Item item) {
return JdItemDTO.builder()
.skuId(item.getSkuId())
.name(item.getName())
.price(new BigDecimal(item.getPrice()))
.marketPrice(new BigDecimal(item.getMarketPrice()))
.mainImage(item.getImageUrl())
.stockStatus("1".equals(item.getStock()) ? "有货" : "无货")
.categoryId(item.getCategory())
.detailUrl(item.getDetailUrl())
.brandName(item.getBrandName())
.weight(item.getWeight())
.build();
}}2.4 签名算法详解(自研接入时用)
京东API使用MD5签名,流程如下:
java
import cn.hutool.crypto.digest.MD5;import cn.hutool.http.HttpUtil;import java.util.Map;import java.util.TreeMap;public class JdSignUtil {
/**
* 生成京东API请求签名
*/
public static String generateSign(Map<String, String> params, String appSecret) {
// 1. 参数排序(TreeMap自动排序)
TreeMap<String, String> sortedParams = new TreeMap<>(params);
// 2. 拼接字符串: key1value1key2value2...
StringBuilder paramStr = new StringBuilder();
sortedParams.forEach((k, v) -> {
if (v != null && !v.isEmpty()) {
paramStr.append(k).append(v);
}
});
// 3. 首尾拼接AppSecret
String signStr = appSecret + paramStr.toString() + appSecret;
// 4. MD5加密,转大写
return MD5.create().digestHex(signStr).toUpperCase();
}
/**
* 构建完整请求URL
*/
public static String buildRequestUrl(String apiMethod, Map<String, String> params,
String appKey, String appSecret) {
// 系统级参数
params.put("app_key", appKey);
params.put("method", apiMethod);
params.put("timestamp", DateUtil.now());
params.put("v", "2.0");
params.put("sign_method", "md5");
params.put("format", "json");
// 生成签名
String sign = generateSign(params, appSecret);
params.put("sign", sign);
// 构建URL
return "https://api.jd.com/routerjson?" + HttpUtil.toParams(params);
}}三、方案二:第三方商品数据API接入
3.1 技术架构设计
对于快速验证或非商家场景,可对接聚合数据服务:
plain
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 你的应用 │────▶│ 代理服务层 │────▶│ 第三方API │
│ (SpringBoot)│◀────│ (缓存+限流) │◀────│ (按次计费) │
└─────────────┘ └─────────────┘ └─────────────┘
│
┌──────┴──────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ Redis │ │ 本地缓存 │
│ (热点数据)│ │ (Guava) │
└─────────┘ └─────────┘3.2 完整Java实现
配置类:
yaml
# application.ymljd:
api:
provider: third-party # 或 official
third-party:
base-url: https://api.example-data.com/jd api-key: ${JD_API_KEY:your_api_key_here}
secret: ${JD_API_SECRET:your_secret_here}
timeout: 10000
retry-times: 3DTO设计:
java
@Data@Builderpublic class JdItemDetailDTO {
// 基础信息
private Long skuId;
private String name;
private String subTitle;
// 价格信息
private BigDecimal jdPrice; // 京东价
private BigDecimal marketPrice; // 市场价
private BigDecimal commission; // 佣金(联盟)
// 媒体信息
private String mainImage;
private List<String> imageList;
private String detailHtml; // 详情页HTML
// 库存物流
private Boolean hasStock;
private String delivery;
// 类目规格
private Long categoryId;
private String categoryName;
private List<SkuSpec> specs;
// 店铺信息
private Long shopId;
private String shopName;
@Data
public static class SkuSpec {
private String specName;
private String specValue;
}}服务实现(含缓存与降级):
java
import cn.hutool.http.HttpRequest;import cn.hutool.http.HttpResponse;import com.github.benmanes.caffeine.cache.Caffeine;import com.github.benmanes.caffeine.cache.LoadingCache;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;import javax.annotation.PostConstruct;import java.math.BigDecimal;import java.util.concurrent.TimeUnit;@Slf4j@Servicepublic class ThirdPartyJdService {
@Autowired
private JdApiProperties properties;
@Autowired
private StringRedisTemplate redisTemplate;
// 本地缓存(Caffeine)- 防热点穿透
private LoadingCache<Long, JdItemDetailDTO> localCache;
@PostConstruct
public void init() {
localCache = Caffeine.newBuilder()
.maximumSize(10_000) // 最大条目
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后5分钟过期
.recordStats() // 开启统计
.build(this::fetchFromApi); // 未命中时调用API
}
/**
* 获取商品详情(多级缓存)
*/
public JdItemDetailDTO getItemDetail(Long skuId) {
// 1. 本地缓存
JdItemDetailDTO cached = localCache.getIfPresent(skuId);
if (cached != null) {
log.debug("本地缓存命中: skuId={}", skuId);
return cached;
}
// 2. Redis缓存
String redisKey = "jd:item:" + skuId;
String json = redisTemplate.opsForValue().get(redisKey);
if (StringUtils.hasText(json)) {
JdItemDetailDTO dto = JSON.parseObject(json, JdItemDetailDTO.class);
localCache.put(skuId, dto); // 回填本地缓存
return dto;
}
// 3. 调用API(带熔断降级)
return fetchWithCircuitBreaker(skuId);
}
/**
* API调用(带重试与签名)
*/
private JdItemDetailDTO fetchFromApi(Long skuId) {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String sign = generateHmacSign(skuId, timestamp);
String url = properties.getBaseUrl() + "/item/detail";
// 重试机制
int retry = 0;
while (retry < properties.getRetryTimes()) {
try (HttpResponse response = HttpRequest.get(url)
.header("X-Api-Key", properties.getApiKey())
.header("X-Timestamp", timestamp)
.header("X-Sign", sign)
.form("sku_id", skuId)
.timeout(properties.getTimeout())
.execute()) {
if (response.getStatus() == 200) {
String body = response.body();
ApiResult<JdItemDetailDTO> result = JSON.parseObject(body,
new TypeReference<ApiResult<JdItemDetailDTO>>() {});
if (result.isSuccess()) {
JdItemDetailDTO dto = result.getData();
// 异步写入Redis
asyncSaveToRedis(skuId, dto);
return dto;
}
throw new BizException("API返回错误: " + result.getMsg());
}
} catch (Exception e) {
log.warn("API调用失败,准备重试: skuId={}, retry={}", skuId, retry, e);
retry++;
if (retry >= properties.getRetryTimes()) {
throw new BizException("获取商品信息失败,请稍后重试");
}
try {
Thread.sleep(100 * retry); // 指数退避
} catch (InterruptedException ignored) {}
}
}
return null;
}
/**
* HMAC-SHA256签名
*/
private String generateHmacSign(Long skuId, String timestamp) {
String content = "sku_id=" + skuId + "×tamp=" + timestamp;
return HmacUtil.hmacSha256(content, properties.getSecret());
}
/**
* 异步保存到Redis(延长过期时间)
*/
private void asyncSaveToRedis(Long skuId, JdItemDetailDTO dto) {
CompletableFuture.runAsync(() -> {
String key = "jd:item:" + skuId;
// 价格信息过期时间短(5分钟),基础信息过期时间长(1小时)
redisTemplate.opsForValue().set(key, JSON.toJSONString(dto), 5, TimeUnit.MINUTES);
});
}
/**
* 熔断降级处理(使用Resilience4j)
*/
@CircuitBreaker(name = "jdApi", fallbackMethod = "fallbackMethod")
private JdItemDetailDTO fetchWithCircuitBreaker(Long skuId) {
return localCache.get(skuId);
}
// 降级方法:返回基础信息或从数据库获取
private JdItemDetailDTO fallbackMethod(Long skuId, Throwable ex) {
log.error("京东API熔断降级: skuId={}", skuId, ex);
// 返回简化版数据或抛出友好提示
return JdItemDetailDTO.builder()
.skuId(skuId)
.name("商品信息暂不可用")
.build();
}}四、高级场景:详情页HTML解析
京东详情页为富文本HTML,需特殊处理:
java
import org.jsoup.Jsoup;import org.jsoup.nodes.Document;import org.jsoup.nodes.Element;import org.jsoup.select.Elements;@Servicepublic class JdDetailParser {
/**
* 解析京东详情页HTML,提取图片与文本
*/
public ItemDescription parseDetailHtml(String html) {
Document doc = Jsoup.parse(html);
ItemDescription desc = new ItemDescription();
// 提取主图列表
Elements mainImages = doc.select("#spec-list img");
List<String> imageUrls = mainImages.stream()
.map(img -> img.attr("data-url"))
.map(this::completeImageUrl)
.collect(Collectors.toList());
desc.setImageList(imageUrls);
// 提取详情内容(过滤广告与无效标签)
Elements detailContent = doc.select(".detail-content img, .detail-content p");
List<ContentNode> nodes = new ArrayList<>();
for (Element el : detailContent) {
if ("img".equals(el.tagName())) {
String src = el.attr("data-lazyload") // 懒加载图片
.defaultIfEmpty(el.attr("src"));
if (!src.isEmpty()) {
nodes.add(ContentNode.image(completeImageUrl(src)));
}
} else if ("p".equals(el.tagName())) {
String text = el.text().trim();
if (!text.isEmpty()) {
nodes.add(ContentNode.text(text));
}
}
}
desc.setContentNodes(nodes);
return desc;
}
/**
* 补全图片URL(京东图片有多种尺寸:n0/n1/n5等)
*/
private String completeImageUrl(String url) {
if (url.startsWith("//")) {
url = "https:" + url;
}
// 替换为高清图:n5是800*800,n1是350*350
return url.replace("/n1/", "/n5/").replace("/n0/", "/n5/");
}}五、性能优化与监控
5.1 缓存策略矩阵
| 数据类型 | 本地缓存 | Redis缓存 | 过期时间 | 更新策略 |
|---|---|---|---|---|
| 商品基础信息 | Caffeine | 是 | 1小时 | 被动过期 |
| 价格信息 | 否 | 是 | 5分钟 | 主动推送 |
| 库存状态 | Caffeine | 否 | 30秒 | 实时查询 |
| 详情页HTML | 是 | 是 | 24小时 | 版本号校验 |
5.2 监控指标
java
@Componentpublic class JdApiMetrics {
private final MeterRegistry registry;
public void recordApiCall(String apiName, boolean success, long durationMs) {
registry.counter("jd.api.calls",
"api", apiName,
"status", success ? "success" : "failure")
.increment();
registry.timer("jd.api.duration", "api", apiName)
.record(durationMs, TimeUnit.MILLISECONDS);
}
public void recordCacheHit(String cacheType) {
registry.counter("jd.cache.hit", "type", cacheType).increment();
}}六、完整代码仓库结构
plain
src/main/java/com/example/jdapi/
├── config/
│ ├── JdApiProperties.java # 配置类
│ └── CacheConfig.java # 缓存配置
├── controller/
│ └── ItemController.java # REST接口
├── service/
│ ├── JdItemService.java # 业务层
│ ├── ThirdPartyJdService.java # 第三方实现
│ └── JdDetailParser.java # HTML解析
├── client/
│ ├── JdSdkClient.java # 官方SDK封装
│ └── HttpApiClient.java # HTTP客户端
├── dto/
│ ├── JdItemDetailDTO.java # 数据传输对象
│ └── ApiResult.java # 通用响应
├── util/
│ ├── JdSignUtil.java # 签名工具
│ └── HmacUtil.java # HMAC加密
└── fallback/
└── JdApiFallback.java # 降级处理七、合规与风控建议
- 频率控制:官方API通常限制 1000次/分钟,第三方API按套餐计费
- 数据存储:商品图片URL可缓存,但详情内容需定期更新
- 隐私保护:不存储用户京东账号相关信息
- 版权合规:商品图片使用需遵守京东素材使用协议