全部
常见问题
产品动态
精选推荐
功能建议

已处理 待处理 {{opt.name}}
已处理 待处理
分析中 已回复 待规划 {{opt.name}}
分析中 已回复 待规划
淘宝选品接口实战:从召回、详情、佣金到转链的 Java 接入笔记

管理 管理 编辑 删除

1. 先厘清:“淘宝选品接口”不是单个 API

做淘宝客/导购/内容电商选品,本质是一条链路:

  1. 候选召回:关键词、类目、榜单、活动物料、高佣/大额券筛选;
  2. 详情校验:标题、主图、价格、库存、店铺分、类目、服务标签;
  3. 收益评估:佣金率、券后价、补贴、定向计划、历史销量;
  4. 转链产出:长链/短链、淘口令、二合一券链接;
  5. 效果归因:点击、付款、结算、退款、渠道/会员/Relation ID。
  6. 官方常用接口大致分三类:
  • 导购/淘宝客选品:taobao.tbk.dg.material.optional 通用物料搜索,taobao.tbk.dg.material.optional.upgrade 升级版,taobao.tbk.dg.material.recommend 按物料/官方商品库召回,taobao.tbk.item.info.get 商品详情。官方文档把 material.optional 定义为“通用物料搜索API(导购)”,返回结果中含 total_results、result_list.map_data、券信息、佣金字段、类目与店铺字段等。 升级版接口在文档中标注为免费、无需用户授权,且收益信息放在 publish_info.income_info、价格促销放在 price_promotion_info。 taobao.tbk.item.info.get 则属于淘宝客公用物料信息查询。
  • 商家自用商品/库存:如果你选品是为了自己店铺运营而不是 CPS 推广,看 taobao.items.onsale.get、taobao.item.seller.get、taobao.items.inventory.get 这类需要店铺授权的接口;开放平台把交易/商品场景接口列在商品同步、订单同步等流程中。
  • 转化组件:taobao.tbk.tpwd.create、taobao.tbk.spread.get、taobao.tbk.dg.punish.order.get 之外的订单明细类接口等。核心原则:能走官方 API 就不要抓页面,不要逆向滑块,不要买黑产 Cookie。

2. 权限与凭证:选品系统的“地基”

接入流程通常是:注册开放平台/联盟账号 → 实名或企业认证 → 创建应用 → 选择类目与 API 权限组 → 提交业务场景 → 获取 app_key/app_secret → 淘宝客侧再维护推广位 adzone_id / pid。开放平台通用流程包括创建应用、获取 API 密钥、按需求申请接口权限并提交资料审核。

实践建议:

  • app_secret、联盟 pid、渠道 relation_id/special_id 只放服务端;前端、App、小程序永不直连签名。
  • 选品服务与转链服务拆库:选品库允许过期,点击转链必须实时。
  • 对 adzone_id 做多租户隔离;不要跨推广位混用链接。
  • 关注限流与权限变更:官方文档与控制台权限页是唯一事实来源,第三方博客只用于排错参考。

3. Java 公共参数与签名示例

下面示例采用 TOP 常见 sign_method=md5 规则:剔除空值与 sign,按键排序,secret + k1v1k2v2... + secret 后 MD5 大写。若你的应用后台开通的是 HMAC 类签名,按当前文档替换 sign() 实现即可;不要硬编码旧规则


import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.TreeMap;

