How to Take Clean Website Screenshots Without Cookie Banners
Ever tried to take automated screenshots of websites, only to have them ruined by massive "Accept All Cookies" banners?
I was building a link preview feature for a side project and quickly discovered that 90% of websites now show these GDPR consent dialogs. Every screenshot looked like this:
┌─────────────────────────────────────────┐
│ [ACTUAL WEBSITE CONTENT - 40%] │
├─────────────────────────────────────────┤
│ │
│ 🍪 We use cookies to improve your │
│ experience. Please accept our │
│ privacy policy to continue. │
│ │
│ [ACCEPT ALL] [CUSTOMIZE] [REJECT] │
│ │
│ - 60% of the screen - │
└─────────────────────────────────────────┘
Not exactly the clean preview I was going for.
The Solution: Automatic Cookie Banner Removal
After trying several existing APIs (most don't handle this well, or charge premium prices), I decided to build my own solution using Node.js and Playwright.
The key insight: cookie banners follow predictable patterns that can be detected and removed before taking the screenshot.
How It Works
1. Detect Cookie Banners
Cookie consent dialogs typically share common characteristics:
const COOKIE_SELECTORS = [
// By ID
'#cookie-consent',
'#cookie-banner',
'#gdpr-banner',
'#cookieConsent',
'#onetrust-consent-sdk',
// By class
'.cookie-banner',
'.cookie-consent',
'.gdpr-popup',
'.consent-banner',
// By common button text patterns
'[aria-label*="cookie"]',
'[aria-label*="consent"]'
];
2. Remove Before Screenshot
Instead of clicking "Accept" (which may set tracking cookies), we simply hide the elements:
async function removeCookieBanners(page) {
await page.evaluate(() => {
const selectors = [
'#cookie-consent', '#gdpr-banner', '#onetrust-consent-sdk',
'.cookie-banner', '.cookie-consent', '.consent-banner',
'[class*="cookie"]', '[class*="consent"]', '[id*="cookie"]'
];
selectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
});
});
// Also check for fixed position overlays
document.querySelectorAll('*').forEach(el => {
const style = getComputedStyle(el);
if (style.position === 'fixed' && style.zIndex > 999) {
const text = el.textContent?.toLowerCase() || '';
if (text.includes('cookie') || text.includes('consent') || text.includes('gdpr')) {
el.style.display = 'none';
}
}
});
});
}
3. Take the Screenshot
const { chromium } = require('playwright');
async function takeCleanScreenshot(url, options = {}) {
const browser = await chromium.launch({
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
const page = await browser.newPage();
await page.setViewportSize({
width: options.width || 1280,
height: options.height || 800
});
await page.goto(url, { waitUntil: 'networkidle' });
// Remove cookie banners
await removeCookieBanners(page);
// Small delay to let DOM update
await page.waitForTimeout(500);
const screenshot = await page.screenshot({
type: options.format || 'png',
fullPage: options.fullPage || false
});
await browser.close();
return screenshot;
}
Full API Implementation
I wrapped this into a proper Express API with:
- Concurrency control (limit parallel screenshots to prevent memory issues)
- Random User-Agent rotation (avoid bot detection)
- Resource blocking (skip videos/large assets for speed)
- Graceful error handling
Here's the simplified architecture:
POST /api/screenshot
{
"url": "https://example.com",
"width": 1280,
"height": 800,
"fullPage": false,
"format": "png",
"removeCookieBanners": true
}
→ Returns: PNG/JPEG image buffer
Results
Before and after comparison:
| Metric | Without Cookie Removal | With Cookie Removal |
|---|---|---|
| Usable for previews | ❌ 30% | ✅ 95%+ |
| Professional look | ❌ No | ✅ Yes |
| Extra processing time | - | +200ms |
Try It Yourself
I deployed this as a free API on RapidAPI if you want to test it without setting up your own infrastructure:
→ Screenshot & Metadata API on RapidAPI
Free tier includes 50 screenshots/month - enough for testing and small projects.
Key Takeaways
- Cookie banners follow patterns - They use predictable class names, IDs, and positioning
- Hide, don't click - Hiding elements is faster and doesn't trigger tracking
- Check z-index + fixed positioning - Overlays typically have high z-index and fixed position
- Allow for edge cases - Some sites use unique selectors; consider a fallback mechanism
What's Next?
I'm considering adding:
- Chat widget removal (Intercom, Drift, etc.)
- Ad blocking
- Custom CSS injection
- Mobile device emulation
Would any of these be useful to you? Let me know in the comments!
Have you dealt with cookie banner issues in your projects? What solutions have you tried?
Top comments (0)