Overview
Queries Amazon product listings daily for tracked competitor ASINs or product names, stores price history, and alerts when a price drops more than 10% from the previous day.
Trigger
Daily cron at 7 AM
Schedule
Daily at 7 AM (cron: 0 7 * * *)
Workflow Steps
Load tracked products
Read product names or search queries from a products table (product_name, asin, last_price, last_checked).
Search Amazon for each product
POST to Scavio search API with platform: amazon for each product query. Extract price, title, and URL from the top matching result.
Parse and normalize price
Strip currency symbols and convert price string to float. Handle 'from $X' and range prices by taking the minimum.
Compare to previous price
Load last_price from the database. Calculate price change percentage. Flag if drop exceeds 10%.
Store price history
Insert new row into price_history table with product_name, price, date, and url. Update last_price in products table.
Send alert for significant drops
For flagged products, send Slack webhook or email alert with product name, old price, new price, change percentage, and Amazon URL.
Python Implementation
import sqlite3
import requests
import re
from datetime import date
import time
DB_PATH = "price_monitor.db"
SCRAVIO_KEY = "YOUR_API_KEY"
ALERT_THRESHOLD = 0.10 # 10% drop
ALERT_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK"
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.executescript("""
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY, product_name TEXT, last_price REAL, last_checked TEXT
);
CREATE TABLE IF NOT EXISTS price_history (
product_name TEXT, price REAL, date TEXT, url TEXT
);
""")
conn.commit()
return conn
def parse_price(price_str: str) -> float | None:
if not price_str:
return None
match = re.search(r'[\d,]+\.?\d*', price_str.replace(',', ''))
return float(match.group()) if match else None
def fetch_amazon_price(query: str) -> dict | None:
resp = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": SCRAVIO_KEY},
json={"query": query, "platform": "amazon"}
)
resp.raise_for_status()
results = resp.json().get("results", [])
if not results:
return None
top = results[0]
return {"title": top.get("title"), "price": parse_price(top.get("price", "")), "url": top.get("url")}
def run():
conn = init_db()
today = date.today().isoformat()
products = conn.execute("SELECT id, product_name, last_price FROM products").fetchall()
for pid, name, last_price in products:
result = fetch_amazon_price(name)
if not result or result["price"] is None:
time.sleep(0.5)
continue
new_price = result["price"]
conn.execute("INSERT INTO price_history VALUES (?,?,?,?)", (name, new_price, today, result["url"]))
conn.execute("UPDATE products SET last_price=?, last_checked=? WHERE id=?", (new_price, today, pid))
if last_price and last_price > 0:
drop_pct = (last_price - new_price) / last_price
if drop_pct >= ALERT_THRESHOLD:
requests.post(ALERT_WEBHOOK, json={
"text": f"Price drop alert: {name} dropped {drop_pct*100:.1f}% from ${last_price:.2f} to ${new_price:.2f}. {result['url']}"
})
conn.commit()
time.sleep(0.5)
if __name__ == "__main__":
run()
JavaScript Implementation
const Database = require('better-sqlite3');
const fetch = require('node-fetch');
const DB_PATH = 'price_monitor.db';
const SCRAVIO_KEY = 'YOUR_API_KEY';
const ALERT_THRESHOLD = 0.10;
const ALERT_WEBHOOK = 'https://hooks.slack.com/services/YOUR/WEBHOOK';
const db = new Database(DB_PATH);
db.exec(`
CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, product_name TEXT, last_price REAL, last_checked TEXT);
CREATE TABLE IF NOT EXISTS price_history (product_name TEXT, price REAL, date TEXT, url TEXT);
`);
function parsePrice(str) {
if (!str) return null;
const m = str.replace(/,/g, '').match(/[\d]+\.?\d*/);
return m ? parseFloat(m[0]) : null;
}
async function fetchAmazonPrice(query) {
const res = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': SCRAVIO_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, platform: 'amazon' })
});
const data = await res.json();
const top = (data.results || [])[0];
if (!top) return null;
return { title: top.title, price: parsePrice(top.price), url: top.url };
}
async function run() {
const today = new Date().toISOString().slice(0, 10);
const products = db.prepare('SELECT id, product_name, last_price FROM products').all();
for (const { id, product_name, last_price } of products) {
const result = await fetchAmazonPrice(product_name);
if (!result || result.price == null) { await new Promise(r => setTimeout(r, 500)); continue; }
db.prepare('INSERT INTO price_history VALUES (?,?,?,?)').run(product_name, result.price, today, result.url);
db.prepare('UPDATE products SET last_price=?, last_checked=? WHERE id=?').run(result.price, today, id);
if (last_price && last_price > 0) {
const drop = (last_price - result.price) / last_price;
if (drop >= ALERT_THRESHOLD) {
await fetch(ALERT_WEBHOOK, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: `Price drop: ${product_name} dropped ${(drop*100).toFixed(1)}% from $${last_price.toFixed(2)} to $${result.price.toFixed(2)}. ${result.url}` })
});
}
}
await new Promise(r => setTimeout(r, 500));
}
}
run().catch(console.error);
Platforms Used
Amazon
Product search with prices, ratings, and reviews