【亚马逊 SP-API 实战】Java 实现单体商品 Listing 创建 + 图片上传完整教程(亲测可用)

前言

在亚马逊卖家开发中,Listings Items API (v2021-08-01) 是核心接口之一,用于程序化创建 / 更新商品 Listing。本文基于Java 语言 ,结合亚马逊官方 SP-API SDK,实现商品 Listing 创建(基础信息)+ 图片批量上传完整流程,代码可直接复用,适配北美站点(美国站),同时对敏感密钥做脱敏处理,保障安全。

适用场景:批量上架商品、自动化 Listing 维护、ERP 系统对接亚马逊 SP-API 依赖:亚马逊 SP-API Java SDK、LWA 授权组件


一、核心知识点前置说明

  1. 授权方式 :使用亚马逊 LWA(Login with Amazon)授权,通过RefreshToken获取访问令牌,是 SP-API 标准授权方式
  2. 接口分工
    • putListingsItem:创建 / 全量更新商品 Listing 基础信息(标题、五点、价格、属性等)
    • patchListingsItem:增量更新商品信息(图片上传专用,不覆盖原有数据)
  3. 关键约束 :创建 Listing 时不能直接携带图片,必须分两步:先创建商品 → 再单独上传图片
  4. 站点适配 :代码默认美国站(ATVPDKIKX0DER),可修改MARKETPLACE_ID适配其他站点

二、完整代码解读(脱敏版)

1. 依赖导入(Maven)

首先在pom.xml中引入亚马逊 SP-API 官方依赖,这是调用接口的基础:

复制代码
   <dependency>
      <groupId>software.amazon.spapi</groupId>
      <artifactId>spapi-sdk</artifactId>
      <version>1.5.0</version>
    </dependency>

2. 代码整体结构

