淘宝商品类目体系是电商平台最核心的基础数据结构之一,它决定了商品的展示方式、属性规则、搜索推荐逻辑以及发布规范。本文将系统性地介绍淘宝开放平台提供的类目相关 API 接口,涵盖类目查询、属性获取、类目树构建等核心能力,并提供完整的 Java 代码实现。
一、接口体系概览
淘宝开放平台提供了多个类目相关接口,各司其职,共同构成完整的类目数据体系:
| 接口名称 | 核心功能 | 适用场景 |
|---|---|---|
taobao.itemcats.get | 批量获取商品类目列表 | 构建类目树、全量同步 |
taobao.item.cat.get | 获取单个类目详情 | 查询特定类目信息 |
taobao.itemprops.get | 获取类目属性列表 | 商品发布前的属性规则获取 |
taobao.itempropvalues.get | 获取类目属性值列表 | 获取属性可选值 |
taobao.itemcats.authorize.get | 获取商家授权类目和品牌 | 天猫商家发布商品前校验 |
关键概念:淘宝商品必须挂靠在叶子类目(
is_parent=false)下发布,叶子类目是指没有子类目的最末级类目。
二、核心接口详解
2.1 taobao.itemcats.get — 批量获取类目列表
这是构建完整类目树的核心接口,支持通过parent_cid递归获取子类目。
请求参数
| 参数名 | 类型 | 必选 | 说明 |
|---|---|---|---|
parent_cid | Number | 条件 | 父类目 ID,0表示根节点,获取一级类目 |
cids | Number[] | 条件 | 类目 ID 列表,逗号分隔(与 parent_cid 至少传一个) |
fields | String[] | 否 | 返回字段,默认cid,parent_cid,name,is_parent |
datetime | Date | 否 | 时间戳,用于获取增量变更(暂无法使用) |
响应字段
| 字段名 | 类型 | 说明 |
|---|---|---|
cid | Number | 类目唯一 ID |
parent_cid | Number | 父类目 ID,0为根类目 |
name | String | 类目名称 |
is_parent | Boolean | 是否为父类目(有子类目则为 true) |
status | String | 状态:normal(正常)、deleted(删除) |
sort_order | Number | 排序值 |
features | Feature[] | 类目特性,如freeze表示冻结状态 |
2.2 taobao.item.cat.get — 获取单个类目详情
用于查询特定类目的详细信息。
请求参数
| 参数名 | 类型 | 必选 | 说明 |
|---|---|---|---|
cid | Number | 是 | 类目 ID |
响应示例
{ "item_cat_get_response": { "item_cat": { "cid": 50008163, "parent_cid": 16, "name": "女装/女士精品", "is_parent": true, "status": "normal", "sort_order": 1, "features": [ { "attr_key": "cat_name", "attr_value": "女装" } ] }, "request_id": "4e9p1x9z8z8z8z8z8z8z8z8z" } }2.3 taobao.itemprops.get — 获取类目属性
商品发布前必须获取目标类目的属性规则,了解哪些字段必填、哪些是枚举值。
请求参数
| 参数名 | 类型 | 必选 | 说明 |
|---|---|---|---|
cid | Number | 是 | 叶子类目 ID |
type | Number | 否 | 1=淘宝属性,2=天猫属性 |
pid | Number | 否 | 属性 ID,指定则只返回该属性 |
fields | String | 否 | 返回字段 |
属性类型说明
| 属性类型 | 说明 | 示例 |
|---|---|---|
| 关键属性 | 品牌、型号等核心标识 | 品牌=Apple,型号=iPhone 15 |
| 商品属性 | 标准属性字段 | 材质、颜色、尺寸 |
| 绑定属性 | 与 SPU 相关的属性 | 产品库绑定属性 |
| 销售属性 | 影响 SKU 拼接的属性 | 颜色、尺码(不同组合生成不同 SKU) |
三、完整 Java 实现
3.1 核心客户端封装
import okhttp3.*; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.TimeUnit; /** * 淘宝开放平台类目 API Java 客户端 */ public class TaobaoCategoryClient { private static final String GATEWAY_URL = "https://eco.taobao.com/router/rest"; private static final String APP_KEY = "your_app_key"; private static final String APP_SECRET = "your_app_secret"; private final OkHttpClient httpClient; public TaobaoCategoryClient() { this.httpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .build(); } // ==================== 签名生成 ==================== /** * 生成 HMAC-MD5 签名 */ public String generateSign(Map<String, String> params) throws Exception { // 1. 过滤空值,排除 sign 和 sign_method List<String> sortedKeys = params.entrySet().stream() .filter(e -> e.getValue() != null && !e.getValue().isEmpty()) .filter(e -> !e.getKey().equals("sign") && !e.getKey().equals("sign_method")) .map(Map.Entry::getKey) .sorted() .toList(); // 2. 拼接字符串:AppSecret + key1value1 + key2value2 + ... + AppSecret StringBuilder signStr = new StringBuilder(APP_SECRET); for (String key : sortedKeys) { signStr.append(key).append(params.get(key)); } signStr.append(APP_SECRET); // 3. HMAC-MD5 加密 Mac mac = Mac.getInstance("HmacMD5"); SecretKeySpec secretKey = new SecretKeySpec( APP_SECRET.getBytes(StandardCharsets.UTF_8), "HmacMD5" ); mac.init(secretKey); byte[] bytes = mac.doFinal(signStr.toString().getBytes(StandardCharsets.UTF_8)); // 4. 转十六进制大写 StringBuilder hexString = new StringBuilder(); for (byte b : bytes) { String hex = Integer.toHexString(b & 0xFF); if (hex.length() == 1) hexString.append("0"); hexString.append(hex); } return hexString.toString().toUpperCase(); } // ==================== 公共请求方法 ==================== /** * 执行 API 请求 */ private JSONObject executeRequest(Map<String, String> params) throws Exception { // 添加公共参数 params.put("app_key", APP_KEY); params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); params.put("format", "json"); params.put("v", "2.0"); params.put("sign_method", "hmac-md5"); // 生成签名 params.put("sign", generateSign(params)); // 构建表单请求 FormBody.Builder formBuilder = new FormBody.Builder(); for (Map.Entry<String, String> entry : params.entrySet()) { formBuilder.add(entry.getKey(), entry.getValue()); } Request request = new Request.Builder() .url(GATEWAY_URL) .post(formBuilder.build()) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("HTTP 请求失败: " + response.code()); } return JSON.parseObject(response.body().string()); } } // ==================== 类目查询接口 ==================== /** * 获取指定父类目下的子类目列表 * * @param parentCid 父类目 ID,0 表示获取一级类目 * @param fields 返回字段 */ public List<Category> getItemCats(Long parentCid, String fields) throws Exception { Map<String, String> params = new HashMap<>(); params.put("method", "taobao.itemcats.get"); params.put("parent_cid", String.valueOf(parentCid)); if (fields != null && !fields.isEmpty()) { params.put("fields", fields); } JSONObject response = executeRequest(params); // 检查错误 if (response.containsKey("error_response")) { JSONObject error = response.getJSONObject("error_response"); throw new RuntimeException( "API错误: [" + error.getString("code") + "] " + error.getString("msg") ); } // 解析类目列表 JSONObject itemcatsResponse = response.getJSONObject("itemcats_get_response"); JSONArray itemCats = itemcatsResponse.getJSONArray("item_cats"); List<Category> categories = new ArrayList<>(); if (itemCats != null) { for (int i = 0; i < itemCats.size(); i++) { JSONObject cat = itemCats.getJSONObject(i); categories.add(parseCategory(cat)); } } return categories; } /** * 获取单个类目详情 */ public Category getItemCat(Long cid) throws Exception { Map<String, String> params = new HashMap<>(); params.put("method", "taobao.item.cat.get"); params.put("cid", String.valueOf(cid)); JSONObject response = executeRequest(params); if (response.containsKey("error_response")) { JSONObject error = response.getJSONObject("error_response"); throw new RuntimeException( "API错误: [" + error.getString("code") + "] " + error.getString("msg") ); } JSONObject catResponse = response.getJSONObject("item_cat_get_response"); JSONObject itemCat = catResponse.getJSONObject("item_cat"); return parseCategory(itemCat); } /** * 获取类目属性列表 */ public List<ItemProp> getItemProps(Long cid, Long type) throws Exception { Map<String, String> params = new HashMap<>(); params.put("method", "taobao.itemprops.get"); params.put("cid", String.valueOf(cid)); if (type != null) { params.put("type", String.valueOf(type)); // 1=淘宝, 2=天猫 } JSONObject response = executeRequest(params); if (response.containsKey("error_response")) { JSONObject error = response.getJSONObject("error_response"); throw new RuntimeException( "API错误: [" + error.getString("code") + "] " + error.getString("msg") ); } JSONObject propsResponse = response.getJSONObject("itemprops_get_response"); JSONArray itemProps = propsResponse.getJSONArray("item_props"); List<ItemProp> props = new ArrayList<>(); if (itemProps != null) { for (int i = 0; i < itemProps.size(); i++) { JSONObject prop = itemProps.getJSONObject(i); props.add(parseItemProp(prop)); } } return props; } // ==================== 数据解析 ==================== private Category parseCategory(JSONObject json) { Category category = new Category(); category.setCid(json.getLong("cid")); category.setParentCid(json.getLong("parent_cid")); category.setName(json.getString("name")); category.setParent(json.getBooleanValue("is_parent")); category.setStatus(json.getString("status")); category.setSortOrder(json.getIntValue("sort_order")); // 解析 features JSONArray features = json.getJSONArray("features"); if (features != null) { Map<String, String> featureMap = new HashMap<>(); for (int i = 0; i < features.size(); i++) { JSONObject f = features.getJSONObject(i); featureMap.put(f.getString("attr_key"), f.getString("attr_value")); } category.setFeatures(featureMap); } return category; } private ItemProp parseItemProp(JSONObject json) { ItemProp prop = new ItemProp(); prop.setPid(json.getLong("pid")); prop.setName(json.getString("name")); prop.setIsKeyProp(json.getBooleanValue("is_key_prop")); prop.setIsSaleProp(json.getBooleanValue("is_sale_prop")); prop.setIsColorProp(json.getBooleanValue("is_color_prop")); prop.setIsEnumProp(json.getBooleanValue("is_enum_prop")); prop.setIsInputProp(json.getBooleanValue("is_input_prop")); prop.setIsItemProp(json.getBooleanValue("is_item_prop")); prop.setMust(json.getBooleanValue("must")); prop.setMulti(json.getBooleanValue("multi")); prop.setStatus(json.getString("status")); prop.setSortOrder(json.getIntValue("sort_order")); // 解析属性值 JSONArray propValues = json.getJSONArray("prop_values"); if (propValues != null) { List<PropValue> values = new ArrayList<>(); for (int i = 0; i < propValues.size(); i++) { JSONObject v = propValues.getJSONObject(i); PropValue pv = new PropValue(); pv.setVid(v.getLong("vid")); pv.setName(v.getString("name")); pv.setNameAlias(v.getString("name_alias")); pv.setStatus(v.getString("status")); pv.setSortOrder(v.getIntValue("sort_order")); values.add(pv); } prop.setPropValues(values); } return prop; } // ==================== 数据模型 ==================== public static class Category { private Long cid; private Long parentCid; private String name; private boolean isParent; private String status; private int sortOrder; private Map<String, String> features; // Getters & Setters public Long getCid() { return cid; } public void setCid(Long cid) { this.cid = cid; } public Long getParentCid() { return parentCid; } public void setParentCid(Long parentCid) { this.parentCid = parentCid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isParent() { return isParent; } public void setParent(boolean parent) { isParent = parent; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public int getSortOrder() { return sortOrder; } public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; } public Map<String, String> getFeatures() { return features; } public void setFeatures(Map<String, String> features) { this.features = features; } @Override public String toString() { return String.format("Category{cid=%d, name='%s', parentCid=%d, isParent=%s}", cid, name, parentCid, isParent); } } public static class ItemProp { private Long pid; private String name; private boolean isKeyProp; // 是否关键属性 private boolean isSaleProp; // 是否销售属性 private boolean isColorProp; // 是否颜色属性 private boolean isEnumProp; // 是否枚举属性 private boolean isInputProp; // 是否可输入属性 private boolean isItemProp; // 是否商品属性 private boolean must; // 是否必填 private boolean multi; // 是否多选 private String status; private int sortOrder; private List<PropValue> propValues; // Getters & Setters public Long getPid() { return pid; } public void setPid(Long pid) { this.pid = pid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isKeyProp() { return isKeyProp; } public void setIsKeyProp(boolean keyProp) { isKeyProp = keyProp; } public boolean isSaleProp() { return isSaleProp; } public void setIsSaleProp(boolean saleProp) { isSaleProp = saleProp; } public boolean isColorProp() { return isColorProp; } public void setIsColorProp(boolean colorProp) { isColorProp = colorProp; } public boolean isEnumProp() { return isEnumProp; } public void setIsEnumProp(boolean enumProp) { isEnumProp = enumProp; } public boolean isInputProp() { return isInputProp; } public void setIsInputProp(boolean inputProp) { isInputProp = inputProp; } public boolean isItemProp() { return isItemProp; } public void setIsItemProp(boolean itemProp) { isItemProp = itemProp; } public boolean isMust() { return must; } public void setMust(boolean must) { this.must = must; } public boolean isMulti() { return multi; } public void setMulti(boolean multi) { this.multi = multi; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public int getSortOrder() { return sortOrder; } public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; } public List<PropValue> getPropValues() { return propValues; } public void setPropValues(List<PropValue> propValues) { this.propValues = propValues; } } public static class PropValue { private Long vid; private String name; private String nameAlias; private String status; private int sortOrder; // Getters & Setters public Long getVid() { return vid; } public void setVid(Long vid) { this.vid = vid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNameAlias() { return nameAlias; } public void setNameAlias(String nameAlias) { this.nameAlias = nameAlias; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public int getSortOrder() { return sortOrder; } public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; } } }四、递归构建完整类目树
import java.util.*; /** * 类目树构建器 */ public class CategoryTreeBuilder { private final TaobaoCategoryClient client; public CategoryTreeBuilder(TaobaoCategoryClient client) { this.client = client; } /** * 递归构建完整类目树 */ public CategoryNode buildCategoryTree() throws Exception { CategoryNode root = new CategoryNode(0L, "根类目", 0); buildChildren(root); return root; } private void buildChildren(CategoryNode parentNode) throws Exception { List<TaobaoCategoryClient.Category> children = client.getItemCats(parentNode.getCid(), "cid,parent_cid,name,is_parent,status,sort_order"); for (TaobaoCategoryClient.Category cat : children) { CategoryNode childNode = new CategoryNode( cat.getCid(), cat.getName(), parentNode.getLevel() + 1 ); childNode.setParentCid(cat.getParentCid()); childNode.setStatus(cat.getStatus()); childNode.setSortOrder(cat.getSortOrder()); childNode.setLeaf(!cat.isParent()); parentNode.addChild(childNode); // 递归获取子类目 if (cat.isParent()) { buildChildren(childNode); } } } /** * 查找叶子类目 */ public List<CategoryNode> findLeafNodes(CategoryNode root) { List<CategoryNode> leaves = new ArrayList<>(); collectLeaves(root, leaves); return leaves; } private void collectLeaves(CategoryNode node, List<CategoryNode> leaves) { if (node.isLeaf()) { leaves.add(node); return; } for (CategoryNode child : node.getChildren()) { collectLeaves(child, leaves); } } /** * 根据 CID 查找类目节点 */ public CategoryNode findByCid(CategoryNode root, Long cid) { if (root.getCid().equals(cid)) return root; for (CategoryNode child : root.getChildren()) { CategoryNode found = findByCid(child, cid); if (found != null) return found; } return null; } // ==================== 类目树节点模型 ==================== public static class CategoryNode { private Long cid; private String name; private Long parentCid; private int level; private String status; private int sortOrder; private boolean isLeaf; private List<CategoryNode> children = new ArrayList<>(); public CategoryNode(Long cid, String name, int level) { this.cid = cid; this.name = name; this.level = level; } public void addChild(CategoryNode child) { children.add(child); } // Getters & Setters public Long getCid() { return cid; } public void setCid(Long cid) { this.cid = cid; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Long getParentCid() { return parentCid; } public void setParentCid(Long parentCid) { this.parentCid = parentCid; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public int getSortOrder() { return sortOrder; } public void setSortOrder(int sortOrder) { this.sortOrder = sortOrder; } public boolean isLeaf() { return isLeaf; } public void setLeaf(boolean leaf) { isLeaf = leaf; } public List<CategoryNode> getChildren() { return children; } public void setChildren(List<CategoryNode> children) { this.children = children; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < level; i++) sb.append(" "); sb.append(String.format("[%d] %s (cid=%d, leaf=%s)", level, name, cid, isLeaf)); return sb.toString(); } } // ==================== 使用示例 ==================== public static void main(String[] args) { try { TaobaoCategoryClient client = new TaobaoCategoryClient(); CategoryTreeBuilder builder = new CategoryTreeBuilder(client); // 构建完整类目树 System.out.println("正在构建类目树..."); CategoryNode root = builder.buildCategoryTree(); // 打印类目树(前三级) printTree(root, 3); // 统计叶子类目数量 List<CategoryNode> leaves = builder.findLeafNodes(root); System.out.println("\n叶子类目总数: " + leaves.size()); // 查找特定类目 CategoryNode dressNode = builder.findByCid(root, 50008163L); if (dressNode != null) { System.out.println("\n找到类目: " + dressNode.getName()); System.out.println("层级: " + dressNode.getLevel()); System.out.println("是否叶子: " + dressNode.isLeaf()); } } catch (Exception e) { e.printStackTrace(); } } private static void printTree(CategoryNode node, int maxLevel) { if (node.getLevel() > maxLevel) return; System.out.println(node); for (CategoryNode child : node.getChildren()) { printTree(child, maxLevel); } } }五、商品发布前的类目属性校验
/** * 商品发布前的类目属性校验工具 */ public class CategoryPublishValidator { private final TaobaoCategoryClient client; public CategoryPublishValidator(TaobaoCategoryClient client) { this.client = client; } /** * 校验类目是否可以发布商品 */ public PublishValidationResult validateCategoryForPublish(Long cid, boolean isTmall) throws Exception { PublishValidationResult result = new PublishValidationResult(); // 1. 获取类目详情 TaobaoCategoryClient.Category category = client.getItemCat(cid); result.setCategory(category); // 2. 检查是否为叶子类目 if (category.isParent()) { result.addError("类目不是叶子类目,商品必须挂靠在叶子类目下"); return result; } // 3. 检查类目状态 if (!"normal".equals(category.getStatus())) { result.addError("类目状态异常: " + category.getStatus()); return result; } // 4. 获取类目属性 Long type = isTmall ? 2L : 1L; List<TaobaoCategoryClient.ItemProp> props = client.getItemProps(cid, type); result.setProperties(props); // 5. 分析必填属性 List<TaobaoCategoryClient.ItemProp> requiredProps = props.stream() .filter(TaobaoCategoryClient.ItemProp::isMust) .toList(); result.setRequiredProperties(requiredProps); // 6. 分析销售属性(影响 SKU) List<TaobaoCategoryClient.ItemProp> saleProps = props.stream() .filter(TaobaoCategoryClient.ItemProp::isSaleProp) .toList(); result.setSaleProperties(saleProps); result.setValid(result.getErrors().isEmpty()); return result; } public static class PublishValidationResult { private boolean valid; private TaobaoCategoryClient.Category category; private List<TaobaoCategoryClient.ItemProp> properties; private List<TaobaoCategoryClient.ItemProp> requiredProperties; private List<TaobaoCategoryClient.ItemProp> saleProperties; private List<String> errors = new ArrayList<>(); public void addError(String error) { errors.add(error); valid = false; } // Getters & Setters public boolean isValid() { return valid; } public void setValid(boolean valid) { this.valid = valid; } public TaobaoCategoryClient.Category getCategory() { return category; } public void setCategory(TaobaoCategoryClient.Category category) { this.category = category; } public List<TaobaoCategoryClient.ItemProp> getProperties() { return properties; } public void setProperties(List<TaobaoCategoryClient.ItemProp> properties) { this.properties = properties; } public List<TaobaoCategoryClient.ItemProp> getRequiredProperties() { return requiredProperties; } public void setRequiredProperties(List<TaobaoCategoryClient.ItemProp> requiredProperties) { this.requiredProperties = requiredProperties; } public List<TaobaoCategoryClient.ItemProp> getSaleProperties() { return saleProperties; } public void setSaleProperties(List<TaobaoCategoryClient.ItemProp> saleProperties) { this.saleProperties = saleProperties; } public List<String> getErrors() { return errors; } public void setErrors(List<String> errors) { this.errors = errors; } } }六、Maven 依赖
<dependencies> <!-- OkHttp HTTP 客户端 --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <!-- FastJSON JSON 解析 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.25</version> </dependency> <!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>2.0.7</version> </dependency> </dependencies>七、常见错误码
| 错误码 | 错误信息 | 解决方案 |
|---|---|---|
40 | Invalid arguments | 检查必填参数是否完整,cid 是否为数字 |
40001 | Insufficient isv permissions | 应用无权限调用该接口,需在开放平台申请 |
40002 | Invalid timestamp | 时间戳格式错误或与服务端时间偏差超过 10 分钟 |
40003 | Invalid signature | 签名生成错误,检查 HMAC-MD5 算法和参数排序 |
40004 | Invalid method | 接口名称拼写错误 |
40006 | Invalid app_key | App Key 不存在或已失效 |
50001 | API call limit exceeded | 调用频率超限,降低调用频率或申请提升额度 |
isv.invalid-parameter:cid | cid 参数不合法 | 传入的类目 ID 不存在或已删除 |
八、最佳实践建议
缓存策略:类目数据变化频率低,建议本地缓存 24-48 小时,减少 API 调用
增量更新:定期同步类目变更(关注
status=deleted的类目)叶子类目校验:发布商品前务必校验目标类目是否为叶子类目
属性缓存:类目属性数据量大,建议按类目分别缓存
错误重试:网络异常时实现指数退避重试
权限管理:天猫商家需额外调用
itemcats.authorize.get校验授权类目
如遇任何疑问或有进一步的需求,请随时与我私信或者评论联系。