← back to Wine Finder Next
scripts/daily-price-tracker.js
202 lines
#!/usr/bin/env node
/**
* Daily Wine Price Tracker
*
* This script runs daily to:
* 1. Fetch current prices from all wine sources
* 2. Log prices to Google Sheets with timestamp
* 3. Build historical price database
* 4. Detect price changes and send alerts
*
* Usage: node scripts/daily-price-tracker.js
* Cron: 0 8 * * * (daily at 8 AM)
*/
require('dotenv').config({ path: require('path').resolve(__dirname, '../.env') });
const vivinoScraper = require('../lib/services/vivinoScraper');
const totalWineScraper = require('../lib/services/totalWineScraper');
const klWineScraper = require('../lib/services/klWineScraper');
const googleSheets = require('../lib/services/googleSheets');
const slackNotifier = require('../lib/services/slackNotifier');
// Wines to track daily (add your favorites here)
const TRACKED_WINES = [
'Caymus Cabernet Sauvignon',
'Silver Oak Cabernet',
'Opus One',
'Screaming Eagle',
'Château Margaux',
'Dom Pérignon',
'Stag\'s Leap',
'Jordan Cabernet',
'Far Niente Chardonnay',
'Shafer Hillside Select'
];
// Generic popular searches to track market trends
const MARKET_SEARCHES = [
'cabernet sauvignon',
'pinot noir',
'chardonnay',
'merlot',
'sauvignon blanc',
'zinfandel'
];
async function trackDailyPrices() {
console.log('🍷 Starting Daily Wine Price Tracker');
console.log(`📅 ${new Date().toISOString()}`);
console.log('');
const results = {
tracked: 0,
logged: 0,
priceChanges: [],
errors: []
};
// Track specific wines
console.log('📊 Tracking Specific Wines...');
for (const wineName of TRACKED_WINES) {
try {
console.log(` Searching: ${wineName}`);
// Search all sources in parallel
const [vivinoResult, totalResult, klResult] = await Promise.all([
vivinoScraper.search(wineName).catch(e => ({ wines: [] })),
totalWineScraper.search(wineName).catch(e => ({ wines: [] })),
klWineScraper.search(wineName).catch(e => ({ wines: [] }))
]);
const allWines = [
...(vivinoResult.wines || []),
...(totalResult.wines || []),
...(klResult.wines || [])
];
if (allWines.length > 0) {
// Log to Google Sheets
const logResult = await googleSheets.logMultipleWines(allWines, wineName);
if (logResult.success) {
results.tracked += allWines.length;
results.logged += logResult.count || 0;
console.log(` ✅ Logged ${allWines.length} prices`);
}
// Check for price changes
for (const wine of allWines) {
const lastPrice = await googleSheets.getLastPrice(wine.name);
if (lastPrice && wine.price) {
const priceDiff = parseFloat(wine.price) - parseFloat(lastPrice.price);
const percentChange = (priceDiff / parseFloat(lastPrice.price)) * 100;
if (Math.abs(percentChange) >= 10) {
results.priceChanges.push({
wine: wine.name,
source: wine.source,
oldPrice: lastPrice.price,
newPrice: wine.price,
change: percentChange.toFixed(2),
url: wine.url
});
}
}
}
} else {
console.log(` ⚠️ No results found`);
}
// Rate limiting - wait 2s between searches
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
console.error(` ❌ Error tracking ${wineName}:`, error.message);
results.errors.push({ wine: wineName, error: error.message });
}
}
// Track market trends
console.log('');
console.log('📈 Tracking Market Trends...');
for (const searchTerm of MARKET_SEARCHES) {
try {
console.log(` Searching: ${searchTerm}`);
const vivinoResult = await vivinoScraper.search(searchTerm);
if (vivinoResult.wines && vivinoResult.wines.length > 0) {
// Take top 5 results for market tracking
const topWines = vivinoResult.wines.slice(0, 5);
const logResult = await googleSheets.logMultipleWines(topWines, searchTerm);
if (logResult.success) {
results.tracked += topWines.length;
results.logged += logResult.count || 0;
console.log(` ✅ Logged ${topWines.length} market prices`);
}
}
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error) {
console.error(` ❌ Error tracking ${searchTerm}:`, error.message);
results.errors.push({ search: searchTerm, error: error.message });
}
}
// Send summary report
console.log('');
console.log('📧 Sending Summary Report...');
const summary = {
date: new Date().toLocaleDateString(),
tracked: results.tracked,
logged: results.logged,
priceChanges: results.priceChanges.length,
errors: results.errors.length
};
console.log(` ✅ Tracked ${results.tracked} wines`);
console.log(` ✅ Logged ${results.logged} prices to Google Sheets`);
console.log(` 📊 Detected ${results.priceChanges.length} price changes`);
console.log(` ⚠️ ${results.errors.length} errors`);
// Send Slack notification if there are significant price changes
if (results.priceChanges.length > 0) {
const alerts = results.priceChanges.map(pc => ({
wine: pc.wine,
currentPrice: pc.newPrice,
lastPrice: pc.oldPrice,
percentageChange: pc.change,
source: pc.source,
url: pc.url
}));
await slackNotifier.sendAlerts(alerts);
console.log(` ✅ Sent Slack alerts for ${results.priceChanges.length} price changes`);
}
console.log('');
console.log('🎉 Daily Price Tracking Complete!');
return summary;
}
// Run the tracker
if (require.main === module) {
trackDailyPrices()
.then(summary => {
console.log('Summary:', summary);
process.exit(0);
})
.catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
}
module.exports = { trackDailyPrices, TRACKED_WINES, MARKET_SEARCHES };