代码分为4 大核心模块,逻辑清晰,适合二次开发:

  • 配置参数模块:存储 SP-API 授权、卖家、站点信息(密钥已脱敏
  • 主流程模块:授权初始化 → 创建商品 → 校验结果 → 上传图片
  • 商品属性构建模块:封装 Listing 所有基础信息(标题、五点、价格等)
  • 图片上传模块:批量上传主图 + 附图,支持请求间隔防限流

3. 逐模块详细解读

(1)敏感配置参数(脱敏处理)

所有密钥、ID 均做脱敏加密,实际使用时替换为自己的信息即可:

复制代码
// ==================== 脱敏配置参数(请替换为自己的真实信息)====================
// LWA授权客户端ID(脱敏)
private static final String CLIENT_ID = "amzn1.application-oa2-client.**************************";
// LWA授权客户端密钥(脱敏)
private static final String CLIENT_SECRET = "amzn1.oa2-cs.v1.**************************************************";
// 刷新令牌(脱敏)
private static final String REFRESH_TOKEN = "Atzr|IwEBI*********************************************************************************************************";
// 卖家ID
private static final String SELLER_ID = "A1AZHTI4Q*****";
// 美国站市场ID(固定值)
private static final String MARKETPLACE_ID = "ATVPDKIKX0DER";
// 商品SKU(自定义)
private static final String SKU = "QB3V8**A63-1";

安全说明:绝对不要在生产环境明文存储密钥,建议配置到环境变量 / 配置中心。

(2)LWA 授权初始化

SP-API 所有接口都需要先完成 LWA 授权,这是调用的前提:

复制代码
// 1. 配置LWA授权凭证
LWAAuthorizationCredentials credentials = LWAAuthorizationCredentials.builder()
        .clientId(CLIENT_ID)            // 客户端ID
        .clientSecret(CLIENT_SECRET)    // 客户端密钥
        .refreshToken(REFRESH_TOKEN)    // 刷新令牌
        .endpoint("https://api.amazon.com/auth/o2/token") // LWA授权地址
        .build();

// 2. 创建ListingsApi实例(指定北美站点接口地址)
ListingsApi listingsApi = new ListingsApi.Builder()
        .lwaAuthorizationCredentials(credentials)
        .endpoint("https://sellingpartnerapi-na.amazon.com")
        .build();
  • 授权地址:全球通用https://api.amazon.com/auth/o2/token
  • 接口端点:北美站点na、欧洲站点eu、远东站点fe
(3)第一步:创建商品 Listing 基础信息

调用putListingsItem接口,提交商品核心属性,不含图片

复制代码
// 构建商品属性(标题、五点、价格、材质等)
Map<String, Object> attributes = buildProductAttributes();

// 组装请求体
ListingsItemPutRequest requestBody = new ListingsItemPutRequest();
requestBody.setProductType("KITCHEN"); // 商品类型(厨房类)
requestBody.setRequirements(ListingsItemPutRequest.RequirementsEnum.LISTING); // 基础Listing要求
requestBody.setAttributes(attributes); // 商品属性

// 调用创建接口
ListingsItemSubmissionResponse response = listingsApi.putListingsItem(
        requestBody, SELLER_ID, SKU, Collections.singletonList(MARKETPLACE_ID),
        null, null, null, null
);
  • 商品类型 :必须填写亚马逊后台认可的 Product Type(本文用厨房类KITCHEN
  • 结果校验:创建后自动检查是否有错误,有错误直接终止流程,避免无效图片上传
(4)核心:商品属性构建

buildProductAttributes()方法封装了Listing 所有必填 + 常用选填属性,直接复用:

  • 基础信息:标题、品牌、描述、型号、颜色、材质
  • 营销信息:五点描述、搜索关键词
  • 交易信息:价格、库存、重量
  • 合规信息:产地、危险品声明(厨房用品无需危险品认证)

重点:创建 Listing 时禁止添加图片属性,亚马逊接口不支持,必须单独上传!

(5)第二步:PATCH 方式上传图片

这是本文的核心亮点,专用patchListingsItem接口上传图片,支持主图 + 附图批量上传:

复制代码
// 定义图片(公开可访问的HTTPS地址,亚马逊必须能直接访问)
List<ImageInfo> images = Arrays.asList(
        new ImageInfo("主图HTTPS地址", "main_product_image_locator", true),
        new ImageInfo("附图1HTTPS地址", "other_product_image_locator_1", false),
        new ImageInfo("附图2HTTPS地址", "other_product_image_locator_2", false)
);

// 循环上传图片
for (ImageInfo img : images) {
    PatchOperation patchOp = new PatchOperation();
    patchOp.setOp(PatchOperation.OpEnum.REPLACE); // 增量更新
    patchOp.setPath("/attributes/" + img.attributeName); // 图片属性路径
    patchOp.setValue(Collections.singletonList(Map.of(
            "marketplace_id", MARKETPLACE_ID,
            "media_location", img.url
    )));
    // 调用PATCH接口上传
    listingsApi.patchListingsItem(...);
    Thread.sleep(500); // 间隔500ms,防接口限流
}

图片要求

  1. 必须是HTTPS 协议的公开访问地址
  2. 主图属性:main_product_image_locator
  3. 附图属性:other_product_image_locator_数字
  4. 图片格式:JPG/PNG,符合亚马逊图片规范
(6)自定义图片实体类

简化图片参数管理,代码更优雅:

复制代码
private static class ImageInfo {
    final String url;            // 图片地址
    final String attributeName;  // 亚马逊图片属性名
    final boolean isMain;        // 是否主图

    ImageInfo(String url, String attributeName, boolean isMain) {
        this.url = url;
        this.attributeName = attributeName;
        this.isMain = isMain;
    }
}

三、代码运行效果

执行main方法,控制台输出清晰的执行日志:

  1. 第一步:创建商品基本信息 → 返回提交 ID、状态
  2. 第二步:批量上传图片 → 主图 / 附图逐个上传成功
  3. 最终:全部完成,商品创建 + 图片上传完毕

错误处理

  • API 异常:打印状态码 + 响应体,快速定位问题(如属性错误、授权失败)
  • 业务错误:检查亚马逊返回的Issues,获取具体错误提示

四、常见问题避坑

  1. 创建 Listing 报错:图片属性不支持 → 解决方案:创建时不要加图片,必须用 PATCH 单独上传
  2. 图片上传失败:亚马逊无法访问图片地址 → 解决方案:使用公开 HTTPS 地址,禁止本地路径 / 私有链接
  3. 授权失败:401 Unauthorized → 解决方案:检查CLIENT_ID/CLIENT_SECRET/REFRESH_TOKEN是否正确
  4. 接口限流:429 Too Many Requests → 解决方案:保留代码中的Thread.sleep(500),降低请求频率
  5. 商品类型错误 → 解决方案:替换为自己类目对应的合法 Product Type

五、总结

本文提供的代码是亚马逊 SP-API Listing 管理的标准实战方案,具备以下优势:

  1. 完整可用:从授权到创建商品、上传图片,一站式实现
  2. 安全规范:敏感密钥脱敏,符合开发安全规范
  3. 易扩展:可轻松改造为批量上架工具,支持多 SKU、多类目
  4. 稳定可靠:严格遵循亚马逊接口规范,错误处理完善

如果你需要对接亚马逊 SP-API 进行商品管理,这份代码可以直接作为核心模块使用!


💡 原创不易,欢迎点赞 + 收藏 + 关注,后续更新更多亚马逊 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.ListingsItemPutRequest;
import software.amazon.spapi.models.listings.items.v2021_08_01.ListingsItemSubmissionResponse;
import software.amazon.spapi.models.listings.items.v2021_08_01.ListingsItemPatchRequest;
import software.amazon.spapi.models.listings.items.v2021_08_01.PatchOperation;

import java.util.*;

public class CreateListingPro {

    // ==================== 配置参数(使用你已验证过的凭证)====================
    private static final String CLIENT_ID = "amzn1.application-oa2-client.d4099ee7a42f480d8a42fa63c15297d26";
    private static final String CLIENT_SECRET = "amzn1.oa2-cs.v1.a940db0aafee4c23d81fa1f1f933f6647d6adb5f2aaf2e1d992cf4a6829972992";
    private static final String REFRESH_TOKEN = "Atzr|IwEBIJ6fz00AKqA5eU166lsnFAMuzcGnYRJN18Q0BpQ4Rx85VwViDVgWJO35FFhU7wkD_hjrM7PCugYbsXZobnpCYCmYnUGLs4C-UVFVSnvLP2wG028l8vn4EKyjxm2faK8vrCszwCPY8FU0V4X7ZdxWf-ugDSvvwoQiO-PFi81Q1OAiSSw6doluNuw8logWW4qMMQdy5Dc2aNBnFKLHRy2LlG8503_LYqefpsbUpmJC8jYq0KBk9Q6IEFT0DhUaZQ_VQhVCwh51tIFPCVU5zR9-uoVDG3xpCu3ny-sVRzQkBz2I35FoHH7EtZksG7LUoZTl6Zi0";
    private static final String SELLER_ID = "A1AZHTI4Q7W3AY";          // 你的卖家ID
    private static final String MARKETPLACE_ID = "ATVPDKIKX0DER";      // 美国站
    private static final String SKU = "QB345GFA63-1";                  // 从你的表格中选取

    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();

            // ========== 第1步:创建商品(不含图片)==========
            System.out.println(">>> 步骤1:创建商品基本信息...");
            Map<String, Object> attributes = buildProductAttributes();

            ListingsItemPutRequest requestBody = new ListingsItemPutRequest();
            requestBody.setProductType("KITCHEN");
            requestBody.setRequirements(ListingsItemPutRequest.RequirementsEnum.LISTING);
            requestBody.setAttributes(attributes);

            List<String> marketplaceIds = Collections.singletonList(MARKETPLACE_ID);

            ListingsItemSubmissionResponse response = listingsApi.putListingsItem(
                    requestBody,
                    SELLER_ID,
                    SKU,
                    marketplaceIds,
                    null, null, null, null
            );

            System.out.println("✅ 商品创建请求已提交!");
            System.out.println("SubmissionId: " + response.getSubmissionId());
            System.out.println("Status: " + response.getStatus());

            // 检查创建结果
            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("❌ 创建商品时有错误,停止上传图片。");
                    return;
                }
            }

            // ========== 第2步:上传图片(使用 PATCH)==========
            System.out.println("\n>>> 步骤2:上传商品图片...");
            uploadImages(listingsApi, credentials);

            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();
        }
    }

    /**
     * 上传图片 - 使用 Listings API 的 PATCH 方式
     * 参考:https://developer-docs.amazon.com/sp-api/docs/listings-items-api-v2021-08-01-reference#patch-listings2021-08-01itemsselleridsku
     */
    private static void uploadImages(ListingsApi listingsApi, LWAAuthorizationCredentials credentials) throws ApiException {
        List<String> marketplaceIds = Collections.singletonList(MARKETPLACE_ID);

        // 定义图片:主图 + 附图
        // 图片必须是可以公开访问的 HTTPS URL
        List<ImageInfo> images = Arrays.asList(
                new ImageInfo("https://m.media-amazon.com/images/I/51f0Hh4toYL._SL1500_.jpg", "main_product_image_locator", true),
                new ImageInfo("https://m.media-amazon.com/images/I/81WUIDw2vwL._SL1500_.jpg", "other_product_image_locator_1", false),
                new ImageInfo("https://m.media-amazon.com/images/I/71+VPo-TOuL._SL1500_.jpg", "other_product_image_locator_2", false)
        );

        for (ImageInfo img : images) {
            try {
                // 构建 PATCH 请求体
                ListingsItemPatchRequest patchRequest = new ListingsItemPatchRequest();
                patchRequest.setProductType("KITCHEN");

                // 构建 patch 操作
                PatchOperation patchOp = new PatchOperation();
                patchOp.setOp(PatchOperation.OpEnum.REPLACE);
                patchOp.setPath("/attributes/" + img.attributeName);

                // 图片属性值结构
                List<Map<String, Object>> imageValues = Collections.singletonList(Map.of(
                        "marketplace_id", MARKETPLACE_ID,
                        "media_location", img.url
                ));
                patchOp.setValue(imageValues);

                patchRequest.setPatches(Collections.singletonList(patchOp));

                // 执行 PATCH
                ListingsItemSubmissionResponse patchResponse = listingsApi.patchListingsItem(
                        patchRequest,    // body
                        SELLER_ID,       // sellerId
                        SKU,             // sku
                        marketplaceIds,  // marketplaceIds
                        null,            // includedData (可选)
                        null,            // mode (可选)
                        null,            // issueLocale (可选)
                        null             // restrictedDataToken (可选)
                );

                System.out.println("  ✅ " + (img.isMain ? "主图" : "附图") + " 上传成功");
                System.out.println("     URL: " + img.url);
                System.out.println("     Status: " + patchResponse.getStatus());

                // 检查是否有问题
                if (patchResponse.getIssues() != null && !patchResponse.getIssues().isEmpty()) {
                    for (var issue : patchResponse.getIssues()) {
                        System.out.println("     ⚠️ [" + issue.getSeverity() + "] " + issue.getMessage());
                    }
                }

                // 避免请求过快,间隔 500ms
                Thread.sleep(500);

            } catch (ApiException e) {
                System.err.println("  ❌ " + (img.isMain ? "主图" : "附图") + " 上传失败");
                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 class ImageInfo {
        final String url;
        final String attributeName;
        final boolean isMain;

        ImageInfo(String url, String attributeName, boolean isMain) {
            this.url = url;
            this.attributeName = attributeName;
            this.isMain = isMain;
        }
    }

    private static Map<String, Object> buildProductAttributes() {
        Map<String, Object> attributes = new HashMap<>();

        String language = "en_US";
        String marketplace = MARKETPLACE_ID;

        // ==================== 1. 标识符 ====================
        attributes.put("merchant_suggested_asin", List.of(Map.of("value", "QB3V01")));

        // 外部产品 ID(GTIN/UPC/EAN)- 请替换为真实有效的 GTIN
        attributes.put("externally_assigned_product_identifier", List.of(
                Map.of("value", "01234567890123", "type", "GTIN")
        ));

        // ==================== 2. 危险品信息 ====================
        // 厨房水龙头通常不需要危险品声明,删除此行或保留正确值
        // attributes.put("supplier_declared_dg_hz_regulation", List.of(Map.of("value", "not_applicable")));
        attributes.put("supplier_declared_dg_hz_regulation", List.of(Map.of("value", "other")));
        // ==================== 3. 基础信息 ====================
        attributes.put("item_name", List.of(
                Map.of("value", "Stylish 22 Inch High-Arc Pull-Down Faucet with Spring for Commercial Kitchen Sinks, Polished Chrome",
                        "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", "QB3V8GFA69-2")));
        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("color", List.of(Map.of("value", "Polished Chrome")));
        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", "QB3V8GFA69-2")));
        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("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)));

        // ==================== 4. 五点描述 ====================
        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...")
        ));

        // ==================== 5. 搜索关键词 ====================
        attributes.put("generic_keyword", List.of(
                Map.of("value", "Farmhouse; Kitchen Faucet; 22 Inch; High Arc; Commercial; Spring Faucet; Pull Down Sprayer; Sink; Polished Chrome;")
        ));

        // ==================== 6. 价格与库存 ====================
        attributes.put("list_price", List.of(Map.of("value", 99.99, "currency_code", "USD")));
        attributes.put("fulfillment_availability", List.of(
                Map.of("fulfillment_channel_code", "DEFAULT", "quantity", 1)
        ));

        // ==================== 7. 重量 ====================
        attributes.put("item_weight", List.of(Map.of("value", 5.9, "unit", "pounds")));

        // ==================== 8. 图片(已移除,改用 PATCH 上传)====================
        // 注意:Listings API putListingsItem 不支持直接传 images 属性
        // 图片通过 patchListingsItem 单独上传

        return attributes;
    }
}
相关推荐
清水白石0081 小时前
Python 可变对象与不可变对象深度解析:为什么 `tuple` 里可以放 `list`?
开发语言·python·list
SWAGGY..1 小时前
【C++初阶】:(11)list的功能介绍&&list迭代器模拟实现
开发语言·c++
不会C语言的男孩2 小时前
C++ Primer 第3章:字符串、向量和数组
开发语言·c++
兰令水2 小时前
leecodecode【反前后指针】【2026.5.31打卡-java版本】
java·开发语言
Dovis(誓平步青云)3 小时前
《QT学习第四篇:常见事件与UDP、TCP、文件系统、(锁、信号量、条件变量》
c语言·开发语言·汇编·qt
isyangli_blog11 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb20081112 小时前
FastAPI APIRouter
开发语言·python
Benszen12 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆12 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang