Monitor Website Changes with Automated Screenshots
Published February 20, 2026 · 9 min read
Photo by Carlos Muza on Unsplash
Websites change constantly — pricing updates, design refreshes, content modifications, and sometimes unexpected defacements. If you need to track these changes (for competitive intelligence, compliance, or quality assurance), a website change detection API built on automated screenshots is the most reliable approach.
Text-based diffing misses visual changes like layout shifts, image swaps, and CSS modifications. Screenshot-based monitoring catches everything the human eye would see.
🚀 TL;DR: Capture periodic screenshots of any webpage, compare them with pixel-level diffing, and get alerted when something changes. We'll build this step-by-step with SnapAPI.
How Visual Change Detection Works
The process is straightforward:
- Step 1: Capture a baseline screenshot of the target page
- Step 2: On a schedule, capture a new screenshot
- Step 3: Compare the two images pixel-by-pixel
- Step 4: If the difference exceeds a threshold, trigger an alert
- Step 5: Update the baseline with the new screenshot
Capture Screenshots with SnapAPI
cURL
curl -X POST https://api.snapapi.pics/v1/screenshot \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://competitor.com/pricing",
"width": 1440,
"height": 900,
"fullPage": true,
"blockCookieBanners": true,
"format": "png"
}' --output baseline.pngNode.js — Full Change Detection System
const fetch = require('node-fetch');
const fs = require('fs');
const { PNG } = require('pngjs');
const pixelmatch = require('pixelmatch');
const SNAPAPI_KEY = process.env.SNAPAPI_KEY;
const THRESHOLD = 0.01; // 1% pixel difference triggers alert
async function captureScreenshot(url) {
const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
method: 'POST',
headers: {
'X-API-Key': SNAPAPI_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url,
width: 1440,
height: 900,
fullPage: true,
blockCookieBanners: true,
format: 'png'
})
});
return Buffer.from(await response.arrayBuffer());
}
async function compareImages(baselinePath, newBuffer) {
const baseline = PNG.sync.read(fs.readFileSync(baselinePath));
const current = PNG.sync.read(newBuffer);
const { width, height } = baseline;
const diff = new PNG({ width, height });
const numDiffPixels = pixelmatch(
baseline.data, current.data, diff.data,
width, height,
{ threshold: 0.1 }
);
const diffPercent = numDiffPixels / (width * height);
return { diffPercent, diffImage: PNG.sync.write(diff) };
}
async function monitorPage(url, name) {
const baselinePath = `baselines/${name}.png`;
const newScreenshot = await captureScreenshot(url);
if (!fs.existsSync(baselinePath)) {
fs.writeFileSync(baselinePath, newScreenshot);
console.log(`Baseline created for ${name}`);
return;
}
const { diffPercent, diffImage } = await compareImages(
baselinePath, newScreenshot
);
if (diffPercent > THRESHOLD) {
console.log(`⚠️ Change detected on ${name}: ${(diffPercent * 100).toFixed(2)}%`);
fs.writeFileSync(`diffs/${name}-${Date.now()}.png`, diffImage);
fs.writeFileSync(`snapshots/${name}-${Date.now()}.png`, newScreenshot);
// Send alert via Slack, email, etc.
await sendAlert(name, diffPercent, newScreenshot);
}
// Update baseline
fs.writeFileSync(baselinePath, newScreenshot);
}
// Monitor multiple pages
const pages = [
{ url: 'https://competitor.com/pricing', name: 'competitor-pricing' },
{ url: 'https://competitor.com/features', name: 'competitor-features' },
{ url: 'https://yoursite.com', name: 'our-homepage' }
];
for (const page of pages) {
await monitorPage(page.url, page.name);
}Python — Change Detection
import requests
import os
from PIL import Image
from io import BytesIO
import numpy as np
from datetime import datetime
SNAPAPI_KEY = os.environ['SNAPAPI_KEY']
THRESHOLD = 0.01 # 1% change triggers alert
def capture_screenshot(url: str) -> bytes:
response = requests.post(
'https://api.snapapi.pics/v1/screenshot',
headers={
'X-API-Key': SNAPAPI_KEY,
'Content-Type': 'application/json'
},
json={
'url': url,
'width': 1440,
'height': 900,
'fullPage': True,
'blockCookieBanners': True,
'format': 'png'
}
)
return response.content
def compare_images(baseline_path: str, new_bytes: bytes) -> float:
baseline = np.array(Image.open(baseline_path))
current = np.array(Image.open(BytesIO(new_bytes)))
# Resize if dimensions differ
if baseline.shape != current.shape:
current = np.array(
Image.open(BytesIO(new_bytes)).resize(
(baseline.shape[1], baseline.shape[0])
)
)
diff = np.abs(baseline.astype(int) - current.astype(int))
changed_pixels = np.sum(diff.mean(axis=2) > 25)
total_pixels = baseline.shape[0] * baseline.shape[1]
return changed_pixels / total_pixels
def monitor_page(url: str, name: str):
baseline_path = f'baselines/{name}.png'
new_screenshot = capture_screenshot(url)
if not os.path.exists(baseline_path):
with open(baseline_path, 'wb') as f:
f.write(new_screenshot)
print(f'Baseline created for {name}')
return
diff_percent = compare_images(baseline_path, new_screenshot)
if diff_percent > THRESHOLD:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
print(f'⚠️ Change detected: {name} ({diff_percent:.2%})')
with open(f'snapshots/{name}_{timestamp}.png', 'wb') as f:
f.write(new_screenshot)
# Update baseline
with open(baseline_path, 'wb') as f:
f.write(new_screenshot)
# Run monitoring
pages = [
('https://competitor.com/pricing', 'competitor-pricing'),
('https://competitor.com/features', 'competitor-features'),
]
for url, name in pages:
monitor_page(url, name)Use Cases for Website Change Detection
Competitive Intelligence
Track competitor pricing pages, feature lists, and landing pages. Get notified the moment they change their pricing model or launch a new feature.
Compliance Monitoring
Regulated industries need to ensure web content stays compliant. Automated visual monitoring catches unauthorized changes to disclaimers, terms, and regulatory content.
Brand Protection
Monitor your own pages for defacement, unauthorized edits, or broken layouts caused by deployments. Catch issues before customers do.
SEO Monitoring
Track how your pages appear in search results. Combine screenshots with SnapAPI's Extract API to monitor both visual appearance and content changes simultaneously.
Adding Content Extraction
Visual diffing tells you something changed. To know what changed, combine it with content extraction:
// Capture both screenshot and content
const [screenshot, content] = await Promise.all([
snapapi.screenshot({ url, fullPage: true }),
snapapi.extract({ url, type: 'markdown' })
]);
// Compare text content for semantic changes
const textDiff = diffText(previousContent, content.data);
// Compare screenshots for visual changes
const visualDiff = compareImages(previousScreenshot, screenshot);
// Rich alert with both types of changes
if (visualDiff > 0.01 || textDiff.changes > 0) {
await alert({
page: url,
visualChange: `${(visualDiff * 100).toFixed(1)}%`,
textChanges: textDiff.summary,
screenshot: screenshot
});
}Scheduling and Infrastructure
For production use, you'll want to run monitoring on a schedule:
- Cron jobs: Simple and reliable for fixed schedules (hourly, daily)
- Serverless functions: AWS Lambda + CloudWatch Events or Vercel Cron
- Queue-based: For large numbers of pages, use a job queue (Bull, Celery) with rate limiting
SnapAPI handles the browser infrastructure — you just need to handle scheduling and storage.
Pricing for Monitoring Workloads
Website monitoring is a high-frequency use case. Here's how SnapAPI pricing works:
- Free: 200 screenshots/month — monitor ~6 pages daily
- Pro ($19/mo): 25,000 screenshots — monitor 100+ pages hourly
- Business ($79/mo): 100,000 screenshots — enterprise-grade monitoring
Get Started
Build your website change detection system in under an hour. Sign up free and start monitoring today.
💡 Pro tip: Use the blockCookieBanners option to prevent GDPR consent popups from causing false-positive change detections on European websites.