How to Block Cookie Banners Programmatically
Cookie consent banners are the bane of automated web workflows. Whether you're taking screenshots, scraping content, running visual regression tests, or generating thumbnails, that GDPR popup covers half the page and ruins your output.
This guide covers every practical approach to removing cookie banners programmatically, from CSS injection to request interception to using an API that handles it for you.
Why Cookie Banners Are Hard to Remove
Cookie banners aren't a single standard. Every website implements them differently:
- Custom implementations: Hand-built HTML/CSS unique to each site
- Consent Management Platforms (CMPs): OneTrust, Cookiebot, TrustArc, Quantcast, Didomi, each with different DOM structures
- Google Consent Mode: Google's own consent UI, rendered differently by language and region
- Overlay vs. banner: Some block the entire page with a modal overlay, others show a bottom bar
- Dynamic loading: Many banners load asynchronously after the page renders, with varying delays
No single CSS selector or script removes all of them. You need a multi-layered approach.
Method 1: CSS Injection (Hide Known Selectors)
The simplest approach: inject CSS that hides known cookie banner elements.
Puppeteer
import puppeteer from 'puppeteer';
const COOKIE_BANNER_SELECTORS = [
// OneTrust
'#onetrust-consent-sdk',
'#onetrust-banner-sdk',
'.onetrust-pc-dark-filter',
// Cookiebot
'#CybotCookiebotDialog',
'#CybotCookiebotDialogBodyUnderlay',
// TrustArc
'.truste_overlay',
'.truste_box_overlay',
'#truste-consent-track',
// Quantcast
'.qc-cmp2-container',
'#qc-cmp2-container',
// Didomi
'#didomi-host',
'#didomi-popup',
// Google Consent
'.fc-consent-root',
'.fc-dialog-overlay',
// Common patterns
'[class*="cookie-banner"]',
'[class*="cookie-consent"]',
'[class*="cookie-notice"]',
'[class*="cookie-popup"]',
'[id*="cookie-banner"]',
'[id*="cookie-consent"]',
'[id*="cookie-notice"]',
'[id*="gdpr"]',
'[class*="consent-banner"]',
'[class*="consent-modal"]',
'[aria-label*="cookie"]',
'[aria-label*="consent"]',
];
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
// Inject CSS to hide all known banner selectors
const hideCSS = COOKIE_BANNER_SELECTORS
.map(s => `${s} { display: none !important; visibility: hidden !important; }`)
.join('\n');
await page.addStyleTag({ content: hideCSS });
// Also remove any overlay/backdrop that blocks interaction
await page.addStyleTag({
content: `
body { overflow: auto !important; }
html { overflow: auto !important; }
`
});
// Wait a moment for the CSS to take effect
await new Promise(resolve => setTimeout(resolve, 500));
await page.screenshot({ path: 'clean.png' });
await browser.close();
Playwright (Python)
from playwright.sync_api import sync_playwright
COOKIE_SELECTORS = [
"#onetrust-consent-sdk",
"#CybotCookiebotDialog",
".qc-cmp2-container",
"#didomi-host",
".fc-consent-root",
'[class*="cookie-banner"]',
'[class*="cookie-consent"]',
'[id*="cookie-notice"]',
'[class*="consent-banner"]',
]
hide_css = " ".join(
f"{s} {{ display: none !important; }}" for s in COOKIE_SELECTORS
) + " body, html { overflow: auto !important; }"
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com", wait_until="networkidle")
page.add_style_tag(content=hide_css)
page.wait_for_timeout(500)
page.screenshot(path="clean.png")
browser.close()
Pros and Cons
Pros: Simple, no external dependencies, works for the majority of common CMPs.
Cons: Doesn't catch every implementation. New banner styles break the selectors. The banner's HTML is still in the DOM (hidden, not removed), which can affect layout. Overlay-style banners may still prevent scrolling unless you also fix the overflow property.
Method 2: Click the Accept Button
Instead of hiding the banner, programmatically click "Accept" or "Agree."
const ACCEPT_SELECTORS = [
// OneTrust
'#onetrust-accept-btn-handler',
// Cookiebot
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
'#CybotCookiebotDialogBodyButtonAccept',
// Generic patterns
'button[id*="accept"]',
'button[class*="accept"]',
'a[id*="accept"]',
'button[data-action="accept"]',
// Text-based matching
'button:has-text("Accept")',
'button:has-text("Accept all")',
'button:has-text("Agree")',
'button:has-text("Got it")',
'button:has-text("OK")',
'button:has-text("I agree")',
];
async function dismissCookieBanner(page) {
for (const selector of ACCEPT_SELECTORS) {
try {
const button = await page.$(selector);
if (button) {
await button.click();
// Wait for banner to animate away
await new Promise(resolve => setTimeout(resolve, 1000));
return true;
}
} catch {
// Selector not found or not clickable
}
}
return false;
}
// Usage
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await dismissCookieBanner(page);
await page.screenshot({ path: 'clean.png' });
Pros: Cleanest result. The banner is genuinely dismissed and the layout adjusts naturally.
Cons: Even more fragile than CSS hiding. Button text varies by language. Some banners require waiting for animation. Clicking "Accept" may set tracking cookies you don't want.
Method 3: Block CMP Scripts via Request Interception
Prevent the cookie banner from loading in the first place by blocking the consent management scripts.
const CMP_DOMAINS = [
'cdn.cookielaw.org', // OneTrust
'consent.cookiebot.com', // Cookiebot
'consent.trustarc.com', // TrustArc
'quantcast.mgr.consensu.org', // Quantcast
'sdk.privacy-center.org', // Didomi
'fundingchoicesmessages.google.com', // Google Consent
'consentmanager.net',
'consentframework.com',
];
await page.setRequestInterception(true);
page.on('request', (request) => {
const url = request.url();
if (CMP_DOMAINS.some(domain => url.includes(domain))) {
request.abort();
} else {
request.continue();
}
});
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'no-banner.png' });
Pros: The banner never appears at all. Fastest approach (no waiting for banner to load then hiding it). Also blocks tracking.
Cons: Some sites check if the CMP loaded and show a fallback banner. Blocking scripts may break site functionality (rare but possible).
Method 4: Combined Approach (Most Robust)
For production use, combine all three methods:
import puppeteer from 'puppeteer';
const CMP_DOMAINS = [
'cdn.cookielaw.org', 'consent.cookiebot.com', 'consent.trustarc.com',
'quantcast.mgr.consensu.org', 'sdk.privacy-center.org',
'fundingchoicesmessages.google.com', 'consentmanager.net',
];
const BANNER_SELECTORS = [
'#onetrust-consent-sdk', '#CybotCookiebotDialog',
'.qc-cmp2-container', '#didomi-host', '.fc-consent-root',
'[class*="cookie-banner"]', '[class*="cookie-consent"]',
'[id*="cookie-notice"]', '[class*="consent-banner"]',
];
async function captureWithoutBanners(url) {
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Layer 1: Block CMP scripts
await page.setRequestInterception(true);
page.on('request', (request) => {
const reqUrl = request.url();
if (CMP_DOMAINS.some(d => reqUrl.includes(d))) {
request.abort();
} else {
request.continue();
}
});
await page.goto(url, { waitUntil: 'networkidle2' });
// Layer 2: Hide remaining banners via CSS
const hideCSS = BANNER_SELECTORS
.map(s => `${s} { display: none !important; }`)
.join('\n') + '\nbody, html { overflow: auto !important; }';
await page.addStyleTag({ content: hideCSS });
// Layer 3: Try clicking accept on anything that survived
const acceptSelectors = [
'#onetrust-accept-btn-handler',
'button[id*="accept"]',
'button[class*="accept"]',
];
for (const sel of acceptSelectors) {
try {
const btn = await page.$(sel);
if (btn) { await btn.click(); break; }
} catch { /* ignore */ }
}
await new Promise(resolve => setTimeout(resolve, 500));
const buffer = await page.screenshot({ type: 'png' });
await browser.close();
return buffer;
}
This catches ~95% of cookie banners. But maintaining this code is an ongoing burden. New CMPs appear, existing ones update their DOM structure, and regional variations add complexity.
Method 5: Use a Screenshot API with Built-In Banner Removal
If cookie banner removal is one feature of a larger workflow (screenshots, thumbnails, archiving), consider using an API that handles it automatically.
cURL
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&block_cookie_banners=true&format=png" \
-H "X-API-Key: YOUR_API_KEY" \
--output clean.png
Node.js
import { SnapRender } from 'snaprender';
const client = new SnapRender('YOUR_API_KEY');
const image = await client.screenshot({
url: 'https://example.com',
blockCookieBanners: true,
blockAds: true,
format: 'png'
});
Python
from snaprender import SnapRender
client = SnapRender("YOUR_API_KEY")
image = client.screenshot(
url="https://example.com",
block_cookie_banners=True,
block_ads=True,
format="png"
)
with open("clean.png", "wb") as f:
f.write(image)
The block_cookie_banners=true parameter (enabled by default) handles all the complexity server-side: CMP script blocking, CSS injection, accept-button clicking, and ongoing maintenance of banner selectors. You don't need to maintain any of the code shown above.
Which Approach Should You Use?
| Approach | Reliability | Maintenance | Best For |
|---|---|---|---|
| CSS injection | ~80% | Medium | Quick scripts, one-off captures |
| Accept button clicking | ~70% | High | When you need the banner genuinely dismissed |
| Script blocking | ~85% | Medium | When you also want to block tracking |
| Combined (all three) | ~95% | Very high | Production self-hosted systems |
| Screenshot API | ~95%+ | None | Production workflows, teams |
For one-off scripts: CSS injection is the fastest to implement.
For production systems: Either invest in the combined approach and maintain it, or use an API. The maintenance cost of keeping banner selectors updated across hundreds of CMP variants is significant.
SnapRender removes cookie banners by default on every screenshot. 500 free screenshots/month, no credit card required. Get your API key and stop fighting with cookie popups.