Price Monitoring App
Track product prices across multiple retailers and get alerts when prices drop. This example shows you how to build an automated price monitoring system that saves you money by watching products 24/7.
Complexity: Beginner
Estimated time: 30-45 minutes
Key Tabstack APIs: /v1/extract/json
The Story Behind This Project
Section titled “The Story Behind This Project”You’re eyeing that new laptop, camera, or pair of sneakers, but the price is just a bit too high. You could check back every day, or you could build a smart price monitor that does it for you. This tool will automatically check prices, track history, and alert you when it’s time to buy.
What This Monitor Does
Section titled “What This Monitor Does”Unlike manual price checking or basic alerts, our price monitor will:
- Automatically discover product page structure using AI
- Extract pricing data from any e-commerce site
- Track price history over time
- Generate price change reports
- Identify the best time to buy based on trends
How It Works: The Price Tracking Pipeline
Section titled “How It Works: The Price Tracking Pipeline”Configure Products → Extract Prices → Compare History → Generate Report- Configuration: You define which products to monitor with their schemas
- Price Extraction: We use
/v1/extract/jsonto extract current pricing data - History Tracking: We compare against previous prices
- Reporting: We generate a markdown report showing changes
Project Setup
Section titled “Project Setup”Let’s build a simple but effective price monitoring system.
Prerequisites
Section titled “Prerequisites”- Node.js 18+ installed
- A Tabstack API key (get one here)
- Basic command-line knowledge
Step 1: Initialize Your Project
Section titled “Step 1: Initialize Your Project”# Create project directorymkdir price-monitorcd price-monitor
# Initialize Node.js projectnpm init -y
# Install dependenciesnpm install node-fetch dotenvStep 2: Create Project Structure
Section titled “Step 2: Create Project Structure”mkdir -p src config datatouch .env config/products.json src/monitor.js src/extractor.js src/reporter.jsYour project structure:
price-monitor/├── config/│ └── products.json # Products to monitor├── src/│ ├── monitor.js # Main monitoring script│ ├── extractor.js # Price extraction logic│ └── reporter.js # Report generation├── data/│ └── price-history.json # Historical price data├── .env # API key└── package.jsonStep 3: Configure Environment
Section titled “Step 3: Configure Environment”Create your .env file:
TABSTACK_API_KEY=your_api_key_hereTABSTACK_API_URL=https://api.tabstack.aiUpdate your package.json to enable ES modules and add scripts:
{ "name": "price-monitor", "version": "1.0.0", "type": "module", "scripts": { "monitor": "node src/monitor.js" }, "dependencies": { "node-fetch": "^3.3.2", "dotenv": "^16.3.1" }}Important: The "type": "module" line is required to use ES6 imports in Node.js.
Building Your Price Monitor
Section titled “Building Your Price Monitor”Step 1: Product Configuration
Section titled “Step 1: Product Configuration”First, let’s define which products we want to monitor. Create config/products.json:
{ "products": [ { "id": "laptop-dell-xps", "name": "Dell XPS 13 Laptop", "url": "https://www.dell.com/en-us/shop/dell-laptops/xps-13-laptop/spd/xps-13-9340-laptop", "targetPrice": 999, "schema": null }, { "id": "camera-sony-a7", "name": "Sony A7 IV Camera", "url": "https://www.bhphotovideo.com/c/product/1656859-REG/sony_ilce_7m4_a7_iv_mirrorless_camera.html", "targetPrice": 2299, "schema": null }, { "id": "shoes-nike-pegasus", "name": "Nike Pegasus 40 Running Shoes", "url": "https://www.nike.com/t/pegasus-40-mens-road-running-shoes-C8m9XV", "targetPrice": 100, "schema": null } ]}Step 2: Price Extractor - The Data Collector
Section titled “Step 2: Price Extractor - The Data Collector”This module handles price extraction using the Tabstack API.
Create src/extractor.js:
import fetch from "node-fetch";import dotenv from "dotenv";
dotenv.config();
const API_KEY = process.env.TABSTACK_API_KEY;const BASE_URL = process.env.TABSTACK_API_URL || "https://api.tabstack.ai";
export class PriceExtractor { constructor() { this.headers = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json", }; }
/** * Extract price data using a predefined schema */ async extractPrice(url, schema) { const response = await fetch(`${BASE_URL}/v1/extract/json`, { method: "POST", headers: this.headers, body: JSON.stringify({ url: url, json_schema: schema || this.getBasicPriceSchema(), }), });
if (!response.ok) { throw new Error(`Price extraction failed: ${response.statusText}`); }
const data = await response.json(); return { price: data.current_price || data.price, originalPrice: data.original_price, currency: data.currency || "USD", availability: data.availability || "unknown", discount: data.discount_percentage, extractedAt: new Date().toISOString(), }; }
/** * Get a basic schema for common e-commerce sites */ getBasicPriceSchema() { return { type: "object", properties: { product_name: { type: "string", description: "The product name or title", }, current_price: { type: "number", description: "Current selling price as a number", }, original_price: { type: "number", description: "Original price if the item is on sale", }, currency: { type: "string", description: "Currency code (USD, EUR, etc.)", }, availability: { type: "string", description: "Stock status (in stock, out of stock, limited, etc.)", }, discount_percentage: { type: "number", description: "Discount percentage if on sale", }, }, required: ["product_name", "current_price", "currency", "availability"], additionalProperties: false, }; }}Step 3: Reporter - The Alert Generator
Section titled “Step 3: Reporter - The Alert Generator”This module compares prices and generates reports.
Create src/reporter.js:
import { promises as fs } from "fs";
export class PriceReporter { constructor(historyFile = "./data/price-history.json") { this.historyFile = historyFile; }
/** * Load price history from file */ async loadHistory() { try { const data = await fs.readFile(this.historyFile, "utf8"); return JSON.parse(data); } catch (error) { // Return empty history if file doesn't exist return { products: {} }; } }
/** * Save price history to file */ async saveHistory(history) { await fs.mkdir("./data", { recursive: true }); await fs.writeFile( this.historyFile, JSON.stringify(history, null, 2), "utf8" ); }
/** * Add a new price point to history */ async addPricePoint(productId, priceData) { const history = await this.loadHistory();
if (!history.products[productId]) { history.products[productId] = { prices: [], lowestPrice: priceData.price, highestPrice: priceData.price, }; }
const productHistory = history.products[productId]; productHistory.prices.push(priceData);
// Update lowest/highest if (priceData.price < productHistory.lowestPrice) { productHistory.lowestPrice = priceData.price; } if (priceData.price > productHistory.highestPrice) { productHistory.highestPrice = priceData.price; }
await this.saveHistory(history); return productHistory; }
/** * Analyze price changes */ analyzePriceChange(productId, currentPrice, productHistory) { if (!productHistory || productHistory.prices.length === 0) { return { status: "new", message: "First time tracking this product", recommendation: "Monitor for a few days to establish baseline", }; }
const previousPrice = productHistory.prices[productHistory.prices.length - 1].price; const priceDiff = currentPrice - previousPrice; const percentChange = ((priceDiff / previousPrice) * 100).toFixed(2);
if (priceDiff === 0) { return { status: "unchanged", message: "Price unchanged", recommendation: "Continue monitoring", }; } else if (priceDiff < 0) { const analysis = { status: "decreased", message: `Price dropped by $${Math.abs(priceDiff).toFixed(2)} (${Math.abs(percentChange)}%)`, recommendation: currentPrice === productHistory.lowestPrice ? "🎉 LOWEST PRICE EVER - Consider buying now!" : "Good time to consider purchasing", };
// Check if it's a significant drop if (Math.abs(percentChange) >= 10) { analysis.alert = `🚨 SIGNIFICANT DROP: ${Math.abs(percentChange)}% off!`; }
return analysis; } else { return { status: "increased", message: `Price increased by $${priceDiff.toFixed(2)} (+${percentChange}%)`, recommendation: "Wait for price to drop", }; } }
/** * Generate a markdown report */ async generateReport(results, targetPrices = {}) { const timestamp = new Date().toLocaleString(); let markdown = `# Price Monitor Report\n\n*Generated: ${timestamp}*\n\n`; markdown += `## Summary\n\n`; markdown += `- **Products Monitored**: ${results.length}\n`;
const drops = results.filter( (r) => r.analysis?.status === "decreased" ).length; const increases = results.filter( (r) => r.analysis?.status === "increased" ).length;
markdown += `- **Price Drops**: ${drops}\n`; markdown += `- **Price Increases**: ${increases}\n\n`;
markdown += `---\n\n`;
for (const result of results) { const { product, priceData, analysis, history } = result; const targetPrice = targetPrices[product.id];
markdown += `## ${product.name}\n\n`;
// Current Status markdown += `**Current Price**: ${priceData.currency} $${priceData.price.toFixed(2)}\n\n`;
if ( priceData.originalPrice && priceData.originalPrice > priceData.price ) { const savings = priceData.originalPrice - priceData.price; markdown += `~~$${priceData.originalPrice.toFixed(2)}~~ **Save $${savings.toFixed(2)}**\n\n`; }
markdown += `**Availability**: ${priceData.availability}\n\n`;
// Price History if (history) { markdown += `**Price Range**:\n`; markdown += `- Lowest: $${history.lowestPrice.toFixed(2)}\n`; markdown += `- Highest: $${history.highestPrice.toFixed(2)}\n`; markdown += `- Tracked Points: ${history.prices.length}\n\n`; }
// Analysis if (analysis) { markdown += `**Analysis**: ${analysis.message}\n\n`;
if (analysis.alert) { markdown += `> ${analysis.alert}\n\n`; }
markdown += `**Recommendation**: ${analysis.recommendation}\n\n`; }
// Target Price Check if (targetPrice && priceData.price <= targetPrice) { markdown += `> 🎯 **TARGET PRICE MET!** This product is at or below your target price of $${targetPrice}\n\n`; }
markdown += `[View Product](${product.url})\n\n`; markdown += `---\n\n`; }
return markdown; }
/** * Save report to file */ async saveReport(markdown) { const filename = `price-report-${Date.now()}.md`; await fs.writeFile(filename, markdown, "utf8"); console.log(`\n💾 Report saved to: ${filename}`); return filename; }}Step 4: Main Monitor - Bringing It Together
Section titled “Step 4: Main Monitor - Bringing It Together”Create src/monitor.js:
import { promises as fs } from "fs";import { PriceExtractor } from "./extractor.js";import { PriceReporter } from "./reporter.js";import dotenv from "dotenv";
dotenv.config();
class PriceMonitor { constructor() { this.extractor = new PriceExtractor(); this.reporter = new PriceReporter(); }
/** * Load product configuration */ async loadProducts() { const data = await fs.readFile("./config/products.json", "utf8"); return JSON.parse(data); }
/** * Save updated product configuration */ async saveProducts(config) { await fs.writeFile( "./config/products.json", JSON.stringify(config, null, 2), "utf8" ); }
/** * Monitor all configured products */ async monitorAll() { console.log("🚀 Starting Price Monitor\n"); console.log("=".repeat(60));
const config = await this.loadProducts(); const results = [];
for (const product of config.products) { console.log(`\n📦 Monitoring: ${product.name}`); console.log(` URL: ${product.url}`);
try { // Extract current price using schema or default console.log(" 💰 Extracting price data..."); const priceData = await this.extractor.extractPrice( product.url, product.schema );
console.log( ` ✅ Current price: ${priceData.currency} $${priceData.price.toFixed(2)}` );
// Update price history const history = await this.reporter.addPricePoint( product.id, priceData );
// Analyze price change const analysis = this.reporter.analyzePriceChange( product.id, priceData.price, history );
console.log(` 📊 ${analysis.message}`);
if (analysis.alert) { console.log(` ${analysis.alert}`); }
results.push({ product, priceData, history, analysis, }); } catch (error) { console.error(` ❌ Error monitoring ${product.name}:`, error.message); results.push({ product, error: error.message, }); }
// Small delay between requests await new Promise((resolve) => setTimeout(resolve, 1000)); }
console.log("\n" + "=".repeat(60)); console.log("📋 Generating Report...\n");
// Generate report const targetPrices = {}; config.products.forEach((p) => { if (p.targetPrice) { targetPrices[p.id] = p.targetPrice; } });
const report = await this.reporter.generateReport(results, targetPrices);
// Display report to console console.log(report);
// Save to file await this.reporter.saveReport(report);
console.log("✅ Monitoring complete!\n"); }}
// Run the monitorconst monitor = new PriceMonitor();monitor.monitorAll().catch(console.error);Running Your Price Monitor
Section titled “Running Your Price Monitor”First Run
Section titled “First Run”Run the monitor to start tracking prices:
npm run monitorExpected Output:
🚀 Starting Price Monitor
============================================================
📦 Monitoring: Dell XPS 13 Laptop URL: https://www.dell.com/en-us/shop/dell-laptops/... 💰 Extracting price data... ✅ Current price: USD $1,099.99 📊 First time tracking this product
📦 Monitoring: Sony A7 IV Camera URL: https://www.bhphotovideo.com/c/product/... 💰 Extracting price data... ✅ Current price: USD $2,498.00 📊 First time tracking this product
============================================================📋 Generating Report...
# Price Monitor Report
*Generated: 1/15/2024, 3:45:23 PM*
## Summary
- **Products Monitored**: 3- **Price Drops**: 0- **Price Increases**: 0
---
## Dell XPS 13 Laptop
**Current Price**: USD $1,099.99
**Availability**: in stock
**Analysis**: First time tracking this product
**Recommendation**: Monitor for a few days to establish baseline
[View Product](https://www.dell.com/en-us/shop/dell-laptops/xps-13-laptop/spd/xps-13-9340-laptop)
---
💾 Report saved to: price-report-1705345523789.md✅ Monitoring complete!Subsequent Runs - Price Tracking
Section titled “Subsequent Runs - Price Tracking”On future runs, the monitor compares prices against history:
npm run monitorExample Output with Price Changes:
📦 Monitoring: Dell XPS 13 Laptop URL: https://www.dell.com/en-us/shop/dell-laptops/... 💰 Extracting price data... ✅ Current price: USD $999.99 📊 Price dropped by $100.00 (9.09%) 🚨 SIGNIFICANT DROP: 9.09% off!
## Dell XPS 13 Laptop
**Current Price**: USD $999.99
~~$1,099.99~~ **Save $100.00**
**Availability**: in stock
**Price Range**:- Lowest: $999.99- Highest: $1,099.99- Tracked Points: 5
**Analysis**: Price dropped by $100.00 (9.09%)
> 🚨 SIGNIFICANT DROP: 9.09% off!
**Recommendation**: 🎉 LOWEST PRICE EVER - Consider buying now!
> 🎯 **TARGET PRICE MET!** This product is at or below your target price of $999
[View Product](https://www.dell.com/en-us/shop/dell-laptops/xps-13-laptop/spd/xps-13-9340-laptop)Scheduling Regular Checks
Section titled “Scheduling Regular Checks”To check prices automatically, you can use cron (Linux/Mac) or Task Scheduler (Windows):
Using cron (Mac/Linux):
# Check prices every day at 9 AM0 9 * * * cd /path/to/price-monitor && npm run monitorUsing node-cron in your code:
npm install node-cronCreate src/scheduler.js:
import cron from "node-cron";import { PriceMonitor } from "./monitor.js";
const monitor = new PriceMonitor();
// Run every day at 9 AMcron.schedule("0 9 * * *", () => { console.log("⏰ Scheduled price check running..."); monitor.monitorAll().catch(console.error);});
console.log("📅 Price monitor scheduled - will run daily at 9 AM");Understanding the Tabstack API Endpoints
Section titled “Understanding the Tabstack API Endpoints”This price monitor uses the /v1/extract/json endpoint:
/v1/extract/json Endpoint
Section titled “/v1/extract/json Endpoint”Extracts structured data using the schema:
// Extracts data based on schemaPOST https://api.tabstack.ai/v1/extract/json{ "url": "https://example.com/product", "json_schema": { ... }}
// Returns extracted data:{ "current_price": 99.99, "product_name": "Example Product"}Troubleshooting
Section titled “Troubleshooting””Price extraction failed”
Section titled “”Price extraction failed””The page structure may not match the schema:
- Manually update the schema if you know the structure
- Check if the product URL is still valid
- Use the basic schema provided by
getBasicPriceSchema()
Incorrect prices extracted
Section titled “Incorrect prices extracted”- Review the schema in
products.json - Adjust property descriptions to be more specific
- Add more required fields to the schema
Next Steps
Section titled “Next Steps”You now have a working price monitor! Here’s what to explore next:
- Add more products to your
products.json - Set up scheduling to run checks automatically
- Implement notifications via email or Slack
- Build a dashboard to visualize price trends
- Compare prices across multiple retailers