JAVA爬取亚马逊的商品信息
一、项目概述
本文介绍一个基于 Java 的亚马逊商品信息爬虫工具。用户只需输入商品名称,程序即可自动爬取亚马逊搜索结果中所有相关商品的信息(包括商品名称和价格),并解决爬取过程中常见的反爬虫限制问题。
二、核心功能与效果展示
1. 主要功能
- 商品搜索爬取:输入商品名称,自动爬取亚马逊搜索结果中的所有相关商品
- 信息提取:准确提取商品名称和价格信息
- 去重处理:自动识别并过滤重复商品
- 反爬虫绕过:采用多种策略应对亚马逊的反爬虫机制
2. 效果展示
商品搜索页面:
爬取结果页面(已去重):
数据准确性验证(第31行商品):
三、技术实现思路
1. 核心算法流程
- 初始化搜索:根据输入的商品名构造亚马逊搜索URL
- 页面类型判断:区分列表页和商品详情页
- 列表页处理:提取所有商品页URL和下一页URL,加入爬取队列
- 商品页处理:提取商品名称和价格,保存到文件
- 队列循环:持续处理队列中的URL直到队列为空
2. 技术特点
- 纯Java实现:仅使用Java标准库,无需第三方依赖
- 正则表达式提取:通过正则匹配从混乱的HTML中提取关键信息
- 广度优先搜索:使用队列实现URL的广度优先遍历
四、关键技术问题与解决方案
1. 反爬虫限制(503错误)
问题现象:亚马逊会识别爬虫行为,频繁访问会返回503错误,导致只能爬取少量页面。
解决方案:
- User-Agent随机化:预置15个不同浏览器的User-Agent,每次请求随机选择
- 请求降速:适当延长两次请求之间的时间间隔
- URL去重:避免重复请求相同页面
- 请求头完善:设置完整的HTTP请求头,模拟真实浏览器
2. 页面结构复杂
问题描述:亚马逊页面HTML结构复杂,信息提取困难。
解决方案:通过仔细分析页面源码,找到关键信息的规律,使用正则表达式精准提取:
- 商品名称:匹配
<meta name="description" content="商品名称,模式 - 商品价格:匹配
class="a-size-medium a-color-price">¥价格</span>模式 - 商品URL:匹配
href="http://www.amazon.cn/.../dp/商品编号"模式
3. 商品重复问题
问题描述:同一商品在搜索结果中可能有3-4个不同URL(名称页、图文页、评论页等)。
解决方案:通过商品编号(dp后面的字符串)进行去重。例如:
http://www.amazon.cn/手机-通讯/dp/B00OB5T26S...... 商品编号:B00OB5T26S五、代码实现详解
1. 核心类结构
import java.io.*; import java.net.*; import java.util.*; import java.util.regex.*; public class AmazonCrawler { // 用于存储上一个商品编号,用于去重 public static String lastProductId = "|"; // 匹配商品URL的正则表达式 public static Pattern p_goods = Pattern.compile("href="(http://www\.amazon\.cn/.+?/dp/(.+?))""); // 存放待爬取的URL队列 public static Queue<String> urlQueue = new LinkedList<String>(); // 输出文件 public static File outputFile; public static BufferedWriter writer; // 15个User-Agent随机使用,降低被识别为爬虫的概率 public static String[] userAgents = { "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; Intel Mac OS X 10.6; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36 OPR/18.0.1284.68", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", "Opera/9.80 (Macintosh; Intel Mac OS X 10.9.1) Presto/2.12.388 Version/12.16", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36 OPR/18.0.1284.68", "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) CriOS/30.0.1599.12 Mobile/11A465 Safari/8536.25", "Mozilla/5.0 (iPad; CPU OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4", "Mozilla/5.0 (iPad; CPU OS 7_0_2 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A501 Safari/9537.53" }; // 核心爬取方法 public static void action(String target) throws IOException, InterruptedException { // 方法实现见下文 } // 主方法 public static void main(String[] args) throws IOException, InterruptedException { // 程序入口见下文 } }2. action 方法完整实现
/** * 核心爬取方法 * @param target 商品搜索关键词 */ public static void action(String target) throws IOException, InterruptedException { // 1. 构造搜索URL String searchUrl = "http://www.amazon.cn/s/ref=nb_sb_noss?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&field-keywords=" + URLEncoder.encode(target, "UTF-8"); // 2. 初始化队列和文件 urlQueue.clear(); urlQueue.add(searchUrl); outputFile = new File("amazon_products_" + System.currentTimeMillis() + ".txt"); writer = new BufferedWriter(new FileWriter(outputFile)); writer.write("商品名称\t价格\t商品URL\n"); writer.write("----------------------------------------\n"); // 3. 已访问URL集合,避免重复爬取 Set<String> visitedUrls = new HashSet<>(); // 4. 主循环:处理队列中的URL while (!urlQueue.isEmpty()) { String currentUrl = urlQueue.poll(); // 检查URL是否已访问过,避免重复爬取 if (visitedUrls.contains(currentUrl)) { continue; } visitedUrls.add(currentUrl); // 5. 发送HTTP请求 URL url = new URL(currentUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(10000); conn.setReadTimeout(10000); // 随机选择User-Agent,模拟不同浏览器 Random rand = new Random(); String userAgent = userAgents[Math.abs(rand.nextInt()) % userAgents.length]; conn.setRequestProperty("User-Agent", userAgent); // 设置其他请求头模拟浏览器 conn.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8"); conn.setRequestProperty("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8"); conn.setRequestProperty("Connection", "keep-alive"); // 6. 获取响应内容 int responseCode = conn.getResponseCode(); // 处理503反爬虫错误,等待后重试 if (responseCode == 503) { System.out.println("遇到503错误,等待5秒后重试..."); Thread.sleep(5000); urlQueue.add(currentUrl); // 重新加入队列 continue; } if (responseCode != 200) { System.out.println("请求失败,状态码: " + responseCode + " - " + currentUrl); continue; } // 读取页面内容 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); StringBuilder contentBuilder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { contentBuilder.append(line).append("\n"); } reader.close(); conn.disconnect(); String content = contentBuilder.toString(); // 7. 判断页面类型并处理 if (currentUrl.contains("/dp/")) { // 商品详情页:提取商品信息 processProductPage(content, currentUrl); } else { // 搜索结果页:提取商品链接和下一页链接 processSearchPage(content); } // 8. 请求降速,避免触发反爬虫 Thread.sleep(2000 + rand.nextInt(3000)); System.out.println("已处理: " + currentUrl); } // 9. 关闭文件 writer.close(); System.out.println("爬取完成!结果已保存到: " + outputFile.getAbsolutePath()); } /** 处理商品详情页,提取商品信息 / private static void processProductPage(String content, String url) throws IOException { // 提取商品名称 Pattern pName = Pattern.compile("<meta name="description" content="([^"]+?),"); Matcher mName = pName.matcher(content); // 提取商品价格 Pattern pPrice = Pattern.compile("class="a-size-medium a-color-price">¥([^<]+?)</span>"); Matcher mPrice = pPrice.matcher(content); String productName = "未找到"; String price = "未找到"; if (mName.find()) { productName = mName.group(1).trim(); } if (mPrice.find()) { price = mPrice.group(1).trim(); } // 写入文件 writer.write(productName + "\t" + price + "\t" + url + "\n"); writer.flush(); System.out.println("提取商品: " + productName + " | 价格: " + price); } /* 处理搜索结果页,提取商品链接 */ private static void processSearchPage(String content) { // 提取商品链接 Matcher goodsMatcher = p_goods.matcher(content); while (goodsMatcher.find()) { String productUrl = goodsMatcher.group(1); String productId = goodsMatcher.group(2); // 去重逻辑:如果商品编号与上一个不同,则加入队列 if (!productId.equals(lastProductId)) { urlQueue.add(productUrl); lastProductId = productId; System.out.println("发现新商品: " + productId); } } // 提取下一页链接(简化处理,实际可能需要更复杂的逻辑) Pattern pNextPage = Pattern.compile("href="([^"]+?page=2[^"]?)""); Matcher mNextPage = pNextPage.matcher(content); if (mNextPage.find()) { String nextPageUrl = "http://www.amazon.cn" + mNextPage.group(1); urlQueue.add(nextPageUrl); } }3. main 方法完整实现
/** * 程序主入口 */ public static void main(String[] args) throws IOException, InterruptedException { System.out.println("=== 亚马逊商品爬虫启动 ==="); System.out.println("作者: CSDN博客"); System.out.println("功能: 自动爬取亚马逊商品信息"); System.out.println("========================\n"); // 1. 获取用户输入 BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in)); System.out.print("请输入要搜索的商品名称: "); String keyword = consoleReader.readLine().trim(); if (keyword.isEmpty()) { System.out.println("错误: 搜索关键词不能为空!"); System.out.println("使用示例: java AmazonCrawler"); System.out.println("然后输入: iPhone 手机"); return; } System.out.println("\n开始爬取商品: " + keyword); System.out.println("正在构造搜索URL..."); // 2. 执行爬取 long startTime = System.currentTimeMillis(); try { action(keyword); } catch (Exception e) { System.err.println("爬取过程中发生错误: " + e.getMessage()); e.printStackTrace(); // 确保文件被关闭 if (writer != null) { try { writer.close(); } catch (IOException ex) { // 忽略关闭异常 } } } // 3. 统计信息 long endTime = System.currentTimeMillis(); long duration = (endTime - startTime) / 1000; System.out.println("\n=== 爬取统计 ==="); System.out.println("搜索关键词: " + keyword); System.out.println("爬取时长: " + duration + " 秒"); if (outputFile != null && outputFile.exists()) { // 统计行数 BufferedReader fileReader = new BufferedReader(new FileReader(outputFile)); int lineCount = 0; while (fileReader.readLine() != null) { lineCount++; } fileReader.close(); int productCount = Math.max(0, lineCount - 2); // 减去标题行和分隔行 System.out.println("爬取商品数量: " + productCount + " 个"); System.out.println("结果文件: " + outputFile.getAbsolutePath()); // 显示前几条结果 if (productCount &gt; 0) { System.out.println("\n前5条商品信息:"); fileReader = new BufferedReader(new FileReader(outputFile)); String displayLine; int displayCount = 0; while ((displayLine = fileReader.readLine()) != null &amp;&amp; displayCount &lt; 7) { // 包括标题 System.out.println(displayLine); displayCount++; } fileReader.close(); } } System.out.println("\n=== 爬取完成 ==="); System.out.println("按回车键退出..."); consoleReader.readLine(); }4. 关键代码解析
搜索URL构造:
String searchUrl = "http://www.amazon.cn/s/ref=nb_sb_noss?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&field-keywords=" + URLEncoder.encode(target, "UTF-8");User-Agent随机选择:
Random rand = new Random(); String userAgent = userAgents[Math.abs(rand.nextInt()) % userAgents.length]; conn.setRequestProperty("User-Agent", userAgent);商品信息提取:
// 提取商品名称 Pattern pName = Pattern.compile("<meta name=\"description\" content=\"([^\"]+?),"); Matcher mName = pName.matcher(content); // 提取商品价格 Pattern pPrice = Pattern.compile("class="a-size-medium a-color-price">¥([^<]+?)</span>"); Matcher mPrice = pPrice.matcher(content);商品去重逻辑:
// 提取商品编号 Matcher goodsMatcher = p_goods.matcher(content); while (goodsMatcher.find()) { String productUrl = goodsMatcher.group(1); String productId = goodsMatcher.group(2); // 去重:如果不等于上一个商品编号,才加入队列 if (!productId.equals(lastProductId)) { urlQueue.add(productUrl); lastProductId = productId; } }反爬虫处理:
// 503错误重试机制 if (responseCode == 503) { System.out.println("遇到503错误,等待5秒后重试..."); Thread.sleep(5000); urlQueue.add(currentUrl); // 重新加入队列 continue; } // 请求降速 Thread.sleep(2000 + rand.nextInt(3000));文件写入:
// 初始化文件 outputFile = new File("amazon_products_" + System.currentTimeMillis() + ".txt"); writer = new BufferedWriter(new FileWriter(outputFile)); writer.write("商品名称\t价格\t商品URL\n"); writer.write("----------------------------------------\n"); // 写入商品信息 writer.write(productName + "\t" + price + "\t" + url + "\n"); writer.flush();六、优化建议与总结
1. 当前实现特点
- 搜索范围:目前只爬取搜索结果第一页,因为第一页商品最相关,后续页面重复或不相关商品较多
- 适用场景:搜索具体商品时效果最佳,搜索品牌时可能需要爬取多页
最终爬取效果:
2. 可优化方向
- 多线程爬取:提高爬取效率
- 更智能的去重:除了商品编号,还可考虑商品名称相似度
- 异常处理增强:增加重试机制和更完善的错误处理
- 数据存储优化:考虑使用数据库而非文本文件存储
- 代理IP支持:应对更严格的反爬虫限制
3. 总结
本项目展示了一个基础的亚马逊商品爬虫实现,重点解决了反爬虫限制和页面信息提取两个核心问题。虽然实现相对简单,但包含了爬虫开发的关键技术点,适合作为学习Java网络爬虫的入门案例。对于实际生产环境,建议在此基础上增加多线程、代理池、分布式等高级特性。
