SaaS pricing changes constantly. If you manage software procurement or build comparison tools, tracking these changes automatically saves significant time and money.
The Opportunity
SaaS pricing pages are public but unstructured. By scraping them regularly, you can detect price increases before renewals, build competitive intelligence, and create comparison tools.
Core Setup
import requests
from bs4 import BeautifulSoup
import json
import hashlib
from datetime import datetime
from pathlib import Path
import re
import time
import random
SCRAPER_KEY = "YOUR_SCRAPERAPI_KEY"
def fetch_page(url):
resp = requests.get(
"http://api.scraperapi.com",
params={"api_key": SCRAPER_KEY, "url": url, "render": "true"},
timeout=30
)
return BeautifulSoup(resp.text, "html.parser")
Scraping Pricing Pages
SAAS_WATCHLIST = [
{"name": "Slack", "url": "https://slack.com/pricing", "category": "communication"},
{"name": "Notion", "url": "https://www.notion.so/pricing", "category": "productivity"},
{"name": "Linear", "url": "https://linear.app/pricing", "category": "project_mgmt"},
{"name": "Vercel", "url": "https://vercel.com/pricing", "category": "hosting"},
]
def scrape_pricing_page(saas_entry):
soup = fetch_page(saas_entry["url"])
pricing_elements = soup.find_all(
["div", "section"],
class_=lambda c: c and any(kw in str(c).lower() for kw in ["pricing", "plan", "tier"])
)
plans = []
for el in pricing_elements:
plan_name = el.find(["h2", "h3", "h4"])
price_el = el.find(string=lambda t: t and "$" in t if t else False)
if plan_name and price_el:
price = extract_price(price_el)
features = [li.text.strip() for li in el.find_all("li")[:10]]
plans.append({
"plan_name": plan_name.text.strip(),
"price": price,
"features": features
})
return {
"saas": saas_entry["name"],
"plans": plans,
"scraped_at": datetime.now().isoformat(),
"page_hash": hashlib.md5(soup.text.encode()).hexdigest()
}
def extract_price(text):
match = re.search(r"\$([\d,]+\.?\d*)", str(text))
return float(match.group(1).replace(",", "")) if match else None
Change Detection
class PricingTracker:
def __init__(self, storage_dir):
self.storage_dir = Path(storage_dir)
self.storage_dir.mkdir(exist_ok=True)
def _history_path(self, saas_name):
return self.storage_dir / f"{saas_name.lower().replace(' ', '_')}.json"
def save_snapshot(self, pricing_data):
path = self._history_path(pricing_data["saas"])
history = json.loads(path.read_text()) if path.exists() else []
history.append(pricing_data)
path.write_text(json.dumps(history, indent=2))
def detect_changes(self, current_data):
path = self._history_path(current_data["saas"])
if not path.exists():
return {"type": "NEW", "saas": current_data["saas"]}
previous = json.loads(path.read_text())[-1]
if previous["page_hash"] == current_data["page_hash"]:
return None
changes = []
prev_plans = {p["plan_name"]: p for p in previous.get("plans", [])}
curr_plans = {p["plan_name"]: p for p in current_data.get("plans", [])}
for plan_name, curr_plan in curr_plans.items():
prev_plan = prev_plans.get(plan_name)
if not prev_plan:
changes.append({"plan": plan_name, "change": "NEW_PLAN"})
elif prev_plan["price"] != curr_plan["price"]:
pct = round(((curr_plan["price"] - prev_plan["price"]) / prev_plan["price"]) * 100, 1) if prev_plan["price"] else None
changes.append({
"plan": plan_name, "change": "PRICE_CHANGE",
"old_price": prev_plan["price"], "new_price": curr_plan["price"],
"pct_change": pct
})
return {"saas": current_data["saas"], "changes": changes} if changes else None
Running the Scan
def run_pricing_scan(watchlist):
tracker = PricingTracker("pricing_data")
for entry in watchlist:
try:
data = scrape_pricing_page(entry)
change = tracker.detect_changes(data)
tracker.save_snapshot(data)
if change and change.get("changes"):
for c in change["changes"]:
if c["change"] == "PRICE_CHANGE":
print(f"[CHANGE] {change['saas']} - {c['plan']}: ${c['old_price']} -> ${c['new_price']}")
time.sleep(random.uniform(3, 6))
except Exception as e:
print(f"[ERROR] {entry['name']}: {e}")
Proxy Infrastructure
- ScraperAPI — JavaScript rendering for SPA pricing pages
- ThorData — Residential proxies for aggressive bot detection
- ScrapeOps — Monitor success rates across 100+ domains
Conclusion
Automated SaaS pricing tracking turns a tedious manual process into a competitive advantage. Start with your top 10 vendors, validate the scraper, then expand.
Top comments (0)