详细介绍:用 Java 给 Amazon 关键词搜索做“全身 MRI”——可量产、可扩展的爬虫实战
一、技术选型:为什么选 Java 而不是 Python?
| 维度 | Java | Python |
|---|---|---|
| 反爬对抗 | 多线程 + 连接池 + 动态代理,易做高并发 | GIL 限制,高并发需另起炉灶 |
| 工程化 | Maven/Gradle 一键依赖,CI/CD 成熟 | 环境碎片化,服务器部署易踩坑 |
| 类型安全 | 编译期检查,重构不爆炸 | 运行时才能发现字段写错 |
| 企业存量 | 90% 跨境电商公司后端就是 Java,直接复用 DAO/Service | 需要额外微服务或消息队列对接 |
一句话:“Python 写原型快,Java 上生产稳。”
二、整体架构速览(3 分钟看懂)
┌-----------------------------┐
| Amazon 关键词搜索页 HTML |
└------------┬----------------┘
│ 1. 随机 UA + 住宅代理池
▼
┌-----------------------------┐
| 解析层(Jsoup + 连接池) |
| 异常重试 / 熔断 / 限流 |
└------------┬----------------┘
│ 2. 字段清洗
▼
┌-----------------------------┐
| 仓库层(CSV / MySQL / S3) |
| 增量 / 版本控制 |
└------------┬----------------┘
│ 3. 监控告警
▼
Grafana + 钉钉群
三、开发前准备(5 分钟搞定)
环境
JDK 17 + Maven 3.9 + IDEA 2024一次性引入依赖
xml
org.jsoup
jsoup
1.17.2
org.apache.httpcomponents.client5
httpclient5
5.3
com.opencsv
opencsv
5.8
ch.qos.logback
logback-classic
1.4.11
org.apache.commons
commons-lang3
3.13.0
目标字段 & CSS 选择器
| 字段 | 选择器 | |---|---| | 标题 |[data-component-type="s-search-result"] h2 a span| | 价格 |.a-price .a-offscreen| | 评分 |.a-icon-alt| | 评论数 |.a-size-base| | 库存/配送 |.a-color-base| | 图片 |.s-image| | 详情页链接 |h2 a的href|
四、MVP:180 行代码即可跑通
单 Maven 工程,支持多线程 10 关键词,自动翻页、重试 429,结果直接写
amazon_keyword.csv。
java
package com.demo;
import com.opencsv.bean.*;
import org.apache.hc.client5.http.classic.methods.*;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.*;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.util.Timeout;
import org.jsoup.Jsoup;
import org.jsoup.nodes.*;
import org.jsoup.select.Elements;
import org.slf4j.*;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.*;
public class AmazonKeywordScraper {
private static final Logger log = LoggerFactory.getLogger(AmazonKeywordScraper.class);
/* === 1. 全局线程安全 Client(连接池 200) === */
private static final CloseableHttpClient HTTP = HttpClients.custom()
.setConnectionManager(new PoolingHttpClientConnectionManager())
.setDefaultRequestConfig(RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(5))
.setResponseTimeout(Timeout.ofSeconds(8))
.build())
.build();
/* === 2. 字段 Bean === */
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Product {
@CsvBindByName String kw;
@CsvBindByName int rank;
@CsvBindByName String title;
@CsvBindByName String price;
@CsvBindByName String rating;
@CsvBindByName String reviewCount;
@CsvBindByName String availability;
@CsvBindByName String imgUrl;
@CsvBindByName String detailLink;
@CsvBindByName String scrapeTime = LocalDateTime.now().toString();
}
/* === 3. 入口 === */
public static void main(String[] args) throws Exception {
List keywords = List.of("coffee maker", "office desk");
int maxPage = 3;
List result = Collections.synchronizedList(new ArrayList<>());
ExecutorService pool = Executors.newFixedThreadPool(10);
for (String kw : keywords) {
for (int p = 1; p <= maxPage; p++) {
final int page = p;
pool.submit(() -> {
try {
List pageList = scrapePage(kw, page);
result.addAll(pageList);
log.info("✅ {} 第 {} 页抓到 {} 条", kw, page, pageList.size());
Thread.sleep(RandomUtils.nextInt(2, 4) * 1000L);
} catch (Exception e) {
log.error("❌ {} 第 {} 页失败: {}", kw, page, e.getMessage());
}
});
}
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.HOURS);
/* 4. 落盘 CSV */
Path out = Paths.get("amazon_keyword.csv");
try (Writer w = Files.newBufferedWriter(out)) {
StatefulBeanToCsv beanToCsv = new StatefulBeanToCsvBuilder(w).build();
beanToCsv.write(result);
}
log.info(">>> 共 {} 条数据已写入 {}", result.size(), out.toAbsolutePath());
}
/* === 5. 核心抓取逻辑 === */
private static List scrapePage(String kw, int pageNo) throws IOException {
String url = "https://www.amazon.com/s?k=" + URLEncoder.encode(kw, "UTF-8") + "&page=" + pageNo;
HttpGet req = new HttpGet(url);
req.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36");
req.setHeader("Accept-Language", "en-US,en;q=0.9");
String html;
try (CloseableHttpResponse resp = HTTP.execute(req)) {
if (resp.getCode() != 200) throw new IOException("HTTP " + resp.getCode());
html = EntityUtils.toString(resp.getEntity());
}
Document doc = Jsoup.parse(html);
Elements products = doc.select("[data-component-type='s-search-result']");
List list = new ArrayList<>();
int base = (pageNo - 1) * 48;
for (int i = 0; i < products.size(); i++) {
Element e = products.get(i);
String title = e.select("h2 a span").text();
String price = e.select(".a-price .a-offscreen").text();
String rating = Optional.ofNullable(e.select(".a-icon-alt").first())
.map(el -> el.attr("innerHTML")).orElse("N/A");
rating = rating.contains("out") ? rating.split(" ")[0] : "N/A";
String review = e.select(".a-size-base").text().replaceAll("[^0-9]", "");
String avail = e.select(".a-color-base").text();
String img = e.select(".s-image").attr("src");
String link = "https://amazon.com" + e.select("h2 a").attr("href");
list.add(new Product(kw, base + i + 1, title, price, rating, review, avail, img, link));
}
return list;
}
}
运行日志:
22:33:12.121 ✅ coffee maker 第 1 页抓到 48 条
...
22:35:18.752 >>> 共 288 条数据已写入 /Users/xxx/amazon_keyword.csv
CSV 预览:
| kw | rank | title | price | rating | reviewCount | availability | imgUrl | detailLink | scrapeTime |
|---|---|---|---|---|---|---|---|---|---|
| coffee maker | 1 | Keurig K-Classic Coffee Maker | $89.99 | 4.6 | 84235 | In stock | https://m.media-amazon.com/... | https://amazon.com/dp/... | 2025-10-22T22:33:12 |
五、反爬四件套,让你的爬虫“长命百岁”
Amazon 的反爬 = “动态阈值 + 行为检测 + 验证码” 三维立体防御。
下面四件套,亲测能把 429 概率降到 1% 以下:
住宅代理池
付费:BrightData、Oxylabs、IPRoyal(支持 SOCKS5)
自建:Tor + 轻量池(适合日采 <5k)
代码层只需把HttpClient包装成代理路由:
HttpHost proxy = new HttpHost("ip", port); return HttpClients.custom().setProxy(proxy).build();浏览器指纹随机化
每次启动随机 UA、Accept-Language、Viewport
禁用
keep-alive防止 TCP 追踪失败代理自动冷宫 30 min
限速 + 重试
单 IP 每秒 ≤ 1 请求;随机 sleep 2~5 s
返回 429 时指数退避 1s→2s→4s→8s,最多 5 次
用 Resilience4j 快速集成:
io.github.resilience4j resilience4j-retry 2.1.0 验证码熔断
检测到标题含 “Robot Check” 立即丢弃该代理,冷宫 30 min
对接 2Captcha / Ruokuai 平台自动打码(成本 ≈ $0.003/次)
六、把数据“喂”给业务:4 个真实场景
选品决策
每天 06:00 定时跑完 1000 个关键词,用 Pandas 算出“昨日新品 Top10” → 飞书群推送,运营上班即可决策。动态定价
将抓取到的 FBA 价格、跟卖数丢进自研算法,自动调整 ERP 售价,保证 Buy Box 胜率 ≥ 85%。库存预警
监控对手availability字段,一旦出现 “Only 2 left” 立即发邮件:可以加大广告抢流量!评论情感分析
把reviewText一并收下来,用 HanLP 做中文分词、SnowNLP 做情感打分,找到 1~2 星差评关键词,反向优化说明书。
七、常见坑合集(血泪史)
| 坑 | 现象 | 解决 |
|---|---|---|
| 价格字段空 | 页面是 JS 渲染,Jsoup 抓不到 | 用 Selenium/Playwright 先执行 JS,再拿最终 HTML |
| 评分 null | 新上架无评论 | 代码里判空,默认值 “N/A” |
| 被重定向验证码 | 返回 200 但 title 是 “Robot Check” | 识别后自动暂停 30 min,切代理 |
| 图片 URL 失效 | Amazon 图片带时效参数 | 及时把 https://images-na.ssl-images-amazon.com/… 转存到自家 OSS |
| CSV 中文乱码 | Excel 直接打开是 “???” | 写文件时指定 UTF-8 with BOM 或使用 OpenCSV 的 CSVWriter(writer, ',', '\uFEFF') |
八、10 万级关键词分布式方案
单线程玩 2 个关键词没毛病,老板一句“给我把全站 30 万关键词每天扫一遍”怎么办?
把上面的 MVP 拆成“三件套”即可水平扩展:
调度层
SpringBoot + Quartz,把 30 万关键词按热度拆成 4 档,分别给 4 个 cron 表达式(小时级/日级/周级/月级)。抓取节点
Kubernetes 部署 20 个 Pod,每个 Pod 消费 Kafka 的amazon_kw队列,抓取完回写 S3(Parquet 格式)。监控大盘
Prometheus 采集“成功数 / 429 数 / 平均耗时”,Grafana 一屏展示;超过阈值自动钉钉 + 邮件。
九、Docker 一键部署(附 Dockerfile)
dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/amazon-keyword-scraper-1.0-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","app.jar"]
构建 & 运行:
bash
mvn clean package -DskipTests
docker build -t amz-keyword-java .
docker run --rm -v $(pwd)/data:/app/data amz-keyword-java
十、写在最后的“防吃牢饭”提示
Amazon 的数据受《计算机欺诈与滥用法》(CFAA)及《数字千年版权法》(DMCA)保护,请务必:
仅抓取“公开可见、无需登录” 的页面;
遵守 robots.txt(Amazon 几乎全站 allow:/,但频率需合理);
数据仅限内部商业分析,不得直接转载、转售或公开 API 化;
生产环境先行法律评估,必要时与律师确认合规条款。

浙公网安备 33010602011771号