商品价格又双叒叕被套路了?这6个Java爬虫技巧帮你追踪所有电商价格历史!
商品价格又双叒叕被套路了?这6个Java爬虫技巧帮你追踪所有电商价格历史!
作为一名后端开发,我见过太多电商价格的"套路现场":
- 某购物节前商品价格先涨再降,"优惠"后比平时还贵100块
- 某知名品牌手机"限时特价"999元,实际上一个月前就是这个价
- 某电商平台"史上最低价"的笔记本,查了价格历史发现半年前更便宜
电商价格套路深如海,作为程序员的我们总不能坐以待毙吧?今天就教大家用Java写一个商品价格追踪系统,让所有价格猫腻无所遁形!
一、商品价格追踪系统到底是个啥?为啥这么有用?
商品价格追踪系统的核心就是:定期爬取电商网站的商品价格,记录价格变化历史,分析价格趋势。
为啥要做价格追踪?
- 省钱神器:知道商品的真实价格波动,避免被虚假促销套路
- 最佳时机:找到商品的价格低点,在最便宜的时候下手
- 商业价值:做电商数据分析,了解市场价格趋势
- 技术提升:爬虫、数据存储、前端展示,全栈技能get
二、Java爬虫构建价格追踪系统的6个核心技巧
技巧1:选对爬虫框架,事半功倍
别再用原始的HttpURLConnection了,现在有更好的选择!
推荐方案:Selenium + ChromeDriver(应对反爬) + Jsoup(解析HTML)
@Component
public class WebDriverManager {
private WebDriver driver;
@PostConstruct
public void initDriver() {
// 配置Chrome选项
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless"); // 无头模式
options.addArguments("--no-sandbox");
options.addArguments("--disable-dev-shm-usage");
options.addArguments("--disable-gpu");
options.addArguments("--window-size=1920,1080");
// 设置User-Agent,模拟真实浏览器
options.addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
// 禁用图片加载,提升速度
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.managed_default_content_settings.images", 2);
options.setExperimentalOption("prefs", prefs);
this.driver = new ChromeDriver(options);
// 设置隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
public WebDriver getDriver() {
return driver;
}
@PreDestroy
public void quitDriver() {
if (driver != null) {
driver.quit();
}
}
}
技巧2:智能解析商品信息,应对页面变化
不同电商网站的页面结构千差万别,要做好适配。
@Component
public class ProductInfoExtractor {
/**
* 京东商品信息提取
*/
public ProductInfo extractJDProduct(String url) {
WebDriver driver = webDriverManager.getDriver();
try {
driver.get(url);
// 等待价格元素加载
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
WebElement priceElement = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.cssSelector(".price-now, .p-price .price")
)
);
// 提取商品信息
String title = driver.findElement(By.cssSelector(".sku-name")).getText();
String priceText = priceElement.getText().replaceAll("[^\\d.]", "");
BigDecimal price = new BigDecimal(priceText);
String imageUrl = driver.findElement(By.cssSelector(".spec-img")).getAttribute("src");
return ProductInfo.builder()
.title(title)
.price(price)
.imageUrl(imageUrl)
.platform("京东")
.url(url)
.crawlTime(LocalDateTime.now())
.build();
} catch (Exception e) {
log.error("京东商品信息提取失败: {}", url, e);
throw new CrawlException("商品信息提取失败", e);
}
}
/**
* 淘宝商品信息提取
*/
public ProductInfo extractTaobaoProduct(String url) {
WebDriver driver = webDriverManager.getDriver();
try {
driver.get(url);
// 淘宝需要特殊处理,可能有登录弹窗
handleTaobaoPopup(driver);
// 等待价格加载
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
WebElement priceElement = wait.until(
ExpectedConditions.presenceOfElementLocated(
By.cssSelector(".notranslate, .tm-price-cur")
)
);
String title = driver.findElement(By.cssSelector(".tb-main-title")).getText();
String priceText = priceElement.getText().replaceAll("[^\\d.]", "");
BigDecimal price = new BigDecimal(priceText);
return ProductInfo.builder()
.title(title)
.price(price)
.platform("淘宝")
.url(url)
.crawlTime(LocalDateTime.now())
.build();
} catch (Exception e) {
log.error("淘宝商品信息提取失败: {}", url, e);
throw new CrawlException("商品信息提取失败", e);
}
}
/**
* 通用商品信息提取(自动识别平台)
*/
public ProductInfo extractProductInfo(String url) {
if (url.contains("jd.com")) {
return extractJDProduct(url);
} else if (url.contains("taobao.com") || url.contains("tmall.com")) {
return extractTaobaoProduct(url);
} else if (url.contains("amazon.cn")) {
return extractAmazonProduct(url);
} else {
throw new UnsupportedOperationException("暂不支持该电商平台: " + url);
}
}
}
技巧3:反爬虫策略,让你的爬虫更稳定
电商网站的反爬虫手段越来越厉害,我们要见招拆招。
@Component
public class AntiCrawlStrategy {
private final Random random = new Random();
private final List<String> userAgents = Arrays.asList(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
);
/**
* 随机延迟,模拟人工操作
*/
public void randomDelay() {
try {
int delay = 2000 + random.nextInt(3000); // 2-5秒随机延迟
Thread.sleep(delay);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* 随机切换User-Agent
*/
public void switchUserAgent(WebDriver driver) {
String userAgent = userAgents.get(random.nextInt(userAgents.size()));
((JavascriptExecutor) driver).executeScript(
"Object.defineProperty(navigator, 'userAgent', {get: () => '" + userAgent + "'})"
);
}
/**
* 模拟鼠标移动
*/
public void simulateHumanBehavior(WebDriver driver) {
Actions actions = new Actions(driver);
// 随机滚动页面
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollTo(0, " + random.nextInt(1000) + ")");
randomDelay();
// 随机移动鼠标
actions.moveByOffset(random.nextInt(100), random.nextInt(100)).perform();
}
/**
* 处理验证码(简单的图片验证码识别)
*/
public boolean handleCaptcha(WebDriver driver) {
try {
// 检查是否有验证码
List<WebElement> captchaElements = driver.findElements(
By.cssSelector(".captcha, .verify-code, .security-code")
);
if (!captchaElements.isEmpty()) {
log.warn("检测到验证码,尝试处理...");
// 这里可以集成第三方验证码识别服务
// 或者使用机器学习模型识别
// 暂时返回false,需要人工处理
return false;
}
return true;
} catch (Exception e) {
log.error("验证码处理失败", e);
return false;
}
}
}
技巧4:数据存储和历史追踪,记录每一次价格变化
价格历史数据是核心,存储设计要合理。
@Entity
@Table(name = "product_price_history")
public class ProductPriceHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "product_id")
private Long productId;
@Column(name = "price", precision = 10, scale = 2)
private BigDecimal price;
@Column(name = "original_price", precision = 10, scale = 2)
private BigDecimal originalPrice; // 原价
@Column(name = "discount_info")
private String discountInfo; // 促销信息
@Column(name = "stock_status")
private String stockStatus; // 库存状态
@Column(name = "crawl_time")
private LocalDateTime crawlTime;
@Column(name = "platform")
private String platform;
// getters and setters...
}
@Service
public class ProductPriceService {
@Autowired
private ProductPriceHistoryRepository priceHistoryRepository;
/**
* 保存价格历史
*/
public void savePriceHistory(ProductInfo productInfo) {
// 检查是否是新的价格变化
ProductPriceHistory lastRecord = priceHistoryRepository
.findTopByProductIdOrderByCrawlTimeDesc(productInfo.getProductId());
if (lastRecord == null || !lastRecord.getPrice().equals(productInfo.getPrice())) {
// 价格有变化,保存新记录
ProductPriceHistory history = new ProductPriceHistory();
history.setProductId(productInfo.getProductId());
history.setPrice(productInfo.getPrice());
history.setOriginalPrice(productInfo.getOriginalPrice());
history.setDiscountInfo(productInfo.getDiscountInfo());
history.setStockStatus(productInfo.getStockStatus());
history.setCrawlTime(productInfo.getCrawlTime());
history.setPlatform(productInfo.getPlatform());
priceHistoryRepository.save(history);
// 如果价格下降,发送通知
if (lastRecord != null && productInfo.getPrice().compareTo(lastRecord.getPrice()) < 0) {
sendPriceDropNotification(productInfo, lastRecord.getPrice());
}
log.info("价格变化记录已保存: 商品ID={}, 新价格={}, 旧价格={}",
productInfo.getProductId(),
productInfo.getPrice(),
lastRecord != null ? lastRecord.getPrice() : "无"
);
}
}
/**
* 获取价格历史趋势
*/
public List<PriceTrendDTO> getPriceTrend(Long productId, int days) {
LocalDateTime startTime = LocalDateTime.now().minusDays(days);
List<ProductPriceHistory> histories = priceHistoryRepository
.findByProductIdAndCrawlTimeAfterOrderByCrawlTime(productId, startTime);
return histories.stream()
.map(history -> PriceTrendDTO.builder()
.price(history.getPrice())
.crawlTime(history.getCrawlTime())
.discountInfo(history.getDiscountInfo())
.build())
.collect(Collectors.toList());
}
/**
* 分析价格趋势
*/
public PriceAnalysisResult analyzePriceTrend(Long productId) {
List<ProductPriceHistory> histories = priceHistoryRepository
.findByProductIdOrderByCrawlTime(productId);
if (histories.size() < 2) {
return PriceAnalysisResult.builder()
.trend("数据不足")
.build();
}
// 计算价格统计信息
List<BigDecimal> prices = histories.stream()
.map(ProductPriceHistory::getPrice)
.collect(Collectors.toList());
BigDecimal minPrice = prices.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
BigDecimal maxPrice = prices.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
BigDecimal avgPrice = prices.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(BigDecimal.valueOf(prices.size()), 2, RoundingMode.HALF_UP);
BigDecimal currentPrice = prices.get(prices.size() - 1);
BigDecimal previousPrice = prices.get(prices.size() - 2);
String trend = currentPrice.compareTo(previousPrice) > 0 ? "上涨" :
currentPrice.compareTo(previousPrice) < 0 ? "下降" : "稳定";
return PriceAnalysisResult.builder()
.minPrice(minPrice)
.maxPrice(maxPrice)
.avgPrice(avgPrice)
.currentPrice(currentPrice)
.trend(trend)
.priceDropPercentage(calculatePriceDropPercentage(currentPrice, maxPrice))
.build();
}
}
技巧5:定时任务和监控,全自动价格追踪
系统要能自动运行,定时抓取价格数据。
@Service
public class ProductCrawlScheduler {
@Autowired
private ProductInfoExtractor productInfoExtractor;
@Autowired
private ProductPriceService productPriceService;
@Autowired
private ProductRepository productRepository;
/**
* 定时抓取所有商品价格(每30分钟执行一次)
*/
@Scheduled(fixedRate = 1800000) // 30分钟
public void crawlAllProducts() {
log.info("开始定时抓取商品价格...");
List<Product> products = productRepository.findByStatusOrderByPriority("ACTIVE");
for (Product product : products) {
try {
crawlSingleProduct(product);
// 随机延迟,避免请求过于频繁
Thread.sleep(5000 + new Random().nextInt(10000));
} catch (Exception e) {
log.error("抓取商品价格失败: productId={}, url={}",
product.getId(), product.getUrl(), e);
}
}
log.info("定时抓取完成,共处理{}个商品", products.size());
}
/**
* 抓取单个商品价格
*/
private void crawlSingleProduct(Product product) {
try {
ProductInfo productInfo = productInfoExtractor.extractProductInfo(product.getUrl());
productInfo.setProductId(product.getId());
// 保存价格历史
productPriceService.savePriceHistory(productInfo);
// 更新商品基本信息
updateProductInfo(product, productInfo);
} catch (Exception e) {
// 记录失败次数
product.setFailCount(product.getFailCount() + 1);
if (product.getFailCount() >= 5) {
product.setStatus("FAILED");
log.warn("商品连续抓取失败5次,已暂停: productId={}", product.getId());
}
productRepository.save(product);
throw e;
}
}
/**
* 高优先级商品快速抓取(每5分钟执行一次)
*/
@Scheduled(fixedRate = 300000) // 5分钟
public void crawlHighPriorityProducts() {
List<Product> highPriorityProducts = productRepository
.findByStatusAndPriorityGreaterThanOrderByPriority("ACTIVE", 8);
for (Product product : highPriorityProducts) {
try {
crawlSingleProduct(product);
Thread.sleep(2000); // 高优先级商品间隔时间短一些
} catch (Exception e) {
log.error("高优先级商品抓取失败: productId={}", product.getId(), e);
}
}
}
/**
* 清理过期数据(每天凌晨2点执行)
*/
@Scheduled(cron = "0 0 2 * * ?")
public void cleanupOldData() {
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(90); // 保留90天数据
int deletedCount = priceHistoryRepository.deleteByCreateTimeBefore(cutoffTime);
log.info("清理了{}条过期价格记录", deletedCount);
}
}
技巧6:价格提醒和数据分析,让数据产生价值
光抓取数据还不够,要能及时提醒用户价格变化。
@Service
public class PriceAlertService {
@Autowired
private WechatMessageService wechatMessageService;
@Autowired
private EmailService emailService;
/**
* 发送价格下降通知
*/
public void sendPriceDropNotification(ProductInfo productInfo, BigDecimal oldPrice) {
BigDecimal dropAmount = oldPrice.subtract(productInfo.getPrice());
BigDecimal dropPercentage = dropAmount.divide(oldPrice, 4, RoundingMode.HALF_UP)
.multiply(BigDecimal.valueOf(100));
// 构建消息内容
String message = String.format(
"💰 价格下降提醒\n\n" +
"商品:%s\n" +
"原价:¥%s\n" +
"现价:¥%s\n" +
"下降:¥%s (%.1f%%)\n" +
"平台:%s\n\n" +
"快去看看吧!",
productInfo.getTitle(),
oldPrice,
productInfo.getPrice(),
dropAmount,
dropPercentage,
productInfo.getPlatform()
);
// 发送微信通知
wechatMessageService.sendTextMessage("price_alert_user", message, "app_agent_id");
// 发送邮件通知(如果价格下降超过10%)
if (dropPercentage.compareTo(BigDecimal.valueOf(10)) >= 0) {
sendEmailAlert(productInfo, oldPrice, dropAmount, dropPercentage);
}
}
/**
* 价格趋势分析和预测
*/
public PricePredictionResult predictPriceTrend(Long productId) {
List<ProductPriceHistory> histories = priceHistoryRepository
.findByProductIdOrderByCrawlTime(productId);
if (histories.size() < 10) {
return PricePredictionResult.builder()
.prediction("数据不足,无法预测")
.confidence(0.0)
.build();
}
// 简单的趋势分析(实际项目中可以用更复杂的算法)
List<BigDecimal> recentPrices = histories.stream()
.skip(Math.max(0, histories.size() - 10))
.map(ProductPriceHistory::getPrice)
.collect(Collectors.toList());
// 计算移动平均线
BigDecimal ma5 = calculateMovingAverage(recentPrices, 5);
BigDecimal ma10 = calculateMovingAverage(recentPrices, 10);
BigDecimal currentPrice = recentPrices.get(recentPrices.size() - 1);
String prediction;
double confidence;
if (currentPrice.compareTo(ma5) < 0 && ma5.compareTo(ma10) < 0) {
prediction = "价格可能继续下降,建议再等等";
confidence = 0.7;
} else if (currentPrice.compareTo(ma5) > 0 && ma5.compareTo(ma10) > 0) {
prediction = "价格可能上涨,建议尽快购买";
confidence = 0.6;
} else {
prediction = "价格相对稳定,可以考虑购买";
confidence = 0.5;
}
return PricePredictionResult.builder()
.prediction(prediction)
.confidence(confidence)
.ma5(ma5)
.ma10(ma10)
.build();
}
}
三、实战案例:从0到1搭建商品价格追踪系统
下面分享我之前搭建的一个完整的商品价格追踪系统案例。
系统架构设计
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端展示层 │ │ API网关层 │ │ 爬虫服务层 │
│ React + ECharts│───▶│ Spring Gateway │───▶│ Selenium │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
┌─────────────────┐ ┌─────────────────┐ │
│ 通知服务层 │ │ 数据服务层 │ │
│ 微信/邮件通知 │◀───│ Spring Boot │◀────────────┘
└─────────────────┘ └─────────────────┘
│
┌─────────────────┐
│ 数据存储层 │
│ MySQL + Redis │
└─────────────────┘
功能特性
- 多平台支持:京东、淘宝、天猫、亚马逊等主流电商
- 智能监控:自动检测价格变化,支持自定义监控频率
- 价格预警:价格下降自动通知,支持微信和邮件
- 数据分析:价格趋势图表,最低价提醒,购买建议
- 反爬虫对抗:模拟真实用户行为,突破各种限制
关键技术指标
- 监控商品数量:支持10万+商品同时监控
- 数据更新频率:高优先级商品5分钟,普通商品30分钟
- 价格变化检测:99%准确率,1分钟内发送通知
- 系统稳定性:7×24小时运行,可用率99.5%
四、6个避坑指南,90%的人都踩过!
1. 请求频率控制坑
问题:请求过于频繁被网站封IP
解决方案:
- 设置随机延迟,模拟人工操作
- 使用代理IP池轮换
- 分时段抓取,避开高峰期
2. 页面结构变化坑
问题:网站改版导致元素定位失效
解决方案:
- 使用多种选择器备选方案
- 定期检查和更新爬虫规则
- 实现自动重试和降级机制
3. 反爬虫验证码坑
问题:遇到验证码无法自动处理
解决方案:
- 集成第三方验证码识别服务
- 使用机器学习模型识别
- 人工介入处理复杂验证码
4. 数据存储性能坑
问题:大量历史数据导致查询缓慢
解决方案:
- 建立合适的数据库索引
- 定期清理过期数据
- 使用Redis缓存热点数据
5. 内存泄漏坑
问题:WebDriver长时间运行导致内存溢出
解决方案:
- 及时关闭WebDriver实例
- 定期重启爬虫进程
- 监控内存使用情况
6. 价格解析错误坑
问题:特殊价格格式导致解析失败
解决方案:
- 使用正则表达式严格过滤
- 多重校验价格合理性
- 记录异常价格便于排查
五、5个监控指标,确保系统稳定运行
1. 爬取成功率
- 目标值:>95%
- 告警阈值:<90%
2. 价格变化检测准确率
- 目标值:>99%
- 告警阈值:<95%
3. 系统响应时间
- 目标值:<2秒
- 告警阈值:>5秒
4. 数据更新及时性
- 目标值:延迟<5分钟
- 告警阈值:延迟>15分钟
5. 资源使用率
- CPU:<70%
- 内存:<80%
- 磁盘:<85%
六、总结:商品价格追踪系统的4个关键点
- 稳定性第一:做好反爬虫对抗,确保长期稳定运行
- 数据质量:准确解析商品信息,及时发现价格变化
- 用户体验:及时通知,直观展示,实用的购买建议
- 系统扩展性:支持多平台,易于添加新的电商网站
这套商品价格追踪系统我已经稳定运行1年多,追踪了5000多个商品,帮我省了不少钱。记住:技术的价值在于解决实际问题,用程序员的方式对抗商家的套路,这就是我们的优势!
如果你也想搭建自己的价格追踪系统,欢迎关注公众号服务端技术精选,一起交流爬虫技术和避坑经验!
标题:商品价格又双叒叕被套路了?这6个Java爬虫技巧帮你追踪所有电商价格历史!
作者:jiangyi
地址:http://jiangyi.space/articles/2025/12/21/1766304297476.html
- 一、商品价格追踪系统到底是个啥?为啥这么有用?
- 二、Java爬虫构建价格追踪系统的6个核心技巧
- 技巧1:选对爬虫框架,事半功倍
- 技巧2:智能解析商品信息,应对页面变化
- 技巧3:反爬虫策略,让你的爬虫更稳定
- 技巧4:数据存储和历史追踪,记录每一次价格变化
- 技巧5:定时任务和监控,全自动价格追踪
- 技巧6:价格提醒和数据分析,让数据产生价值
- 三、实战案例:从0到1搭建商品价格追踪系统
- 系统架构设计
- 功能特性
- 关键技术指标
- 四、6个避坑指南,90%的人都踩过!
- 1. 请求频率控制坑
- 2. 页面结构变化坑
- 3. 反爬虫验证码坑
- 4. 数据存储性能坑
- 5. 内存泄漏坑
- 6. 价格解析错误坑
- 五、5个监控指标,确保系统稳定运行
- 1. 爬取成功率
- 2. 价格变化检测准确率
- 3. 系统响应时间
- 4. 数据更新及时性
- 5. 资源使用率
- 六、总结:商品价格追踪系统的4个关键点
0 评论