淘宝选品接口实战:从召回、详情、佣金到转链的 Java 接入笔记
1. 先厘清:“淘宝选品接口”不是单个 API
做淘宝客/导购/内容电商选品,本质是一条链路:
- 候选召回:关键词、类目、榜单、活动物料、高佣/大额券筛选;
- 详情校验:标题、主图、价格、库存、店铺分、类目、服务标签;
- 收益评估:佣金率、券后价、补贴、定向计划、历史销量;
- 转链产出:长链/短链、淘口令、二合一券链接;
- 效果归因:点击、付款、结算、退款、渠道/会员/Relation ID。
官方常用接口大致分三类:
- 导购/淘宝客选品:
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则属于淘宝客公用物料信息查询。 - 转化组件:
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() 实现即可;不要硬编码旧规则。java
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 更接近用户到手价判断。java
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、权限最小化、调用审计。
一句话总结:淘宝选品接口的核心不是“拿到商品列表”,而是把 召回—详情—收益—转链—归因 做成可回放、可降级、可审计的系统;代码可以薄,字段归一与合规治理必须厚。
如遇任何疑问或有进一步的需求,请随时与我私信或者评论联系。