public final class TopClient {
    private static final DateTimeFormatter TS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public static String md5TopSign(Map<String, String> params, String appSecret) {
        TreeMap<String, String> sorted = new TreeMap<>();
        params.forEach((k, v) -> {
            if (v != null && !v.isEmpty() && !"sign".equals(k)) sorted.put(k, v);
        });

        StringBuilder sb = new StringBuilder(appSecret);
        sorted.forEach((k, v) -> sb.append(k).append(v));
        sb.append(appSecret);

        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] dig = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
            StringBuilder hex = new StringBuilder();
            for (byte b : dig) hex.append(String.format("%02X", b));
            return hex.toString();
        } catch (Exception e) {
            throw new IllegalStateException("sign error", e);
        }
    }

    public static String hmacSha256Hex(Map<String, String> params, String appSecret) {
        TreeMap<String, String> sorted = new TreeMap<>();
        params.forEach((k, v) -> {
            if (v != null && !v.isEmpty() && !"sign".equals(k)) sorted.put(k, v);
        });
        StringBuilder sb = new StringBuilder();
        sorted.forEach((k, v) -> sb.append(k).append(v));

        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            byte[] raw = mac.doFinal(sb.toString().getBytes(StandardCharsets.UTF_8));
            StringBuilder hex = new StringBuilder();
            for (byte b : raw) hex.append(String.format("%02x", b));
            return hex.toString();
        } catch (Exception e) {
            throw new IllegalStateException("hmac sign error", e);
        }
    }

    public static Map<String, String> commonParams(String appKey, String method) {
        Map<String, String> p = new TreeMap<>();
        p.put("app_key", appKey);
        p.put("method", method);
        p.put("format", "json");
        p.put("v", "2.0");
        p.put("timestamp", LocalDateTime.now().format(TS));
        p.put("sign_method", "md5"); // 若后台配置 hmac,可切换 sign/hmacSha256Hex
        return p;
    }

    public static String form(Map<String, String> params) {
        StringBuilder sb = new StringBuilder();
        params.forEach((k, v) -> {
            if (v == null) return;
            if (sb.length() > 0) sb.append('&');
            sb.append(URLEncoder.encode(k, StandardCharsets.UTF_8))
              .append('=')
              .append(URLEncoder.encode(v, StandardCharsets.UTF_8));
        });
        return sb.toString();
    }
}

4. 选品召回:升级版物料搜索

升级版返回更贴近选品:收益、近 2 小时/当日推广销量、最终促销价、未来活动价、满减路径都在结构化字段里。官方示例中 publish_info.income_info.commission_rate 为比例乘 100 后的整数,如 55 表示 5.5%;two_hour_promotion_sales、daily_promotion_sales 可用于热度粗排;price_promotion_info.final_promotion_price 更接近用户到手价判断。


import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import okhttp3.*;

public class TbkSelection {
    private static final String GATEWAY = "https://eco.taobao.com/router/rest";
    private final OkHttpClient http = new OkHttpClient();
    private final String appKey;
    private final String appSecret;
    private final String adzoneId;

    public TbkSelection(String appKey, String appSecret, String adzoneId) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.adzoneId = adzoneId;
    }

    public String searchOptionalUpgrade(String keyword, long pageNo, long pageSize) throws IOException {
        Map<String, String> p = new TreeMap<>(TopClient.commonParams(appKey, "taobao.tbk.dg.material.optional.upgrade"));
        p.put("adzone_id", adzoneId);
        p.put("q", keyword);
        p.put("page_no", String.valueOf(pageNo));
        p.put("page_size", String.valueOf(pageSize));
        // 选品常用过滤:按需开启,不要一次堆满
        // p.put("has_coupon", "true");
        // p.put("sort", "tk_rate_des");     // 以文档枚举为准
        // p.put("start_price", "20");
        // p.put("end_price", "200");
        // p.put("start_tk_rate", "100");    // 如文档要求乘100,则1%传100
        // p.put("is_tmall", "true");
        // p.put("itemloc", "杭州");
        p.put("sign", TopClient.md5TopSign(p, appSecret));

        Request req = new Request.Builder()
                .url(GATEWAY)
                .post(RequestBody.create(TopClient.form(p), MediaType.get("application/x-www-form-urlencoded; charset=utf-8")))
                .build();

        try (Response resp = http.newCall(req).execute()) {
            String body = resp.body() == null ? "" : resp.body().string();
            if (!resp.isSuccessful()) throw new IOException("HTTP " + resp.code() + ": " + body);
            return body;
        }
    }
}

注意:不同接口对佣金/比例的缩放可能不同。旧版字段常见 commission_rate=1550表示15.5%,新版/升级版的 income_rate 示例直接给 5.50。入库前统一归一化为 Decimal commissionRate,不要在前端混用“1550 / 15.5 / 0.155”。

5. 详情校验与商品补充信息

