Say you're consulting for a business. You want to show up every Monday with a slide deck that says:
"Your competitor was charging $149 for this two weeks ago. Now they're charging $199."
That's a powerful conversation. But capturing 100 screenshots manually every week? That's a full day of miserable browser work.
Here's how to automate it in under 40 lines of Node.js — no Puppeteer, no headless browser config, no Chrome version drama.
The Problem with Manual Screenshot Monitoring
The obvious approach: write a Puppeteer script, loop through URLs, save screenshots.
You've done this before. You know how it goes:
- Chrome crashes at screenshot #47 with "Page crashed!"
-
networkidle2sometimes works, sometimes doesn't - Running this on a server means managing Chromium, fonts, sandboxes, and
--no-sandboxflags - Memory leaks on long-running jobs
- CI passes locally, fails in production
For a one-time task, Puppeteer is fine. For something you want to run reliably every Monday at 8am? It's a maintenance burden.
The Setup
We'll use SnapAPI — a hosted screenshot API that handles the browser, caching, and batch processing — plus the official snapapi-sdk npm package.
npm install snapapi-sdk node-fetch
Set your API key:
export SNAPAPI_KEY=your_key_here
Step 1: Define Your URL List
Create a competitors.json file:
[
{ "name": "CompetitorA", "url": "https://competitorA.com/pricing" },
{ "name": "CompetitorB", "url": "https://competitorB.com/pricing" },
{ "name": "CompetitorC", "url": "https://competitorC.com/plans" }
]
You can have 100 of these. The API handles batching.
Step 2: Capture Weekly Screenshots
const SnapAPI = require('snapapi-sdk');
const fs = require('fs');
const path = require('path');
const client = new SnapAPI({ apiKey: process.env.SNAPAPI_KEY });
async function captureWeekly(competitors) {
const weekLabel = new Date().toISOString().split('T')[0]; // e.g. "2026-03-17"
const outputDir = path.join('screenshots', weekLabel);
fs.mkdirSync(outputDir, { recursive: true });
console.log(`Capturing ${competitors.length} screenshots for week: ${weekLabel}`);
for (const competitor of competitors) {
try {
const imageBuffer = await client.screenshot(competitor.url, {
format: 'png',
full_page: true,
width: 1440,
});
const filename = path.join(outputDir, `${competitor.name}.png`);
fs.writeFileSync(filename, imageBuffer);
console.log(`✓ ${competitor.name}`);
} catch (err) {
console.error(`✗ ${competitor.name}: ${err.message}`);
}
}
console.log(`Done. Screenshots saved to: ${outputDir}`);
}
const competitors = JSON.parse(fs.readFileSync('competitors.json', 'utf8'));
captureWeekly(competitors);
Run it:
node capture.js
You get a folder organized by date, one PNG per competitor, full-page captures at 1440px width.
Step 3: Compare Week-Over-Week
This is the part that makes the slide deck. You want to see what changed.
For a quick visual diff, add pixelmatch:
npm install pixelmatch pngjs
const { PNG } = require('pngjs');
const pixelmatch = require('pixelmatch');
const fs = require('fs');
function diffScreenshots(file1, file2, outputFile) {
const img1 = PNG.sync.read(fs.readFileSync(file1));
const img2 = PNG.sync.read(fs.readFileSync(file2));
const { width, height } = img1;
const diff = new PNG({ width, height });
const numDiffPixels = pixelmatch(
img1.data, img2.data, diff.data,
width, height,
{ threshold: 0.1 }
);
fs.writeFileSync(outputFile, PNG.sync.write(diff));
const changePercent = ((numDiffPixels / (width * height)) * 100).toFixed(2);
return { numDiffPixels, changePercent };
}
// Compare this week vs last week for each competitor
const thisWeek = '2026-03-17';
const lastWeek = '2026-03-10';
const competitors = JSON.parse(fs.readFileSync('competitors.json', 'utf8'));
for (const competitor of competitors) {
const current = `screenshots/${thisWeek}/${competitor.name}.png`;
const previous = `screenshots/${lastWeek}/${competitor.name}.png`;
if (fs.existsSync(current) && fs.existsSync(previous)) {
const result = diffScreenshots(current, previous, `diffs/${competitor.name}-diff.png`);
if (parseFloat(result.changePercent) > 1) {
console.log(`⚠️ ${competitor.name} changed ${result.changePercent}% — review diff`);
} else {
console.log(`✓ ${competitor.name} unchanged (${result.changePercent}% diff)`);
}
}
}
Any competitor whose pricing page changed by more than 1% gets flagged. You open the diff image, see exactly what moved.
Step 4: Schedule It Weekly
Cron on Linux/Mac:
# Every Monday at 8am
0 8 * * 1 cd /path/to/project && node capture.js >> logs/weekly.log 2>&1
On Windows, use Task Scheduler. On a VPS, set it and forget it.
What This Looks Like in Practice
After 4 weeks, your folder structure is:
screenshots/
2026-02-24/
CompetitorA.png
CompetitorB.png
CompetitorC.png
2026-03-03/
...
2026-03-10/
...
2026-03-17/
...
diffs/
CompetitorB-diff.png ← flagged, changed 8.3%
CompetitorB's pricing page changed. You open the diff, see the price moved from $149 to $199. You put that in your Monday meeting deck.
That's a real deliverable. It took you 35 minutes to set up once, and now it runs automatically.
Scaling to 100 URLs
The loop above works for any size list. SnapAPI handles the concurrent requests, rate limiting, and caching on their end. You don't need to throttle manually or worry about Chromium memory for large batches.
For very large lists (100+ URLs), add a small delay between requests if you want to be conservative:
await new Promise(resolve => setTimeout(resolve, 500)); // 500ms between requests
Or use the batch endpoint directly for parallel processing — check the SnapAPI docs for the batch API.
Get Your API Key
Free tier available at snapapi.tech. No credit card required to start.
The full code from this post is ~60 lines. It replaces a weekend of Puppeteer debugging.
If this was useful, I write about web automation, APIs, and developer tools. More posts in this series:
Top comments (0)