Web Scraping for Price Monitoring
Scrape competitor prices, track e-commerce changes, and trigger alerts when prices shift across markets with fastCRW — structured, timestamped data at scale.
fastCRW is ideal for continuous price tracking at scale. Render JavaScript for dynamic pricing, extract structured data via JSON schemas, and build alerts on top of clean, timestamped data.
Verdict
Price monitoring is one of the highest-ROI scraping use cases. Retailers adjust prices daily. Marketplaces run dynamic promotions. Missing a price drop or competitor move can cost thousands in lost sales or margin. fastCRW solves this by making it cheap and easy to track prices at scale: render JavaScript to capture dynamic pricing, extract structured data into comparable fields, and build alerts on top of timestamped snapshots.
Why This Matters
Price is the most volatile and value-sensitive signal in e-commerce. Manual price tracking does not scale past 10–20 products. By the time an analyst checks a competitor's site, the price may have changed three times.
Automated price monitoring gives you:
- continuous tracking without analyst overhead,
- real-time alerts when competitors move,
- historical data to detect seasonal and trend-based pricing patterns,
- and the ability to optimize your own prices based on competitor signals.
Real-world impact:
- Retailers adjust prices upward in slow sales periods and downward when inventory builds. Missing a 20% drop can cost you a sale.
- Marketplaces (Amazon, eBay) change prices hourly based on demand. Price intelligence informs repricing strategy.
- SaaS and subscriptions often discount during promotional windows. Tracking competitor discounts keeps you competitive.
- Travel and hospitality prices fluctuate by day and occupancy. Real-time alerts tell you when to undercut or hold firm.
Where fastCRW Helps
| Monitoring need | fastCRW role |
|---|---|
| Competitor pricing pages | scrape with Chrome rendering to extract dynamic prices |
| Marketplace listings | scrape Amazon, eBay, Shopify product pages with JSON extraction |
| Price change detection | Store snapshots, diff against baseline, trigger alerts |
| Multi-source aggregation | crawl a site's category pages, scrape each product with structured extraction |
| Historical trend analysis | Build time-series database of prices, detect seasonality and promotions |
Typical Flow
- Start with a list of competitor product URLs or category pages.
- For each URL, scrape with Chrome rendering and structured extraction (JSON schema).
- Store the result (price, availability, currency, timestamp) in a database.
- On the next scheduled scrape (daily, hourly, or per-minute for high-velocity markets), compare new prices against the baseline.
- Detect changes: price drop, price increase, out-of-stock, new competitor, promotion ended.
- Trigger alerts via email, Slack, SMS, or webhook.
- Log all changes to a time-series database for trend analysis and historical replay.
Good Fits
- E-commerce teams tracking competitor prices and adjusting their own pricing engine,
- Marketplace sellers (Amazon, eBay, Shopify) monitoring their category and pricing rank,
- Price comparison sites (PriceGrabber, Honey, CamelCamelCamel) aggregating prices across retailers,
- Dynamic pricing engines feeding real-time competitor data into ML pricing models,
- and brand teams protecting MAP (Minimum Advertised Price) by monitoring unauthorized discounting.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Price Monitoring Pipeline with fastCRW │
└─────────────────────────────────────────────────────────────┘
[Competitor URLs] ──→ [fastCRW Scrape] ──→ [Extract JSON]
(Chrome render) (Pydantic schema)
│
↓
[Time-Series DB] ←── [Diff & Compare] ←── [Store + Timestamp]
(Detect changes)
│
↓
[Alerts & Notifications] ←── [Alert Logic]
(Email, Slack, Webhook) (Price drop, discount, OOS)
│
↓
[Dashboard] ←── [Historical Analysis]
(Trends, seasonality)
Key Components
Scrape layer: Use fastCRW's Chrome rendering (renderJs: true) to render dynamic pricing, countdown timers, flash sales, and real-time stock status.
Extraction layer: Define a reusable JSON schema (Pydantic-style) for price fields. Include product ID, name, original price, sale price, currency, discount percentage, availability, and timestamp.
Storage layer: PostgreSQL or SQLite with a price_snapshots table (product_id, url, price, timestamp). Query by product_id to get historical price series.
Diff layer: On each new scrape, load the previous snapshot and compare fields. Flag which fields changed.
Alert layer: Rules engine that triggers notifications based on change thresholds (price dropped >10%, price increased while inventory high, competitor entered market, price below threshold).
Implementation Walkthrough
Here's a working example of a daily price monitor for e-commerce products. This code:
- Scrapes a list of competitor product URLs
- Extracts prices using structured JSON extraction
- Stores results with timestamps
- Detects price changes
- Triggers alerts (email placeholder)
import requests
import json
from datetime import datetime
import sqlite3
from typing import Optional
# Initialize database
conn = sqlite3.connect("prices.db")
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS price_snapshots (
id INTEGER PRIMARY KEY,
product_id TEXT,
product_name TEXT,
url TEXT,
price REAL,
original_price REAL,
currency TEXT,
in_stock BOOLEAN,
discount_pct REAL,
scraped_at TIMESTAMP
)
""")
conn.commit()
# Define extraction schema for pricing
PRICE_SCHEMA = {
"type": "object",
"properties": {
"product_name": {"type": "string"},
"product_id": {"type": "string"},
"current_price": {"type": "number"},
"original_price": {"type": "number"},
"discount_percentage": {"type": "number"},
"currency": {"type": "string"},
"in_stock": {"type": "boolean"}
},
"required": ["product_name", "current_price", "currency"]
}
# Function to scrape a product page
def scrape_product_price(url: str) -> Optional[dict]:
"""Scrape a product page and extract price data."""
api_key = "your_fastcrw_api_key"
response = requests.post(
"https://api.fastcrw.com/v1/scrape",
headers={"Authorization": f"Bearer {api_key}"},
json={
"url": url,
"renderJs": True, # Enable Chrome rendering for dynamic content
"formats": ["json"],
"jsonSchema": PRICE_SCHEMA
}
)
if response.status_code == 200:
data = response.json()
if data.get("success"):
return data.get("data", {}).get("json")
print(f"Failed to scrape {url}: {response.status_code}")
return None
# Function to detect price changes
def detect_price_change(product_id: str, new_price: float) -> Optional[dict]:
"""Compare new price against the most recent baseline."""
cursor.execute("""
SELECT price, scraped_at FROM price_snapshots
WHERE product_id = ?
ORDER BY scraped_at DESC
LIMIT 1
""", (product_id,))
result = cursor.fetchone()
if not result:
return None # No baseline yet
old_price, old_time = result
change_pct = ((new_price - old_price) / old_price * 100) if old_price > 0 else 0
if abs(change_pct) > 0.5: # Alert if price changed by >0.5%
return {
"old_price": old_price,
"new_price": new_price,
"change_pct": round(change_pct, 2),
"previous_scraped_at": old_time
}
return None
# Function to store and alert
def process_product(url: str):
"""Scrape, store, detect changes, and alert."""
extracted = scrape_product_price(url)
if not extracted:
print(f"Skipping {url}: extraction failed")
return
# Extract fields from the structured JSON
product_id = extracted.get("product_id", "unknown")
product_name = extracted.get("product_name", "Unknown")
current_price = extracted.get("current_price")
original_price = extracted.get("original_price")
currency = extracted.get("currency", "USD")
in_stock = extracted.get("in_stock", True)
discount_pct = extracted.get("discount_percentage", 0)
# Store the snapshot
cursor.execute("""
INSERT INTO price_snapshots
(product_id, product_name, url, price, original_price, currency, in_stock, discount_pct, scraped_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (product_id, product_name, url, current_price, original_price, currency, in_stock, discount_pct, datetime.now()))
conn.commit()
# Detect change
change = detect_price_change(product_id, current_price)
if change:
print(f"ALERT: {product_name} ({product_id}) changed by {change['change_pct']}%")
print(f" Old: {currency} {change['old_price']:.2f} → New: {currency} {current_price:.2f}")
# Trigger notification (email, Slack, webhook, etc.)
send_alert(product_name, change, currency)
else:
print(f"OK: {product_name} ({product_id}) - {currency} {current_price:.2f}")
# Placeholder alert function
def send_alert(product_name: str, change: dict, currency: str):
"""Send alert via email, Slack, or webhook."""
# TODO: Implement email/Slack/webhook notification
print(f" → Sending alert for {product_name}")
# Main loop
if __name__ == "__main__":
competitor_urls = [
"https://your-target-site.com/product/widget-a",
"https://competitor-a.example.com/product/gadget-b",
"https://competitor-b.example.com/product/B000000001",
]
for url in competitor_urls:
process_product(url)
conn.close()
How it works:
- Scrape:
POST /v1/scrapewithrenderJs: Trueand a JSON schema for extraction. - Extract: fastCRW parses the rendered HTML and extracts structured data into your schema.
- Store: Save the extracted fields with a timestamp to the database.
- Diff: Query the previous baseline and calculate percentage change.
- Alert: If change exceeds threshold (0.5%), send a notification.
Cost estimate: 100 products × 30 days = 3,000 scrapes/month. Every renderer (auto, LightPanda, http, Chrome) costs 1 credit, so that is 3,000 credits/month regardless of renderer — fits the Hobby plan ($13/mo — launch price, was $19, 3,000 credits) exactly.
Production Considerations
Rate Limiting and Respect
E-commerce sites are vigilant about bot traffic. To avoid blocks:
- Space requests: 30–60 seconds between requests to the same domain.
- Rotate User-Agents: Use realistic browser User-Agent strings.
- Respect robots.txt: fastCRW respects robots.txt by default; only override it when you have a legitimate right to. Review
/robots.txtbefore adding new URLs. - Use the proxy pool: For high-frequency scraping (>10 requests/hour per domain), enable fastCRW's rotating proxy pool.
- Monitor HTTP status codes: 429 (rate limited), 403 (forbidden), 503 (temporarily unavailable) indicate you need to slow down.
Handling Anti-Bot Protection
Amazon, Shopify, and other platforms use anti-bot measures:
- CAPTCHA: fastCRW cannot solve CAPTCHAs. If a page behind CAPTCHA becomes your primary data source, consider their official API.
- Cloudflare: fastCRW's Chrome rendering handles most Cloudflare challenges automatically.
- Fingerprinting: Cloudflare and others detect browser automation. Chrome rendering mimics a real browser, but high request volume will still trigger blocks. Use the rotating proxy pool and slow your request rate.
- IP rotation: The proxy pool rotates IPs per request. For high-velocity scraping, enable per-domain IP rotation.
JavaScript Rendering Modes
- LightPanda: Fast, light, supports basic JS. Best for simple dynamic pages.
- Chrome: Full browser automation, handles complex JS, animations, and challenges.
Every renderer (auto, LightPanda, http, Chrome) costs 1 credit per scrape. For 100 products scraped daily, that is 3,000 credits/month regardless of rendering mode.
Handling Out-of-Stock and Discontinued Products
Real e-commerce has dynamics:
- Out-of-stock: Flag in your schema. Don't alert on missing prices; alert on transitions (in-stock → out-of-stock).
- Discontinued products: Track URL 404s. Remove from monitoring list or set TTL on old products.
- Regional availability: Some products are geo-locked. If a price disappears, check if it's a geo issue or discontinuation.
Scaling to Thousands of Products
For 10K+ products:
- Parallel scraping: Fire concurrent
/v1/scrapecalls (up to your plan's concurrent-request limit) instead of looping sequentially — far faster for large catalogs. - Distributed workers: Run scraping on multiple workers (each with its own IP/proxy) to parallelize across competitors.
- Time-series database: PostgreSQL with time-series extensions (TimescaleDB) scales better than SQLite for millions of price points.
- Alert aggregation: Don't send one email per price change. Batch alerts into a daily digest or use Slack threads.
Pricing Math
Every renderer (auto, LightPanda, http, Chrome) costs 1 credit per scrape. The table shows monthly credit usage and the smallest plan that fits.
| Scenario | Scrapes/month | Credits/month | Fits plan |
|---|---|---|---|
| 50 products, daily | 1,500 | 1,500 | Hobby ($13/mo, 3,000 cr) |
| 100 products, daily | 3,000 | 3,000 | Hobby ($13/mo, 3,000 cr) |
| 100 products, daily + 5-credit extract | 3,000 | 18,000 | Standard ($69/mo, 100,000 cr) |
| 1,000 products, daily | 30,000 | 30,000 | Standard ($69/mo, 100,000 cr) |
| 10,000 products, daily | 300,000 | 300,000 | Growth |
Launch pricing (shown above) ends 2026-06-01, after which the regular plan prices apply. See the pricing page for current Hobby, Standard, Growth, and Scale rates.
Pro tip: Use auto or LightPanda rendering for static pricing pages and enable Chrome only for pages that truly need full browser automation (React-based storefronts, complex anti-bot challenges). Chrome is slower than lighter renderers, though it costs the same 1 credit — reserve it for pages that genuinely require it.
FAQ
Q: How do I handle multiple currencies?
A: Include currency as a field in your extraction schema. Store alongside the price. For cross-currency comparison, use a real-time FX API (OANDA, Alpha Vantage) to convert to a base currency.
Q: Can I track inventory levels too?
A: Yes. Add stock_quantity or in_stock to your schema. Track inventory as time-series data. This helps you predict price drops (when inventory is high, retailers drop prices to clear stock).
Q: How do I handle duplicate products across sellers?
A: Add a normalized product identifier (SKU, GTIN, ASIN) to your schema. Map all URLs and sellers back to the canonical product. This lets you compare prices across multiple retailers for the same product.
Q: What if a competitor blocks my IP?
A: fastCRW includes a rotating proxy pool. Each request can route through a different IP. If you hit strict rate limits, slow down (increase time between requests) or adjust fastCRW's proxy configuration.
Q: Should I monitor prices every hour or every day?
A: Depends on market velocity. Daily works for most retail. Hourly (or per-minute) for flash sales, travel pricing, or auction-based marketplaces. Each extra scrape costs money; balance freshness against budget.
Q: How do I export price history for analysis?
A: Query your database by date range. Export to CSV for analysis in Pandas, Excel, or Tableau. Time-series databases (TimescaleDB, ClickHouse) make trend analysis easier than SQLite for large datasets.
Related resources
- Firecrawl alternatives — managed scraping API comparison if you're evaluating vendors for the same pipeline
- Scrapfly alternatives — proxy-rotating extraction at scale, used by some price-tracking pipelines
- n8n integration — wire price monitoring into a no-code workflow with scheduled scrapes
- Make integration — same data flow, different automation platform
- Competitor monitoring — go beyond price to track positioning, messaging, and feature releases
- Brand monitoring — see where your products appear across third-party marketplaces and reviews
Continue exploring
More from Use Cases
Web Scraping for Competitor Monitoring
Web Scraping for Lead Enrichment
Web Scraping for Real Estate Data
Use fastCRW to build property listing pipelines from public real estate sites with structured extraction of price, location, beds/baths, and features.
Web Scraping for Content Aggregation
Build a comprehensive content aggregation pipeline with fastCRW: discover URLs across any source, scrape full-text pages into clean markdown, deduplicate, extract structured metadata, and feed a data pipeline dashboard — all via a single Firecrawl-compatible API.
Web Scraping for RAG and AI Agent Training Data
Collect, clean, and normalize web corpora for RAG knowledge bases and AI agent training datasets with fastCRW — high-fidelity markdown, 63.74% truth-recall, Firecrawl-compatible API, single Rust binary.
Related hubs