taobao.tbk.item.info.get 适合做候选商品批量详情补齐;但不要把详情接口当成高并发实时库存源。 CPS 场景更重要的是“能否推广、券是否有效、当前到手价多少”。

java


public String itemInfoGet(String numIids, String fields) throws IOException {
    Map<String, String> p = new TreeMap<>(TopClient.commonParams(appKey, "taobao.tbk.item.info.get"));
    p.put("num_iids", numIids);     // 多个 id 用逗号,具体上限看文档
    p.put("fields", fields);        // 按文档指定返回字段,别 fields=*
    p.put("sign", TopClient.md5TopSign(p, appSecret));

    Request req = new Request.Builder()
            .url(GATEWAY)
            .post(RequestBody.create(TopClient.form(p), MediaType.get("application/x-www-form-urlencoded; charset=utf-8")))
            .build();
    try (Response resp = http.newCall(req).execute()) {
        String body = resp.body() == null ? "" : resp.body().string();
        if (!resp.isSuccessful()) throw new IOException("HTTP " + resp.code() + ": " + body);
        return body;
    }
}

6. 选品打分:别只按佣金率排序

建议把候选商品归一化成 SelectionCandidate 后打分:

java


public class SelectionCandidate {
    public String itemId;
    public String title;
    public String categoryName;
    public String shopTitle;
    public BigDecimal finalPrice;       // 到手价/预估促销价
    public BigDecimal commissionRate;   // 0.155 = 15.5%
    public BigDecimal commissionAmount; // 预估佣金,若接口返回
    public Long dailySales;
    public Long twoHourSales;
    public BigDecimal shopDsr;          // 注意部分接口用*5或差值表示,先归一
    public Boolean hasCoupon;
    public String couponInfo;
    public String clickUrl;
    public String couponShareUrl;
}

public class ScoreService {
    public double score(SelectionCandidate c) {
        // 示例权重:按业务调参;所有分子分母先做异常值截断
        double income = clamp(c.commissionAmount == null ? 0 : c.commissionAmount.doubleValue(), 0, 100);
        double hot = log1p(c.dailySales == null ? 0 : c.dailySales);
        double price = c.finalPrice == null ? 0 : clamp(100 - c.finalPrice.doubleValue(), 0, 100);
        double dsr = clamp(c.shopDsr == null ? 0 : c.shopDsr.doubleValue(), 0, 5) * 20;
        double couponBoost = Boolean.TRUE.equals(c.hasCoupon) ? 8 : 0;
        return 0.45 * income + 0.25 * hot + 0.15 * price + 0.10 * dsr + couponBoost;
    }

    private static double clamp(double v, double min, double max) { return Math.max(min, Math.min(max, v)); }
    private static double log1p(double v) { return Math.log1p(Math.max(0, v)) * 10; }
}

工程上要加三类护栏:
  • 新鲜度:券/价/佣金强时效字段短缓存 1–5 分钟;点击/下单链路实时取链。
  • 黑名单:退款率高、DSR 低、类目禁投、品牌词侵权、店招与实物不符候选直接过滤。
  • 实验桶:同关键词下保留 10% 流量探索低佣金但高转化商品,避免系统只推“高佣低质”。

7. 转链与数据合规

转链建议独立服务:入参 item_id/券 me/推广位/渠道标识,出参短链或淘口令;落库保留 relation_id/special_id、时间戳与版本号,便于归因。淘口令/短链本身也是敏感资源,不要在前端日志、埋点明文里完整打印。

合规红线:

  • 不绕过滑块、不逆向 App 签名、不抓取详情页增量;
  • 不用多账号轮询突破 QPS;遇到限流做退避与队列削峰;
  • 买家隐私字段最小化保存、加密存储、按 retention 清理;
  • API 已覆盖字段不重复爬虫化采集;竞品监控用授权数据或公开聚合服务并审查条款。

