—— 从注册账号、生成签名到多线程批量抓取,完整代码直接复制跑通!
在电商数据驱动的时代,淘宝商品详情就是金矿。但淘宝反爬全站顶尖:滑块、IP 封、JS 混淆、x-sign 动态令牌……与其硬刚,不如“合法驾驶”——直接调用官方接口 taobao.item_get_pro,数据最全、最稳、可商用!下面用一篇超长软文,带你纯 Java 实现签名、抓取、解析、落地全流程,文末附源码仓库。
一、先放结果:我们能拿到什么?
字段 | 示例 |
---|---|
商品标题 | 【新品】iPhone 15 128G 蓝色 原装正品 |
价格 | 4999.00 |
主图URL | //img.alicdn.com/imgextra/xxx.jpg |
库存 | 356 |
销量 | 2180 |
图文详情(HTML) | <p>正品保障…</p> |
SKU 列表 | 颜色×内存×版本×价格×库存 |
店铺名称 | 小李数码旗舰店 |
二、技术选型:为什么Java也能“丝滑”爬虫?
模块 | 选型 | 理由 |
---|---|---|
HTTP 客户端 | Apache HttpClient 4.x | 连接池、代理、重试、并发全套API |
JSON 解析 | Jackson 2.15 | 注解+TreeNode双模式,字段缺失不报错 |
签名算法 | 原生MessageDigest | 零依赖,与淘宝C版完全一致 |
并发框架 | ThreadPoolExecutor | 8线程+BlockingQueue,限速简单 |
结果落地 | OpenCSV | 一行注解导出CSV,Excel直接打开 |
三、3 分钟完成“官方通行证”注册
- 打开 淘宝开放平台 ➜ 注册开发者
- 创建应用 ➜ 拿到 App Key + App Secret
- 申请 taobao.item_get_pro 权限(勾选“商品”类目)
- 审核约 2h ➜ 通过后即可正式调用,QPS=10,日调用 10W+ 足够
四、签名算法:淘宝的“钥匙”一次讲透
官方规则(MD5 版):sign = MD5(appSecret + 按Key排序(业务参数) + appSecret).toUpperCase()
业务参数不包括 sign 本身,也不包括 ? 和 &。
工具类 SignUtil.java:
java
public final class SignUtil {
public static String sign(Map<String, String> params, String appSecret) {
StringBuilder sb = new StringBuilder(appSecret);
params.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(e -> sb.append(e.getKey()).append(e.getValue()));
sb.append(appSecret);
return md5(sb.toString()).toUpperCase();
}
private static String md5(String raw) {
try {
byte[] bytes = MessageDigest.getInstance("MD5").digest(raw.getBytes(StandardCharsets.UTF_8));
return new BigInteger(1, bytes).toString(16);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
五、核心代码:从参数组装到 JSON 落地
① 构建请求 URL
java
public class TaoBaoClient {
private static final String API = "https://eco.taobao.com/router/rest";
private final String appKey, appSecret;
public String getItemJson(String numIid) throws Exception {
Map<String, String> p = new LinkedHashMap<>();
p.put("method", "taobao.item_get_pro");
p.put("app_key", appKey);
p.put("v", "2.0");
p.put("format", "json");
p.put("sign_method", "md5");
p.put("timestamp", LocalDateTime.now().toString());
p.put("num_iid", numIid);
p.put("fields", "title,price,pic_url,desc,skus,volume");
p.put("sign", SignUtil.sign(p, appSecret));
String url = API + "?" + p.entrySet().stream()
.map(e -> e.getKey() + "=" + URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
try (CloseableHttpClient client = HttpClients.createDefault()) {
return EntityUtils.toString(client.execute(new HttpGet(url)).getEntity());
}
}
}
② JSON → POJO(Jackson 注解)
java
@Data @JsonIgnoreProperties(ignoreUnknown = true)
public class ItemResp {
private Item item;
}
@Data @JsonIgnoreProperties(ignoreUnknown = true)
public class Item {
private String title;
private String price;
private String pic_url;
private String desc;
private Skus skus;
private Long volume; // 销量
}
@Data @JsonIgnoreProperties(ignoreUnknown = true)
public class Skus {
private List<Sku> sku;
}
@Data @JsonIgnoreProperties(ignoreUnknown = true)
public class Sku {
private String properties_name; // 颜色:蓝色;内存:128G
private String price;
private Long quantity;
}
③ 一行代码解析
java
ItemResp resp = new ObjectMapper().readValue(json, ItemResp.class);
Item item = resp.getItem();
System.out.println(item.getTitle() + " ¥" + item.getPrice());
六、批量抓取:ThreadPool + 限速 + 重试
线程池 8 线程,队列 2k,失败 3 次即跳过:
java
ExecutorService pool = Executors.newFixedThreadPool(8);
ConcurrentLinkedQueue<Item> result = new ConcurrentLinkedQueue<>();
Files.readAllLines(Path.of("items.txt"))
.forEach(id -> pool.submit(() -> {
int retry = 0;
while (retry < 3) {
try {
ItemResp resp = mapper.readValue(client.getItemJson(id), ItemResp.class);
result.add(resp.getItem());
System.out.println("✅ " + id);
break;
} catch (Exception e) {
retry++;
System.err.println("⚠️ " + id + " 第" + retry + "次失败");
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
}
}
}));
pool.shutdown();
pool.awaitTermination(1, TimeUnit.HOURS);
七、CSV 落地:OpenCSV 注解导出
java
@Builder
@CsvBindByName
class CsvRow {
@CsvBindByName String title;
@CsvBindByName String price;
@CsvBindByName String pic_url;
@CsvBindByName String desc;
@CsvBindByName Long volume;
}
List<CsvRow> rows = result.stream()
.map(i -> CsvRow.builder()
.title(i.getTitle())
.price(i.getPrice())
.pic_url(i.getPic_url())
.desc(StringUtils.abbreviate(i.getDesc(), 200))
.volume(i.getVolume())
.build())
.collect(Collectors.toList());
try (Writer w = Files.newBufferedWriter(Path.of("taobao.csv"))) {
new StatefulBeanToCsvBuilder<CsvRow>(w).build().write(rows);
}
八、反爬 & 合规清单
风险 | 解决方案 |
---|---|
签名错误 | 参数按字典序,MD5大写 |
QPS 超限 | 官方 10/s,线程池限速 8/s |
IP 被封 | 走官方 API,不封 IP |
数据侵权 | 不重新分发,仅内部分析 |
商用场景 | 建议购买“数据Plus”套餐,更稳 |
九、运行截图
bash
$ java -jar taobao-spider.jar
✅ 652874751412
✅ 679573433212
✅ 679329478532
[=] 共抓取 500 条,已写入 taobao.csv
CSV 示例(Excel 直接打开):
表格复制
title | price | pic_url | volume | desc |
---|---|---|---|---|
iPhone15 128G 蓝色 | 4999.00 | //img.alicdn.com/xxx.jpg | 2180 | 正品保障,全国联保… |
十、进阶脑洞
- Spring Boot 封装 REST 服务,对外提供“商品查询”接口;
- Redis 缓存 24h,避免重复调用;
- 接入 RabbitMQ,将“失败 ID”丢进队列重试;
- 用 ECharts + MySQL 做价格趋势看板;
- Docker 镜像 50M,一条命令 docker run -e AK=xxx -e AS=xxx 即可跑。
合法调用,稳中带快;官方接口,永不掉线!
下篇想继续看 “淘宝评论+视频+主图打包下载” ?留言告诉我,安排!