1688(阿里巴巴批发网)作为国内最大的B2B电商平台,为开发者提供了丰富的API接口用于商品搜索、数据获取等操作。本文将详细介绍如何使用Java调用1688关键词搜索接口,涵盖从账号注册到完整代码实现的完整流程。
一、接口概述
1688开放平台提供了多种商品搜索相关接口,核心接口包括:
| 接口名称 | 功能描述 | 适用场景 |
|---|---|---|
alibaba.product.search | 按关键词搜索商品列表 | 通用商品搜索 |
item_search | 关键词搜索商品列表(第三方封装) | 快速集成 |
alibaba.product.list.get | 获取店铺商品列表 | 店铺商品管理 本文以 item_search 接口为例,介绍通过关键词搜索1688商品列表的完整实现。该接口支持分页查询、价格过滤、排序等功能,返回商品ID、标题、价格、库存、图片等核心字段。 |
二、准备工作
1. 注册开发者账号
- 注册开发者账号
- 完成企业认证(个人开发者功能受限)
- 创建应用,获取以下凭证:
| 凭证 | 说明 |
|---|---|
AppKey | 应用唯一标识 |
AppSecret | 应用密钥,用于签名 |
AccessToken | 用户授权令牌(OAuth2.0) |
2. Maven 依赖配置
<dependencies>
<!-- HTTP 请求 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<!-- JSON 解析 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
</dependencies>
三、核心代码实现
1. 签名工具类
1688 API 采用 HMAC-SHA1 签名算法,需将参数按字典序排序后拼接生成签名:
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.stream.Collectors;
public class SignUtil {
private static final String HMAC_SHA1 = "HmacSHA1";
/**
* 生成 HMAC-SHA1 签名
* 签名规则:将参数按 key 字典序排序,拼接为 key1=value1&key2=value2,使用 appSecret 作为密钥进行 HMAC-SHA1 加密
*/
public static String generateHmacSha1Sign(Map<String, String> params, String appSecret) {
// 1. 过滤空值,按 key 字典序排序
String sortedParams = params.entrySet().stream()
.filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty())
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
// 2. HMAC-SHA1 加密
try {
Mac mac = Mac.getInstance(HMAC_SHA1);
SecretKeySpec secretKey = new SecretKeySpec(
appSecret.getBytes(StandardCharsets.UTF_8),
HMAC_SHA1
);
mac.init(secretKey);
byte[] bytes = mac.doFinal(sortedParams.getBytes(StandardCharsets.UTF_8));
return bytesToHex(bytes).toUpperCase();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("签名生成失败", e);
}
}
/**
* 字节数组转十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
2. Access Token 获取(OAuth2.0)注册账号获取key
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.util.ArrayList;
import java.util.List;
public class AuthService {
private static final String TOKEN_URL = "https://gw.open.1688.com/openapi/http/1/system.oauth2/getToken";
/**
* 使用 client_credentials 模式获取 Access Token
*/
public static String getAccessToken(String clientId, String clientSecret) {
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(TOKEN_URL);
List<BasicNameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("grant_type", "client_credentials"));
params.add(new BasicNameValuePair("client_id", clientId));
params.add(new BasicNameValuePair("client_secret", clientSecret));
params.add(new BasicNameValuePair("need_refresh_token", "true"));
post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
String response = EntityUtils.toString(client.execute(post).getEntity(), "UTF-8");
JsonObject json = JsonParser.parseString(response).getAsJsonObject();
if (json.has("access_token")) {
return json.get("access_token").getAsString();
} else {
throw new RuntimeException("获取 Token 失败: " + response);
}
} catch (Exception e) {
throw new RuntimeException("Token 获取异常", e);
}
}
}
3. 关键词搜索服务类
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ItemSearchService {
// 1688 开放平台网关地址
private static final String API_URL = "https://gw.open.1688.com/openapi/param2/2/portals.open/api.listOfferDetail/";
// 或第三方封装接口
private static final String API_URL_THIRD = "https://api-gw.onebound.cn/1688/item_search/";
private final String appKey;
private final String appSecret;
private final Gson gson = new Gson();
public ItemSearchService(String appKey, String appSecret) {
this.appKey = appKey;
this.appSecret = appSecret;
}
/**
* 按关键词搜索商品
*
* @param keyword 搜索关键词
* @param page 页码(从1开始)
* @param pageSize 每页数量(最大50)
* @param sort 排序方式:price(价格)、sale(销量)、credit(信用)
* @param priceStart 起始价格(可选)
* @param priceEnd 结束价格(可选)
* @return 商品列表
*/
public SearchResult searchByKeyword(String keyword, int page, int pageSize,
String sort, Double priceStart, Double priceEnd) {
// 构建请求参数
Map<String, String> params = new HashMap<>();
params.put("key", appKey);
params.put("secret", appSecret);
params.put("q", keyword);
params.put("page", String.valueOf(page));
params.put("page_size", String.valueOf(pageSize));
if (sort != null) {
params.put("sort", sort);
}
if (priceStart != null) {
params.put("start_price", String.valueOf(priceStart));
}
if (priceEnd != null) {
params.put("end_price", String.valueOf(priceEnd));
}
// 生成签名
String sign = SignUtil.generateHmacSha1Sign(params, appSecret);
params.put("sign", sign);
// 构建请求 URL
String url = buildUrl(API_URL_THIRD, params);
// 发送请求
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
httpGet.setHeader("Accept", "application/json");
String response = EntityUtils.toString(client.execute(httpGet).getEntity(), "UTF-8");
JsonObject jsonResponse = JsonParser.parseString(response).getAsJsonObject();
return parseSearchResult(jsonResponse);
} catch (Exception e) {
throw new RuntimeException("搜索商品失败: " + e.getMessage(), e);
}
}
/**
* 构建带参数的 URL
*/
private String buildUrl(String baseUrl, Map<String, String> params) {
StringBuilder url = new StringBuilder(baseUrl);
url.append("?");
params.forEach((key, value) -> {
try {
url.append(key).append("=")
.append(URLEncoder.encode(value, "UTF-8"))
.append("&");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("URL 编码失败", e);
}
});
// 移除末尾的 &
return url.substring(0, url.length() - 1);
}
/**
* 解析搜索响应
*/
private SearchResult parseSearchResult(JsonObject response) {
SearchResult result = new SearchResult();
// 检查错误
if (response.has("error")) {
JsonObject error = response.getAsJsonObject("error");
result.setSuccess(false);
result.setErrorCode(error.has("code") ? error.get("code").getAsString() : "UNKNOWN");
result.setErrorMessage(error.has("message") ? error.get("message").getAsString() : "未知错误");
return result;
}
result.setSuccess(true);
// 解析商品列表
if (response.has("items") && response.get("items").isJsonObject()) {
JsonObject itemsObj = response.getAsJsonObject("items");
if (itemsObj.has("item") && itemsObj.get("item").isJsonArray()) {
JsonArray items = itemsObj.getAsJsonArray("item");
List<Product> productList = new ArrayList<>();
for (int i = 0; i < items.size(); i++) {
JsonObject item = items.get(i).getAsJsonObject();
Product product = new Product();
product.setProductId(item.has("num_iid") ? item.get("num_iid").getAsString() : "");
product.setTitle(item.has("title") ? item.get("title").getAsString() : "");
product.setPrice(item.has("price") ? item.get("price").getAsString() : "0");
product.setOriginalPrice(item.has("orginal_price") ? item.get("orginal_price").getAsString() : "0");
product.setPicUrl(item.has("pic_url") ? item.get("pic_url").getAsString() : "");
product.setDetailUrl(item.has("detail_url") ? item.get("detail_url").getAsString() : "");
product.setSales(item.has("sales") ? item.get("sales").getAsInt() : 0);
product.setSellerName(item.has("seller_nick") ? item.get("seller_nick").getAsString() : "");
product.setLocation(item.has("area") ? item.get("area").getAsString() : "");
product.setPostFee(item.has("post_fee") ? item.get("post_fee").getAsString() : "0");
productList.add(product);
}
result.setProducts(productList);
}
// 解析分页信息
if (itemsObj.has("page")) {
result.setCurrentPage(itemsObj.get("page").getAsInt());
}
if (itemsObj.has("real_total_results")) {
result.setTotalResults(itemsObj.get("real_total_results").getAsInt());
}
}
return result;
}
}
4. 实体类定义
import java.util.List;
/**
* 搜索结果封装类
*/
public class SearchResult {
private boolean success;
private String errorCode;
private String errorMessage;
private List<Product> products;
private int currentPage;
private int totalResults;
// Getters and Setters
public boolean isSuccess() { return success; }
public void setSuccess(boolean success) { this.success = success; }
public String getErrorCode() { return errorCode; }
public void setErrorCode(String errorCode) { this.errorCode = errorCode; }
public String getErrorMessage() { return errorMessage; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
public List<Product> getProducts() { return products; }
public void setProducts(List<Product> products) { this.products = products; }
public int getCurrentPage() { return currentPage; }
public void setCurrentPage(int currentPage) { this.currentPage = currentPage; }
public int getTotalResults() { return totalResults; }
public void setTotalResults(int totalResults) { this.totalResults = totalResults; }
@Override
public String toString() {
return "SearchResult{" +
"success=" + success +
", totalResults=" + totalResults +
", productsCount=" + (products != null ? products.size() : 0) +
'}';
}
}
/**
* 商品信息实体类
*/
public class Product {
private String productId; // 商品ID
private String title; // 商品标题
private String price; // 当前价格
private String originalPrice; // 原价
private String picUrl; // 商品图片
private String detailUrl; // 商品详情页链接
private int sales; // 销量
private String sellerName; // 卖家昵称
private String location; // 发货地
private String postFee; // 运费
// Getters and Setters
public String getProductId() { return productId; }
public void setProductId(String productId) { this.productId = productId; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getPrice() { return price; }
public void setPrice(String price) { this.price = price; }
public String getOriginalPrice() { return originalPrice; }
public void setOriginalPrice(String originalPrice) { this.originalPrice = originalPrice; }
public String getPicUrl() { return picUrl; }
public void setPicUrl(String picUrl) { this.picUrl = picUrl; }
public String getDetailUrl() { return detailUrl; }
public void setDetailUrl(String detailUrl) { this.detailUrl = detailUrl; }
public int getSales() { return sales; }
public void setSales(int sales) { this.sales = sales; }
public String getSellerName() { return sellerName; }
public void setSellerName(String sellerName) { this.sellerName = sellerName; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
public String getPostFee() { return postFee; }
public void setPostFee(String postFee) { this.postFee = postFee; }
@Override
public String toString() {
return "Product{" +
"productId='" + productId + '\'' +
", title='" + title + '\'' +
", price='" + price + '\'' +
", sales=" + sales +
", sellerName='" + sellerName + '\'' +
'}';
}
}
5. 主程序入口
public class ItemSearchDemo {
public static void main(String[] args) {
// 配置信息(请替换为实际值)
String appKey = "your_app_key";
String appSecret = "your_app_secret";
String keyword = "手机支架"; // 搜索关键词
try {
// 初始化搜索服务
ItemSearchService searchService = new ItemSearchService(appKey, appSecret);
System.out.println("正在搜索关键词: " + keyword);
System.out.println("=".repeat(60));
// 执行搜索(第1页,每页40条,按价格排序)
SearchResult result = searchService.searchByKeyword(
keyword,
1, // 页码
40, // 每页数量
"price", // 排序方式:price/sale/credit
10.0, // 起始价格
100.0 // 结束价格
);
// 输出结果
if (result.isSuccess()) {
System.out.println("搜索成功!");
System.out.println("总结果数: " + result.getTotalResults());
System.out.println("当前页: " + result.getCurrentPage());
System.out.println("商品数量: " + result.getProducts().size());
System.out.println("-".repeat(60));
// 遍历商品列表
for (Product product : result.getProducts()) {
System.out.println("商品ID: " + product.getProductId());
System.out.println("标题: " + product.getTitle());
System.out.println("价格: ¥" + product.getPrice());
System.out.println("原价: ¥" + product.getOriginalPrice());
System.out.println("销量: " + product.getSales());
System.out.println("卖家: " + product.getSellerName());
System.out.println("发货地: " + product.getLocation());
System.out.println("图片: " + product.getPicUrl());
System.out.println("链接: " + product.getDetailUrl());
System.out.println("-".repeat(60));
}
} else {
System.err.println("搜索失败!");
System.err.println("错误码: " + result.getErrorCode());
System.err.println("错误信息: " + result.getErrorMessage());
}
} catch (Exception e) {
System.err.println("程序执行异常: " + e.getMessage());
e.printStackTrace();
}
}
}
四、接口返回数据示例
调用成功后,接口返回的 JSON 数据结构如下:
{
"items": {
"page": 1,
"real_total_results": 1250,
"total_results": 1250,
"pagecount": 32,
"item": [
{
"num_iid": "123456789012",
"title": "手机支架桌面懒人支架可折叠",
"price": "15.80",
"orginal_price": "25.00",
"pic_url": "https://cbu01.alicdn.com/xxx.jpg",
"detail_url": "https://detail.1688.com/offer/123456789012.html",
"sales": 5000,
"seller_nick": "某某电子厂",
"area": "广东深圳",
"post_fee": "0.00"
}
]
}
}
五、请求参数说明
| 参数名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
key | String | 是 | AppKey |
secret | String | 是 | AppSecret |
q | String | 是 | 搜索关键词 |
page | Integer | 否 | 页码,默认1 |
page_size | Integer | 否 | 每页数量,默认20,最大50 |
sort | String | 否 | 排序:price(价格)、sale(销量)、credit(信用) |
start_price | Double | 否 | 起始价格 |
end_price | Double | 否 | 结束价格 |
cat | String | 否 | 类目ID |
filter | String | 否 | 额外过滤条件 |
六、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
401 认证失败 | AppKey/AppSecret 错误或过期 | 检查凭证是否正确,确认应用状态正常 |
403 权限不足 | 未申请接口权限 | 在开放平台申请 商品搜索 相关 API 权限 |
429 请求频繁 | 超出调用频率限制 | 增加请求间隔,实现限流策略 |
签名错误 | 签名算法或参数排序有误 | 确认使用 HMAC-SHA1 算法,参数按字典序排序 |
数据为空 | 关键词无匹配结果或参数错误 | 更换关键词,检查价格区间是否合理 |
Token 过期 | AccessToken 有效期通常为2小时 | 实现 Token 自动刷新机制 |
七、进阶优化建议
1. Token 自动刷新与缓存
public class TokenManager {
private String accessToken;
private long expireTime;
private final String clientId;
private final String clientSecret;
public TokenManager(String clientId, String clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
}
public synchronized String getValidToken() {
// 提前5分钟刷新
if (accessToken == null || System.currentTimeMillis() > expireTime - 300000) {
accessToken = AuthService.getAccessToken(clientId, clientSecret);
expireTime = System.currentTimeMillis() + 7200 * 1000;
}
return accessToken;
}
}
2. 连接池配置
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
public class HttpClientPool {
private static final CloseableHttpClient httpClient;
static {
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(50);
httpClient = HttpClients.custom()
.setConnectionManager(cm)
.setConnectionManagerShared(true)
.build();
}
public static CloseableHttpClient getClient() {
return httpClient;
}
}
3. 批量搜索与异步处理
对于需要批量获取多页数据的场景,建议使用 CompletableFuture 实现异步并行调用:
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class BatchSearchService {
public List<Product> searchAllPages(String keyword, int totalPages) {
List<CompletableFuture<List<Product>>> futures = IntStream.rangeClosed(1, totalPages)
.mapToObj(page -> CompletableFuture.supplyAsync(() -> {
SearchResult result = searchService.searchByKeyword(keyword, page, 40, "price", null, null);
return result.getProducts();
}))
.collect(Collectors.toList());
return futures.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.collect(Collectors.toList());
}
}