8. 常见坑位清单

  • Remote service error:先重试一次并退避;频繁出现查权限、QPS、参数枚举、签名时间偏移。
  • 佣金率单位混乱:统一存 decimal,入库前做 rate > 1 ? rate/100 : rate 这类归一要谨慎,最好按接口字段单独适配器。
  • 库存为 0:CPS 选品不等于商家实时库存;不要把 stock=0 当唯一下架依据,结合详情/活动状态。
  • 券面额误导:coupon_info=满299元减20元 对低客单无效,必须计算券后价门槛。
  • 返回字段缺失:多半是 fields 未指定、权限未开通或场景不支持;测试期全字段,上线收敛字段。
  • 第三方“超级搜索/高佣申请”封装:可作灰度补充,但核心链路要有官方回退与字段校验,避免上游改字段直接打穿。

9. 上线 Checklist

  • 权限:物料搜索/详情/转链权限组已开通,adzone_id 与推广位一致;
  • 签名:服务端生成;密钥 KMS/配置中心管理;时钟 NTP 同步;
  • 重试:仅对幂等查询重试;写操作与转链防重;
  • 缓存:候选 5–15 分钟,券价 1–5 分钟,点击转链不缓存;
  • 监控:按接口/错误码/耗时/比例单位异常监控;
  • 数据:字段版本化、原始报文抽样留档、归因可回放;
  • 合规:隐私加密、授权回调 HTTPS、权限最小化、调用审计。
  • 一句话总结:淘宝选品接口的核心不是“拿到商品列表”,而是把 召回—详情—收益—转链—归因 做成可回放、可降级、可审计的系统;代码可以薄,字段归一与合规治理必须厚。

如遇任何疑问或有进一步的需求,请随时与我私信或者评论联系。


{{voteData.voteSum}} 人已参与
支持
反对
请登录后查看

123c001fa85d 最后编辑于2026-09-17 11:33:23

快捷回复
{{replySubmitting ? '提交中...' : '回复'}}
{{replySubmitting ? '提交中...' : '回复'}}
回复({{post_count}}) {{!is_user ? '我的回复' :'全部回复'}}
排序 默认正序 回复倒序 点赞倒序

{{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level || item.bbs_level }}

作者 管理员 企业

{{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推荐': '推荐'}}
{{item.is_suggest == 1? '取消推荐': '推荐'}} 【已收集】
{{item.floor}}# 沙发 板凳 地板 {{item.floor}}# 【已收集】
{{item.user_info.title || '暂无简介'}}
附件

{{itemf.name}}

{{item.created_at}}  {{item.ip_address}}
打赏
已打赏¥{{item.reward_price}}
{{item.like_count}}
分享
{{item.showReply ? '取消回复' : '回复'}}
删除
{{replySubmitting ? '提交中...' : '回复'}}
{{replySubmitting ? '提交中...' : '回复'}}

{{itemc.user_info.nickname}}

{{itemc.user_name}}

回复 {{itemc.comment_user_info.nickname}}

附件

{{itemf.name}}

{{itemc.created_at}}
打赏
已打赏¥{{itemc.reward_price}}
{{itemc.like_count}}
{{itemc.showReply ? '取消回复' : '回复'}}
删除
{{replySubmitting ? '提交中...' : '回复'}}
{{replySubmitting ? '提交中...' : '回复'}}
收起 展开更多
查看更多
打赏
已打赏¥{{reward_price}}
41
{{like_count}}
{{collect_count}}
添加回复 ({{post_count}})

相关推荐

{{replySubmitting ? '提交中...' : '回复'}}
{{replySubmitting ? '提交中...' : '回复'}}
问题:
问题自动获取的帖子内容,不准确时需要手动修改. [获取答案]
答案:
提交
bug 需求 取 消 确 定
打赏金额
当前余额:¥{{rewardUserInfo.reward_price}}
{{item.price}}元
请输入 0.1-{{reward_max_price}} 范围内的数值
打赏成功
¥{{price}}
完成 确认打赏

微信登录/注册

{{ wechatLoginError }}
切换手机号登录

{{ bind_phone ? '绑定手机' : '手机登录'}}

{{codeText}}
切换微信登录/注册
暂不绑定
CRMEB客服
CRMEB咨询热线 400-8888-794

扫码领取产品资料

功能清单
思维导图
安装教程
CRMEB开源商城下载 源码下载 CRMEB帮助文档 帮助文档
返回顶部 返回顶部
CRMEB客服