前言
【亚马逊 SP-API 实战】Java 实现单体商品 Listing 创建 + 图片上传完整教程(亲测可用)-CSDN博客
在上一篇教程中,我们实现了单个亚马逊 Listing 的创建与图片上传 。但在实际运营中,变体商品(如不同颜色、尺寸) 才是主流形态。
本文基于亚马逊 SP-API v2021-08-01 ,使用 Java 语言实现变体商品全自动上架: ✅ 创建父商品(虚拟关联体) ✅ 批量创建子变体(颜色 / 尺寸) ✅ 每个变体独立上传图片 ✅ 完整错误处理 + 防限流机制
代码完全可直接运行,敏感信息已脱敏,适配北美站点(美国站)。
一、变体 Listing 核心原理
亚马逊变体商品必须遵循父子结构:
- 父商品(Parent):虚拟商品,无库存、无价格、不可购买,仅用于关联子变体
- 子商品(Child):真实可售商品,包含价格、库存、变体属性(颜色 / 尺寸)
- 变体主题(Variation Theme) :如
COLOR、SIZE,决定变体维度 - 关联关系 :子商品通过
parent_sku绑定父商品
二、核心技术栈
- 语言:Java 8+
- API:亚马逊 SP-API Listings Items v2021-08-01
- 授权:LWA Refresh Token 授权
- 接口:
putListingsItem:创建父子商品patchListingsItem:上传变体独立图片
三、完整代码解读(脱敏版)
1. 依赖(Maven)
<dependency>
<groupId>software.amazon.spapi</groupId>
<artifactId>spapi-sdk</artifactId>
<version>1.5.0</version>
</dependency>
2. 脱敏配置信息(必须替换)
所有密钥已脱敏加密,使用时替换为自己的真实信息:
java
运行
// ==================== 脱敏配置参数 ====================
private static final String CLIENT_ID = "amzn1.application-oa2-client.**************************";
private static final String CLIENT_SECRET = "amzn1.oa2-cs.v1.**************************************************";
private static final String REFRESH_TOKEN = "Atzr|IwEBI********************************************************************";
private static final String SELLER_ID = "A1AZHTIRR4Q7WJAY";
private static final String MARKETPLACE_ID = "ATVPDKIKX0DER"; // 美国站
3. 变体数据结构(模拟 Excel)
使用VariantData类封装每个变体的独立信息,可直接对接 Excel 导入:
private static class VariantData {
final String sku; // 变体SKU
final String color; // 颜色
final double price; // 价格
final String gtin; // UPC/EAN
final List<String> imageUrls; // 独立图片
}
批量变体数据配置(可扩展 N 个变体):
private static final List<VariantData> VARIANTS = Arrays.asList(
new VariantData("QB3V8GFA63-CHROME", "Polished Chrome", 99.99, "01234567890123", 图片列表),
new VariantData(...)
);
4. 主执行流程(三步曲)
public static void main(String[] args) {
// 1. LWA授权初始化
// 2. 创建父商品
createParentListing(...);
// 3. 批量创建子变体
for (VariantData v : VARIANTS) createChildVariant(...);
// 4. 每个变体上传独立图片
for (VariantData v : VARIANTS) uploadVariantImages(...);
}
执行顺序绝对不能乱! 必须:父商品 → 子变体 → 图片上传
5. 核心模块详解
(1)创建父商品(虚拟商品)
父商品关键属性:
// 标记为父商品
attributes.put("parentage_level", List.of(Map.of("value", "parent")));
// 变体主题:颜色
attributes.put("variation_theme", List.of(Map.of("name", "COLOR")));
⚠️ 父商品不能包含:价格、库存、GTIN、颜色、图片
(2)创建子变体(核心)
子商品必须关联父商品:
// 标记为子商品
attributes.put("parentage_level", List.of(Map.of("value", "child")));
// 绑定父SKU
attributes.put("child_parent_sku_relationship", List.of(
Map.of("parent_sku", PARENT_SKU, "variation_theme", "COLOR")
));
// 变体属性:颜色
attributes.put("color", List.of(Map.of("value", variant.color)));
每个变体拥有:
- 独立 SKU
- 独立价格
- 独立 GTIN
- 独立图片
(3)变体独立图片上传
使用PATCH接口为每个变体单独上传图片:
PatchOperation op = new PatchOperation();
op.setOp(REPLACE);
op.setPath("/attributes/main_product_image_locator");
op.setValue(图片地址);
✅ 支持:主图 + N 张附图 ✅ 每个变体图片互不干扰
(4)错误日志统一处理
private static void printIssues(响应结果) {
// 打印 ERROR/WARNING 信息
// 自动判断是否创建失败
}
四、代码运行效果(控制台输出)
>>> 步骤1:创建父商品...
✅ 父商品创建请求已提交!
>>> 步骤2:创建子变体商品...
--- 正在创建变体: Polished Chrome ---
✅ 变体创建成功
>>> 步骤3:上传各变体图片...
✅ 主图上传成功
✅ 附图上传成功
🎉 全部完成!
五、必看避坑指南(非常重要)
- 父商品不能有价格 / 库存 / Gtin,否则创建失败
- 变体主题必须一致(父 & 子都要写)
- 子商品必须明确关联 parent_sku
- 图片必须是 HTTPS 公开地址,亚马逊可直接下载
- 请求必须加延时,避免触发限流(代码已内置)
- 每个变体必须有唯一 GTIN,不能重复
- 类目必须支持变体(如厨房水龙头支持 COLOR 变体)
六、适用扩展场景
- 批量上架变体 Listing
- ERP 系统对接亚马逊变体商品
- 一键铺货工具核心模块
- 按 Excel 模板自动创建变体
七、总结
这是一套工业级可用的亚马逊变体 Listing 自动化创建方案: ✅ 父子商品结构标准 ✅ 变体独立图片 ✅ 批量处理 ✅ 错误完善 ✅ 可直接对接 Excel / 数据库
如果你做亚马逊 ERP、铺货工具、自动化运营,这份代码可以直接作为核心模块使用!
💡 原创不易,觉得有用欢迎点赞 + 收藏 + 关注,后续继续更新 SP-API 库存、订单、定价接口实战!
附:全部代码
import com.amazon.SellingPartnerAPIAA.LWAAuthorizationCredentials;
import com.amazon.SellingPartnerAPIAA.LWAException;
import software.amazon.spapi.ApiException;
import software.amazon.spapi.api.listings.items.v2021_08_01.ListingsApi;
import software.amazon.spapi.models.listings.items.v2021_08_01.ListingsItemPatchRequest;
import software.amazon.spapi.models.listings.items.v2021_08_01.ListingsItemPutRequest;
import software.amazon.spapi.models.listings.items.v2021_08_01.ListingsItemSubmissionResponse;
import software.amazon.spapi.models.listings.items.v2021_08_01.PatchOperation;
import java.util.*;
public class CreateListingVariants {
// ==================== 配置参数(使用你已验证过的凭证)====================
private static final String CLIENT_ID = "amzn1.application-oa2-client.d4099ee7a42f480d8a4ee2fa63c1597d26";
private static final String CLIENT_SECRET = "amzn1.oa2-cs.v1.a940db0aafee4c23d81fa1f1f933f6647d6adb5f2aafe1dsd992cf4a6829972992";
private static final String REFRESH_TOKEN = "Atzr|IwEBIJ6fz00AKqA5eU166lsnFAMuzcGnYRJN18Q0BpQ4Rx85VwViDVgWsdJO35FFhU7wkD_hjrM7PCugYbsXZobnpCYCmYnUGLs4C-UVFVSnvLP2wG028l8vn4EKyjxm2faK8vrCszwCPY8FU0V4X7ZdxWf-ugDSvvwoQiO-PFi81Q1OAiSSw6doluNuw8logWW4qMMQdy5Dc2aNBnFKLHRyLlG8503_LYqefpsbUpmJC8jYq0KBk9Q6IEFT0DhUaZQ_VQhVCwh51tIFPCVU5zR9-uoVDG3xpCu3ny-sVRzQkBz2I35FoHH7EtZksG7LUoZTl6Zi0";
private static final String SELLER_ID = "A1AZHTI4Q7WeJAY"; // 你的卖家ID
private static final String MARKETPLACE_ID = "ATVPDKIKX0DER"; // 美国站
// ==================== 变体配置(模拟Excel数据,写死)====================
// 父SKU(虚拟SKU,不单独售卖,仅用于关联子变体)
private static final String PARENT_SKU = "QB3V8GFA63-PARENT";
// 变体主题(颜色)
private static final String VARIATION_THEME = "COLOR";
// 变体数据列表(模拟Excel行数据)
private static final List<VariantData> VARIANTS = Arrays.asList(
new VariantData(
"QB3V8GFA63-CHROME", // SKU
"Polished Chrome", // 颜色
99.99, // 价格
"01234567890123", // GTIN
Arrays.asList( // 图片URLs
"https://m.media-amazon.com/images/I/51f0Hh4toYL._SL1500_.jpg",
"https://m.media-amazon.com/images/I/81WUIDw2vwL._SL1500_.jpg",
"https://m.media-amazon.com/images/I/71+VPo-TOuL._SL1500_.jpg"
)
),
new VariantData(
"QB3V8GFA63-MATTE-BLACK", // SKU
"Matte Black", // 颜色
109.99, // 价格
"01234567890124", // GTIN
Arrays.asList( // 图片URLs
"https://m.media-amazon.com/images/I/61aB+CDEFGH._SL1500_.jpg",
"https://m.media-amazon.com/images/I/82XYZ123456._SL1500_.jpg",
"https://m.media-amazon.com/images/I/73ABC789012._SL1500_.jpg"
)
),
new VariantData(
"QB3V8GFA63-BRUSHED-NICKEL", // SKU
"Brushed Nickel", // 颜色
119.99, // 价格
"01234567890125", // GTIN
Arrays.asList( // 图片URLs
"https://m.media-amazon.com/images/I/62DEF+GHIJK._SL1500_.jpg",
"https://m.media-amazon.com/images/I/83LMN456789._SL1500_.jpg",
"https://m.media-amazon.com/images/I/74OPQ345678._SL1500_.jpg"
)
),
new VariantData(
"QB3V8GFA63-OIL-RUBBED", // SKU
"Oil Rubbed Bronze", // 颜色
129.99, // 价格
"01234567890126", // GTIN
Arrays.asList( // 图片URLs
"https://m.media-amazon.com/images/I/63GHI+JKLMN._SL1500_.jpg",
"https://m.media-amazon.com/images/I/84RST567890._SL1500_.jpg",
"https://m.media-amazon.com/images/I/75UVW456789._SL1500_.jpg"
)
)
);
public static void main(String[] args) {
try {
// 1. 配置 LWA 凭证
LWAAuthorizationCredentials credentials = LWAAuthorizationCredentials.builder()
.clientId(CLIENT_ID)
.clientSecret(CLIENT_SECRET)
.refreshToken(REFRESH_TOKEN)
.endpoint("https://api.amazon.com/auth/o2/token")
.build();
// 2. 创建 ListingsApi 实例
ListingsApi listingsApi = new ListingsApi.Builder()
.lwaAuthorizationCredentials(credentials)
.endpoint("https://sellingpartnerapi-na.amazon.com")
.build();
List<String> marketplaceIds = Collections.singletonList(MARKETPLACE_ID);
// ========== 第1步:创建父商品(虚拟商品,不可购买)==========
System.out.println(">>> 步骤1:创建父商品(虚拟商品,用于关联变体)...");
createParentListing(listingsApi, marketplaceIds);
// 等待父商品创建完成
Thread.sleep(1000);
// ========== 第2步:创建所有子变体商品 ==========
System.out.println("\n>>> 步骤2:创建子变体商品...");
for (VariantData variant : VARIANTS) {
System.out.println("\n--- 正在创建变体: " + variant.color + " (SKU: " + variant.sku + ") ---");
createChildVariant(listingsApi, marketplaceIds, variant);
Thread.sleep(800); // 避免请求过快
}
// ========== 第3步:为每个变体上传图片 ==========
System.out.println("\n>>> 步骤3:上传各变体图片...");
for (VariantData variant : VARIANTS) {
System.out.println("\n--- 正在上传 " + variant.color + " 的图片 ---");
uploadVariantImages(listingsApi, marketplaceIds, variant);
Thread.sleep(800);
}
System.out.println("\n🎉 全部完成!父商品和所有变体已创建,图片已上传。");
} catch (ApiException e) {
System.err.println("❌ API 调用异常");
System.err.println("状态码: " + e.getCode());
System.err.println("响应体: " + e.getResponseBody());
e.printStackTrace();
} catch (Exception e) {
System.err.println("❌ 其他错误: " + e.getMessage());
e.printStackTrace();
}
}
/**
* 创建父商品(虚拟商品)
* 父商品不包含具体库存和价格,仅用于关联子变体
*/
private static void createParentListing(ListingsApi listingsApi, List<String> marketplaceIds) throws ApiException, LWAException {
Map<String, Object> attributes = buildParentAttributes();
ListingsItemPutRequest requestBody = new ListingsItemPutRequest();
requestBody.setProductType("KITCHEN");
requestBody.setRequirements(ListingsItemPutRequest.RequirementsEnum.LISTING);
requestBody.setAttributes(attributes);
ListingsItemSubmissionResponse response = listingsApi.putListingsItem(
requestBody,
SELLER_ID,
PARENT_SKU,
marketplaceIds,
null, null, null, null
);
System.out.println("✅ 父商品创建请求已提交!");
System.out.println("SubmissionId: " + response.getSubmissionId());
System.out.println("Status: " + response.getStatus());
printIssues(response);
}
/**
* 创建子变体商品
*/
private static void createChildVariant(ListingsApi listingsApi, List<String> marketplaceIds, VariantData variant) throws ApiException, LWAException {
Map<String, Object> attributes = buildChildAttributes(variant);
ListingsItemPutRequest requestBody = new ListingsItemPutRequest();
requestBody.setProductType("KITCHEN");
requestBody.setRequirements(ListingsItemPutRequest.RequirementsEnum.LISTING);
requestBody.setAttributes(attributes);
ListingsItemSubmissionResponse response = listingsApi.putListingsItem(
requestBody,
SELLER_ID,
variant.sku,
marketplaceIds,
null, null, null, null
);
System.out.println("✅ 变体 [" + variant.color + "] 创建请求已提交!");
System.out.println("SubmissionId: " + response.getSubmissionId());
System.out.println("Status: " + response.getStatus());
printIssues(response);
}
/**
* 为变体上传图片(使用 PATCH)
*/
private static void uploadVariantImages(ListingsApi listingsApi, List<String> marketplaceIds, VariantData variant) throws ApiException {
// 定义图片属性名
String[] imageAttributeNames = {
"main_product_image_locator",
"other_product_image_locator_1",
"other_product_image_locator_2"
};
for (int i = 0; i < variant.imageUrls.size(); i++) {
String imageUrl = variant.imageUrls.get(i);
String attributeName = imageAttributeNames[Math.min(i, imageAttributeNames.length - 1)];
boolean isMain = (i == 0);
try {
ListingsItemPatchRequest patchRequest = new ListingsItemPatchRequest();
patchRequest.setProductType("KITCHEN");
PatchOperation patchOp = new PatchOperation();
patchOp.setOp(PatchOperation.OpEnum.REPLACE);
patchOp.setPath("/attributes/" + attributeName);
List<Map<String, Object>> imageValues = Collections.singletonList(Map.of(
"marketplace_id", MARKETPLACE_ID,
"media_location", imageUrl
));
patchOp.setValue(imageValues);
patchRequest.setPatches(Collections.singletonList(patchOp));
ListingsItemSubmissionResponse patchResponse = listingsApi.patchListingsItem(
patchRequest,
SELLER_ID,
variant.sku,
marketplaceIds,
null, null, null, null
);
System.out.println(" ✅ " + (isMain ? "主图" : "附图" + i) + " 上传成功");
System.out.println(" URL: " + imageUrl);
System.out.println(" Status: " + patchResponse.getStatus());
printIssues(patchResponse);
Thread.sleep(500);
} catch (ApiException e) {
System.err.println(" ❌ " + (isMain ? "主图" : "附图" + i) + " 上传失败");
System.err.println(" 状态码: " + e.getCode());
System.err.println(" 响应: " + e.getResponseBody());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (LWAException e) {
throw new RuntimeException(e);
}
}
}
/**
* 构建父商品属性(虚拟商品)
*/
private static Map<String, Object> buildParentAttributes() {
Map<String, Object> attributes = new HashMap<>();
String language = "en_US";
String marketplace = MARKETPLACE_ID;
// 父商品标识:parentage_level = "parent"
attributes.put("parentage_level", List.of(Map.of("value", "parent")));
// 变体主题
attributes.put("variation_theme", List.of(Map.of("name", VARIATION_THEME)));
// 基础信息(父商品也需要,但不包含库存和价格)
attributes.put("item_name", List.of(
Map.of("value", "Stylish 22 Inch High-Arc Pull-Down Faucet with Spring for Commercial Kitchen Sinks",
"language_tag", language, "marketplace_id", marketplace)
));
attributes.put("product_description", List.of(
Map.of("value", "This stylish high-arc pull-down faucet features a commercial spring design...",
"language_tag", language)
));
attributes.put("brand", List.of(Map.of("value", "TGM", "language_tag", language)));
attributes.put("manufacturer", List.of(Map.of("value", "TGM", "language_tag", language)));
attributes.put("condition_type", List.of(Map.of("value", "new_new")));
attributes.put("item_type_keyword", List.of(Map.of("value", "kitchen-faucet")));
attributes.put("model_name", List.of(Map.of("value", "Kitchen Faucet Spring")));
// 父商品不需要:price, inventory, color, gtin, images等具体变体属性
// 1. 五点描述(Bullet Point)- 父商品必填
attributes.put("bullet_point", List.of(
Map.of("value", "Elegant and Functional Design: This 22-inch high-arc kitchen faucet features a sleek, single-handle design..."),
Map.of("value", "Premium Quality Construction: Crafted from 100% lead-free brass for all water-contact parts..."),
Map.of("value", "Enhanced Flow Rate: Experience the efficiency of a 1.8 GPM flow rate..."),
Map.of("value", "Long-Lasting Seal Valve: Engineered to international brand standards..."),
Map.of("value", "Versatile 3-Function Spray Head: Our pull-down sprayer offers three modes...")
));
// 2. 原产国(Country of Origin)- 父商品必填
attributes.put("country_of_origin", List.of(Map.of("value", "CN")));
// 3. 是否需要电池(Are Batteries Required?)- 父商品必填,水龙头不需要电池
attributes.put("batteries_required", List.of(
Map.of("value", false, "marketplace_id", marketplace)
));
// 危险品信息
attributes.put("supplier_declared_dg_hz_regulation", List.of(Map.of("value", "other")));
return attributes;
}
/**
* 构建子变体属性
*/
private static Map<String, Object> buildChildAttributes(VariantData variant) {
Map<String, Object> attributes = new HashMap<>();
String language = "en_US";
String marketplace = MARKETPLACE_ID;
// ========== 变体关系 ==========
// 子商品标识
attributes.put("parentage_level", List.of(Map.of("value", "child")));
// 关联到父SKU
attributes.put("child_parent_sku_relationship", List.of(
Map.of("parent_sku", PARENT_SKU, "variation_theme", VARIATION_THEME)
));
// ========== 变体主题 ==========
attributes.put("variation_theme", List.of(Map.of("name", VARIATION_THEME)));
// ========== 标识符 ==========
attributes.put("merchant_suggested_asin", List.of(Map.of("value", "QB3V8GFA63")));
attributes.put("externally_assigned_product_identifier", List.of(
Map.of("value", variant.gtin, "type", "GTIN")
));
// ========== 危险品信息 ==========
attributes.put("supplier_declared_dg_hz_regulation", List.of(Map.of("value", "other")));
// ========== 基础信息(通用部分) ==========
attributes.put("item_name", List.of(
Map.of("value", "Stylish 22 Inch High-Arc Pull-Down Faucet with Spring for Commercial Kitchen Sinks, " + variant.color,
"language_tag", language, "marketplace_id", marketplace)
));
attributes.put("product_description", List.of(
Map.of("value", "This stylish high-arc pull-down faucet features a commercial spring design...",
"language_tag", language)
));
attributes.put("brand", List.of(Map.of("value", "TGM", "language_tag", language)));
attributes.put("manufacturer", List.of(Map.of("value", "TGM", "language_tag", language)));
attributes.put("part_number", List.of(Map.of("value", variant.sku)));
attributes.put("condition_type", List.of(Map.of("value", "new_new")));
attributes.put("material", List.of(Map.of("value", "Brass")));
attributes.put("size", List.of(Map.of("value", "One Size")));
attributes.put("country_of_origin", List.of(Map.of("value", "CN")));
attributes.put("item_type_keyword", List.of(Map.of("value", "kitchen-faucet")));
attributes.put("model_name", List.of(Map.of("value", "Kitchen Faucet Spring")));
attributes.put("model_number", List.of(Map.of("value", variant.sku)));
attributes.put("care_instructions", List.of(Map.of("value", "Clean with soft cloth")));
attributes.put("included_components", List.of(Map.of("value", "Faucet, Installation Kit, Instructions")));
// ========== 变体特定属性:颜色 ==========
attributes.put("color", List.of(Map.of("value", variant.color)));
// ========== 布尔 / 数字 ==========
attributes.put("is_refurbished", List.of(Map.of("value", false)));
attributes.put("number_of_boxes", List.of(Map.of("value", 1)));
attributes.put("contains_liquid_contents", List.of(Map.of("value", false)));
attributes.put("number_of_items", List.of(Map.of("value", 1)));
// ========== 五点描述(通用) ==========
attributes.put("bullet_point", List.of(
Map.of("value", "Elegant and Functional Design: This 22-inch high-arc kitchen faucet features a sleek, single-handle design..."),
Map.of("value", "Premium Quality Construction: Crafted from 100% lead-free brass for all water-contact parts..."),
Map.of("value", "Enhanced Flow Rate: Experience the efficiency of a 1.8 GPM flow rate..."),
Map.of("value", "Long-Lasting Seal Valve: Engineered to international brand standards..."),
Map.of("value", "Versatile 3-Function Spray Head: Our pull-down sprayer offers three modes...")
));
// ========== 搜索关键词(通用) ==========
attributes.put("generic_keyword", List.of(
Map.of("value", "Farmhouse; Kitchen Faucet; 22 Inch; High Arc; Commercial; Spring Faucet; Pull Down Sprayer; Sink; " + variant.color + ";")
));
// ========== 变体特定:价格与库存 ==========
attributes.put("list_price", List.of(Map.of("value", variant.price, "currency_code", "USD")));
attributes.put("fulfillment_availability", List.of(
Map.of("fulfillment_channel_code", "DEFAULT", "quantity", 1)
));
// ========== 重量 ==========
attributes.put("item_weight", List.of(Map.of("value", 5.9, "unit", "pounds")));
return attributes;
}
/**
* 打印响应中的问题
*/
private static void printIssues(ListingsItemSubmissionResponse response) {
if (response.getIssues() != null && !response.getIssues().isEmpty()) {
boolean hasError = false;
for (var issue : response.getIssues()) {
if ("ERROR".equalsIgnoreCase(String.valueOf(issue.getSeverity()))) {
hasError = true;
}
System.out.println(" - [" + issue.getSeverity() + "] " + issue.getCode() + ": " + issue.getMessage());
}
if (hasError) {
System.out.println("❌ 请求包含错误!");
}
}
}
/**
* 变体数据内部类(模拟Excel行)
*/
private static class VariantData {
final String sku; // 变体SKU
final String color; // 颜色
final double price; // 价格
final String gtin; // GTIN/UPC
final List<String> imageUrls; // 图片URL列表
VariantData(String sku, String color, double price, String gtin, List<String> imageUrls) {
this.sku = sku;
this.color = color;
this.price = price;
this.gtin = gtin;
this.imageUrls = imageUrls;
}
}
}
