Last month, a friend running a small e-commerce store asked me: "How do big companies track competitor prices? I can't afford $500/month tools."
I told him I'd build one for free. It took 30 minutes.
Here's exactly how I did it — and you can copy this approach for any niche.
Full code on GitHub: price-monitoring-free
The Problem
Price monitoring tools like Prisync ($99-399/mo) or Competera ($1000+/mo) are built for enterprises. Small business owners need something simpler:
Track 10-50 products across 3-5 competitor sites
Get notified when prices change
See price history over time
The Architecture (Dead Simple)
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Scheduler │────▶│ Scraper API │────▶│ JSON Store │
│ (cron/n8n) │ │ (sitemap + │ │ (GitHub) │
│ │ │ product │ │ │
└─────────────┘ │ pages) │ └──────┬──────┘
└──────────────┘ │
┌────▼─────┐
│ Notifier │
│ (webhook) │
└──────────┘
Step 1: Find Product URLs via Sitemap API
Most e-commerce sites expose their product URLs in XML sitemaps. Instead of guessing URLs, parse the sitemap:
const axios = require('axios');
const parseStringPromise = require('xml2js');
async function getProductUrls(domain) url.includes('/item'));
console.log(`Found $urls.length product pages on $domain`);
return urls;
const products = await getProductUrls('competitor-store.com');
// Found 847 product pages
Pro tip: Check robots.txt first — it often links to multiple sitemaps including product-specific ones like /sitemap-products.xml.
Step 2: Extract Prices with Structured Data
Here's the trick most people miss: e-commerce sites embed prices in JSON-LD structured data for Google. You don't need to parse HTML — just extract the JSON:
const cheerio = require('cheerio');
async function getPrice(url)
const data: html = await axios.get(url,
headers: 'User-Agent': 'Mozilla/5.0 (compatible; PriceBot/1.0)'
);
const $ = cheerio.load(html);
const jsonLd = $('script[type="application/ld+json"]')
.map((_, el) =>
try return JSON.parse($(el).html());
catch return null;
)
.get()
.find(d => d['@type'] === 'Product');
if (jsonLd?.offers)
return ;
return null;
Step 3: Store Price History in GitHub (Free)
Instead of paying for a database, use GitHub as a free JSON store:
const Octokit = require('@octokit/rest');
const octokit = new Octokit( auth: process.env.GITHUB_TOKEN );
async function savePriceData(prices)
const date = new Date().toISOString().split('T')[0];
const content = Buffer.from(JSON.stringify(prices, null, 2)).toString('base64');
await octokit.repos.createOrUpdateFileContents(
owner: 'your-username',
repo: 'price-tracker-data',
path: `data/$date.json`,
message: `Price snapshot $date`,
content
);
Step 4: Detect Changes & Notify
async function detectChanges(today, yesterday)
const changes = [];
for (const product of today)
const prev = yesterday.find(p => p.url === product.url);
if (prev && prev.price !== product.price)
const change = ((product.price - prev.price) / prev.price * 100).toFixed(1);
changes.push(
name: product.name,
oldPrice: prev.price,
newPrice: product.price,
change: `$change%`,
url: product.url
);
if (changes.length > 0)
await axios.post(process.env.WEBHOOK_URL,
content: `Price changes detected!\n` +
changes.map(c => `$c.name: $$c.oldPrice -> $$c.newPrice ($c.change)`).join('\n')
);
return changes;
Step 5: Automate with Cron
const competitors = ['store-a.com', 'store-b.com', 'store-c.com'];
async function run()
const allPrices = [];
for (const domain of competitors)
const urls = await getProductUrls(domain);
for (const url of urls.slice(0, 50))
const price = await getPrice(url);
if (price) allPrices.push(price);
await new Promise(r => setTimeout(r, 1000));
console.log(`Tracked $allPrices.length products`);
await savePriceData(allPrices);
run();
Add to cront
Tags:
#0
Want to run a more efficient business?
Mewayz gives you CRM, HR, Accounting, Projects & eCommerce — all in one workspace. 14-day free trial, no credit card needed.
Try Mewayz Free →