Overview
Searches Google daily for competitor pricing pages, extracts pricing tier information from SERP snippets, and writes structured pricing data to a dashboard-ready database table.
Trigger
Daily cron at 6 AM
Schedule
Daily at 6 AM (cron: 0 6 * * *)
Workflow Steps
Load competitor list
Read competitor names and domains from a competitors table. These are the companies to track pricing for.
Search for pricing pages
For each competitor, POST to Scavio search API with query '[competitor name] pricing 2026 site:[competitor domain]'. Extract top result URL and snippet.
Parse pricing from snippet
Use regex and keyword matching to extract price mentions from the snippet: dollar amounts, 'per month', 'per year', 'free', 'enterprise'. Structure as tier objects.
Store structured pricing data
Insert competitor_name, date, raw_snippet, parsed_tiers (JSON), source_url into competitor_pricing table.
Flag pricing changes
Compare today's parsed_tiers against yesterday's for each competitor. If tiers differ, flag as changed and store diff.
Update dashboard
The competitor_pricing table is queried by the dashboard directly. Optionally send a daily summary of any pricing changes detected.
Python Implementation
import sqlite3
import requests
import re
import json
from datetime import date, timedelta
import time
DB_PATH = "competitor_pricing.db"
SCRAVIO_KEY = "YOUR_API_KEY"
def init_db():
conn = sqlite3.connect(DB_PATH)
conn.executescript("""
CREATE TABLE IF NOT EXISTS competitors (name TEXT, domain TEXT);
CREATE TABLE IF NOT EXISTS competitor_pricing (
competitor TEXT, date TEXT, raw_snippet TEXT,
parsed_tiers TEXT, source_url TEXT, changed INTEGER DEFAULT 0,
PRIMARY KEY (competitor, date)
);
""")
conn.commit()
return conn
def search_pricing(name: str, domain: str) -> dict | None:
resp = requests.post(
"https://api.scavio.dev/api/v1/search",
headers={"x-api-key": SCRAVIO_KEY},
json={"query": f"{name} pricing 2026 site:{domain}", "platform": "google", "num": 3}
)
resp.raise_for_status()
results = resp.json().get("results", [])
if not results:
return None
r = results[0]
return {"snippet": r.get("snippet", ""), "url": r.get("url", "")}
def parse_pricing_tiers(snippet: str) -> list:
tiers = []
# Find price mentions: $X/mo, $X/month, $X/year, $X per month
price_pattern = r'\$([\d,]+)(?:\.\d+)?(?:/mo|/month|/year|\s+per\s+month|\s+per\s+year)?'
prices = re.findall(price_pattern, snippet, re.IGNORECASE)
if "free" in snippet.lower():
tiers.append({"tier": "free", "price": 0})
for i, price in enumerate(prices):
price_val = float(price.replace(',', ''))
tiers.append({"tier": f"tier_{i+1}", "price": price_val})
if "enterprise" in snippet.lower() or "contact" in snippet.lower():
tiers.append({"tier": "enterprise", "price": None})
return tiers
def run():
conn = init_db()
today = date.today().isoformat()
yesterday = (date.today() - timedelta(days=1)).isoformat()
competitors = conn.execute("SELECT name, domain FROM competitors").fetchall()
changes = []
for name, domain in competitors:
result = search_pricing(name, domain)
if not result:
time.sleep(0.5)
continue
tiers = parse_pricing_tiers(result["snippet"])
tiers_json = json.dumps(tiers)
prev = conn.execute(
"SELECT parsed_tiers FROM competitor_pricing WHERE competitor=? AND date=?",
(name, yesterday)
).fetchone()
changed = 0
if prev and prev[0] != tiers_json:
changed = 1
changes.append(f"{name}: pricing tiers changed")
conn.execute(
"INSERT OR REPLACE INTO competitor_pricing VALUES (?,?,?,?,?,?)",
(name, today, result["snippet"], tiers_json, result["url"], changed)
)
conn.commit()
time.sleep(0.5)
if changes:
print("Pricing changes detected:", changes)
if __name__ == "__main__":
run()
JavaScript Implementation
const Database = require('better-sqlite3');
const fetch = require('node-fetch');
const DB_PATH = 'competitor_pricing.db';
const SCRAVIO_KEY = 'YOUR_API_KEY';
const db = new Database(DB_PATH);
db.exec(`
CREATE TABLE IF NOT EXISTS competitors (name TEXT, domain TEXT);
CREATE TABLE IF NOT EXISTS competitor_pricing (competitor TEXT, date TEXT, raw_snippet TEXT, parsed_tiers TEXT, source_url TEXT, changed INTEGER DEFAULT 0, PRIMARY KEY (competitor, date));
`);
async function searchPricing(name, domain) {
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: `${name} pricing 2026 site:${domain}`, platform: 'google', num: 3 })
});
const results = (await res.json()).results || [];
if (!results.length) return null;
return { snippet: results[0].snippet || '', url: results[0].url || '' };
}
function parseTiers(snippet) {
const tiers = [];
if (/free/i.test(snippet)) tiers.push({ tier: 'free', price: 0 });
const prices = [...snippet.matchAll(/\$([\d,]+)(?:\.\d+)?/g)].map(m => parseFloat(m[1].replace(/,/g,'')));
prices.forEach((p, i) => tiers.push({ tier: `tier_${i+1}`, price: p }));
if (/enterprise|contact/i.test(snippet)) tiers.push({ tier: 'enterprise', price: null });
return tiers;
}
async function run() {
const today = new Date().toISOString().slice(0,10);
const yesterday = new Date(Date.now()-86400000).toISOString().slice(0,10);
const competitors = db.prepare('SELECT name, domain FROM competitors').all();
for (const { name, domain } of competitors) {
const result = await searchPricing(name, domain);
if (!result) { await new Promise(r=>setTimeout(r,500)); continue; }
const tiers = JSON.stringify(parseTiers(result.snippet));
const prev = db.prepare('SELECT parsed_tiers FROM competitor_pricing WHERE competitor=? AND date=?').get(name, yesterday);
const changed = prev && prev.parsed_tiers !== tiers ? 1 : 0;
if (changed) console.log(`${name}: pricing changed`);
db.prepare('INSERT OR REPLACE INTO competitor_pricing VALUES (?,?,?,?,?,?)').run(name, today, result.snippet, tiers, result.url, changed);
await new Promise(r=>setTimeout(r,500));
}
}
run().catch(console.error);
Platforms Used
Web search with knowledge graph, PAA, and AI overviews