—— 從注冊賬號(hào)、生成簽名到多線程批量抓取,完整代碼直接復(fù)制跑通!
在電商數(shù)據(jù)驅(qū)動(dòng)的時(shí)代,淘寶商品詳情就是金礦。但淘寶反爬全站頂尖:滑塊、IP 封、JS 混淆、x-sign 動(dòng)態(tài)令牌……與其硬剛,不如“合法駕駛”——直接調(diào)用官方接口 taobao.item_get_pro,數(shù)據(jù)最全、最穩(wěn)、可商用!下面用一篇超長軟文,帶你純 Java 實(shí)現(xiàn)簽名、抓取、解析、落地全流程,文末附源碼倉庫。
一、先放結(jié)果:我們能拿到什么?
字段 | 示例 |
---|---|
商品標(biāo)題 | 【新品】iPhone 15 128G 藍(lán)色 原裝正品 |
價(jià)格 | 4999.00 |
主圖URL | //img.alicdn.com/imgextra/xxx.jpg |
庫存 | 356 |
銷量 | 2180 |
圖文詳情(HTML) | <p>正品保障…</p> |
SKU 列表 | 顏色×內(nèi)存×版本×價(jià)格×庫存 |
店鋪名稱 | 小李數(shù)碼旗艦店 |
二、技術(shù)選型:為什么Java也能“絲滑”爬蟲?
模塊 | 選型 | 理由 |
---|---|---|
HTTP 客戶端 | Apache HttpClient 4.x | 連接池、代理、重試、并發(fā)全套API |
JSON 解析 | Jackson 2.15 | 注解+TreeNode雙模式,字段缺失不報(bào)錯(cuò) |
簽名算法 | 原生MessageDigest | 零依賴,與淘寶C版完全一致 |
并發(fā)框架 | ThreadPoolExecutor | 8線程+BlockingQueue,限速簡單 |
結(jié)果落地 | OpenCSV | 一行注解導(dǎo)出CSV,Excel直接打開 |
三、3 分鐘完成“官方通行證”注冊
- 打開 淘寶開放平臺(tái) ? 注冊開發(fā)者
- 創(chuàng)建應(yīng)用 ? 拿到 App Key + App Secret
- 申請 taobao.item_get_pro 權(quán)限(勾選“商品”類目)
- 審核約 2h ? 通過后即可正式調(diào)用,QPS=10,日調(diào)用 10W+ 足夠
四、簽名算法:淘寶的“鑰匙”一次講透
官方規(guī)則(MD5 版):sign = MD5(appSecret + 按Key排序(業(yè)務(wù)參數(shù)) + appSecret).toUpperCase()
業(yè)務(wù)參數(shù)不包括 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);
}
}
}
五、核心代碼:從參數(shù)組裝到 JSON 落地
① 構(gòu)建請求 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; // 顏色:藍(lán)色;內(nèi)存: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());
六、批量抓?。篢hreadPool + 限速 + 重試
線程池 8 線程,隊(duì)列 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 注解導(dǎo)出
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);
}
八、反爬 & 合規(guī)清單
風(fēng)險(xiǎn) | 解決方案 |
---|---|
簽名錯(cuò)誤 | 參數(shù)按字典序,MD5大寫 |
QPS 超限 | 官方 10/s,線程池限速 8/s |
IP 被封 | 走官方 API,不封 IP |
數(shù)據(jù)侵權(quán) | 不重新分發(fā),僅內(nèi)部分析 |
商用場景 | 建議購買“數(shù)據(jù)Plus”套餐,更穩(wěn) |
九、運(yùn)行截圖
bash
$ java -jar taobao-spider.jar
? 652874751412
? 679573433212
? 679329478532
[=] 共抓取 500 條,已寫入 taobao.csv
CSV 示例(Excel 直接打開):
表格復(fù)制
title | price | pic_url | volume | desc |
---|---|---|---|---|
iPhone15 128G 藍(lán)色 | 4999.00 | //img.alicdn.com/xxx.jpg | 2180 | 正品保障,全國聯(lián)保… |
十、進(jìn)階腦洞
- Spring Boot 封裝 REST 服務(wù),對外提供“商品查詢”接口;
- Redis 緩存 24h,避免重復(fù)調(diào)用;
- 接入 RabbitMQ,將“失敗 ID”丟進(jìn)隊(duì)列重試;
- 用 ECharts + MySQL 做價(jià)格趨勢看板;
- Docker 鏡像 50M,一條命令 docker run -e AK=xxx -e AS=xxx 即可跑。
合法調(diào)用,穩(wěn)中帶快;官方接口,永不掉線!
下篇想繼續(xù)看 “淘寶評論+視頻+主圖打包下載” ?留言告訴我,安排!