--- title: Price Monitoring App | Tabstack description: 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 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 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 ``` Configure Products → Extract Prices → Compare History → Generate Report ``` 1. **Configuration**: You define which products to monitor with their schemas 2. **Price Extraction**: We use `/v1/extract/json` to extract current pricing data 3. **History Tracking**: We compare against previous prices 4. **Reporting**: We generate a markdown report showing changes ## Project Setup Let’s build a simple but effective price monitoring system. ### Prerequisites - Node.js 18+ installed - A Tabstack API key ([get one here](https://console.tabstack.ai/)) - Basic command-line knowledge ### Step 1: Initialize Your Project Terminal window ``` # Create project directory mkdir price-monitor cd price-monitor # Initialize Node.js project npm init -y # Install dependencies npm install node-fetch dotenv ``` ### Step 2: Create Project Structure Terminal window ``` mkdir -p src config data touch .env config/products.json src/monitor.js src/extractor.js src/reporter.js ``` Your 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.json ``` ### Step 3: Configure Environment Create your `.env` file: Terminal window ``` TABSTACK_API_KEY=your_api_key_here TABSTACK_API_URL=https://api.tabstack.ai ``` **Update 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 ### 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 This module handles price extraction using the Tabstack API. Create `src/extractor.js`: 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 This module compares prices and generates reports. Create `src/reporter.js`: 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 Create `src/monitor.js`: 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 monitor const monitor = new PriceMonitor(); monitor.monitorAll().catch(console.error); ``` ## Running Your Price Monitor ### First Run Run the monitor to start tracking prices: Terminal window ``` npm run monitor ``` **Expected 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 On future runs, the monitor compares prices against history: Terminal window ``` npm run monitor ``` **Example 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 To check prices automatically, you can use cron (Linux/Mac) or Task Scheduler (Windows): **Using cron (Mac/Linux):** Terminal window ``` # Check prices every day at 9 AM 0 9 * * * cd /path/to/price-monitor && npm run monitor ``` **Using node-cron in your code:** Terminal window ``` npm install node-cron ``` Create `src/scheduler.js`: ``` import cron from "node-cron"; import { PriceMonitor } from "./monitor.js"; const monitor = new PriceMonitor(); // Run every day at 9 AM cron.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 This price monitor uses the `/v1/extract/json` endpoint: ### `/v1/extract/json` Endpoint Extracts structured data using the schema: ``` // Extracts data based on schema POST 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 ### ”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 - Review the schema in `products.json` - Adjust property descriptions to be more specific - Add more required fields to the schema ## Next Steps You now have a working price monitor! Here’s what to explore next: 1. **Add more products** to your `products.json` 2. **Set up scheduling** to run checks automatically 3. **Implement notifications** via email or Slack 4. **Build a dashboard** to visualize price trends 5. **Compare prices** across multiple retailers