AINode.jsAutomation
Product Price Monitor with AI Alerts
TT
TopicTrick Team
Product Price Monitor with AI Alerts
Never miss a price drop again. This tool monitors product prices on any e-commerce site, stores price history, and sends intelligent alerts when prices drop — with GPT-4o analysing whether it is actually a good deal.
This is Tool 9 of the Build 50 AI Automation Tools course.
What You'll Build
- Monitor multiple products across different sites
- Store price history in SQLite
- Detect price drops and send email/webhook alerts
- AI analysis of whether a price is genuinely good value
Setup
bash
mkdir price-monitor && cd price-monitor
npm init -y
npm install express puppeteer openai better-sqlite3 node-cron nodemailer dotenvbash
# .env
OPENAI_API_KEY=sk-your-key-here
SMTP_HOST=smtp.gmail.com
SMTP_USER=you@gmail.com
SMTP_PASS=your-app-password
ALERT_EMAIL=alerts@yourdomain.com
PORT=3000Database Setup
js
// src/db.js
import Database from 'better-sqlite3';
const db = new Database('prices.db');
db.exec(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
url TEXT UNIQUE NOT NULL,
target_price REAL,
alert_threshold REAL DEFAULT 0.05,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS price_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER REFERENCES products(id),
price REAL NOT NULL,
currency TEXT DEFAULT 'USD',
in_stock INTEGER DEFAULT 1,
checked_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
export const queries = {
addProduct: db.prepare('INSERT OR IGNORE INTO products (name, url, target_price, alert_threshold) VALUES (?, ?, ?, ?)'),
getProducts: db.prepare('SELECT * FROM products'),
logPrice: db.prepare('INSERT INTO price_history (product_id, price, currency, in_stock) VALUES (?, ?, ?, ?)'),
getLatestPrice: db.prepare('SELECT * FROM price_history WHERE product_id = ? ORDER BY checked_at DESC LIMIT 1'),
getPriceHistory: db.prepare('SELECT * FROM price_history WHERE product_id = ? ORDER BY checked_at DESC LIMIT 30'),
};
export default db;Price Extraction Service
js
// src/services/priceService.js
import puppeteer from 'puppeteer';
import OpenAI from 'openai';
import { queries } from '../db.js';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function extractPrice(url) {
const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
try {
const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36');
await page.goto(url, { waitUntil: 'networkidle2', timeout: 30_000 });
const text = await page.evaluate(() => {
document.querySelectorAll('script,style,nav,footer,header').forEach(el => el.remove());
return document.body?.innerText?.slice(0, 5000) || '';
});
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: `Extract price information from this product page text.
Return JSON: {"price": number, "currency": "USD", "inStock": true/false, "productName": "string"}
Return null for price if not found.`,
},
{ role: 'user', content: text },
],
response_format: { type: 'json_object' },
temperature: 0,
});
return JSON.parse(response.choices[0].message.content);
} finally {
await browser.close();
}
}
async function analyzePrice(product, currentPrice, history) {
if (history.length < 2) return null;
const prices = history.map(h => h.price);
const avg = prices.reduce((a, b) => a + b, 0) / prices.length;
const min = Math.min(...prices);
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{
role: 'system',
content: `Analyze this price data and give a buying recommendation.
Return JSON: {"recommendation": "buy_now | wait | good_deal", "reasoning": "string", "dealScore": 1-10}`,
},
{
role: 'user',
content: `Product: ${product.name}
Current price: $${currentPrice}
Average price (30 days): $${avg.toFixed(2)}
All-time low: $${min}
Target price: $${product.target_price || 'not set'}
Price history: ${JSON.stringify(prices)}`,
},
],
response_format: { type: 'json_object' },
temperature: 0.3,
});
return JSON.parse(response.choices[0].message.content);
}
export async function checkProduct(product) {
const priceData = await extractPrice(product.url);
if (!priceData?.price) return { success: false, error: 'Price not found' };
const lastPrice = queries.getLatestPrice.get(product.id);
queries.logPrice.run(product.id, priceData.price, priceData.currency || 'USD', priceData.inStock ? 1 : 0);
const history = queries.getPriceHistory.all(product.id);
const analysis = await analyzePrice(product, priceData.price, history);
const priceDrop = lastPrice ? (lastPrice.price - priceData.price) / lastPrice.price : 0;
const isAlert = priceDrop >= (product.alert_threshold || 0.05) ||
(product.target_price && priceData.price <= product.target_price);
return {
product: product.name,
currentPrice: priceData.price,
lastPrice: lastPrice?.price,
priceChange: priceDrop,
inStock: priceData.inStock,
analysis,
shouldAlert: isAlert,
};
}
export async function checkAllProducts() {
const products = queries.getProducts.all();
const results = [];
for (const product of products) {
try {
const result = await checkProduct(product);
results.push({ success: true, ...result });
} catch (err) {
results.push({ success: false, product: product.name, error: err.message });
}
await new Promise(r => setTimeout(r, 2000 + Math.random() * 2000));
}
return results;
}Server + Scheduler
js
// src/server.js
import 'dotenv/config';
import express from 'express';
import cron from 'node-cron';
import { checkAllProducts, checkProduct } from './services/priceService.js';
import { queries } from './db.js';
const app = express();
app.use(express.json());
app.post('/products', (req, res) => {
const { name, url, targetPrice, alertThreshold } = req.body;
if (!name || !url) return res.status(400).json({ error: 'name and url required' });
queries.addProduct.run(name, url, targetPrice || null, alertThreshold || 0.05);
res.json({ success: true, message: `Monitoring ${name}` });
});
app.get('/products', (req, res) => {
const products = queries.getProducts.all();
res.json({ products });
});
app.post('/check', async (req, res, next) => {
try {
const results = await checkAllProducts();
res.json({ success: true, results });
} catch (err) { next(err); }
});
// Check every 6 hours
cron.schedule('0 */6 * * *', async () => {
console.log('Checking prices...');
const results = await checkAllProducts();
const alerts = results.filter(r => r.shouldAlert);
if (alerts.length > 0) {
console.log(`ALERT: ${alerts.length} price drops detected`);
// Send email alerts here
}
});
app.use((err, _req, res, _next) => res.status(500).json({ error: err.message }));
app.listen(process.env.PORT ?? 3000, () => console.log('Price Monitor running'));Testing
bash
# Add a product to monitor
curl -X POST http://localhost:3000/products \
-H "Content-Type: application/json" \
-d '{"name": "Sony WH-1000XM5", "url": "https://example-store.com/product/sony-wh1000xm5", "targetPrice": 280}'
# Trigger an immediate check
curl -X POST http://localhost:3000/checkSample alert response:
json
{
"product": "Sony WH-1000XM5",
"currentPrice": 249.99,
"lastPrice": 349.99,
"priceChange": 0.286,
"inStock": true,
"analysis": {
"recommendation": "buy_now",
"reasoning": "Price dropped 28.6% — lowest in 30 days and below your target price of $280",
"dealScore": 9
},
"shouldAlert": true
}Build 50 AI Automation Tools — Tool 9 of 50
Price monitoring is live. Continue to Tool 10 to build an AI SEO content analyzer.
Summary
- SQLite with better-sqlite3 stores price history with zero setup — perfect for local monitoring tools
- AI price extraction works across any e-commerce site without site-specific selectors
- AI deal analysis scores whether the current price is genuinely worth buying
- node-cron runs checks every 6 hours automatically in the background
- Extend with Nodemailer for email alerts or Twilio for SMS when price targets are hit
Continue to Tool 10: SEO Content Analyzer →
Post Navigation (Previous/Next)
