websurfer/crawler/cookies.py
2026-11-07 16:58:29 +01:00

177 lines
5.7 KiB
Python

import asyncio
# Selectors for common Consent Management Platforms (CMPs) and cookie banners
COOKIE_BANNER_SELECTORS = [
# Complianz (WordPress)
"button.cmplz-accept",
"button.cmplz-btn.cmplz-accept",
".cmplz-btn.cmplz-accept",
"#cmplz-cookiebanner-content .cmplz-accept",
# Borlabs Cookie (WordPress)
"a._brlbs-accept-all",
"button._brlbs-accept-all",
"._brlbs-btn-accept-all",
"[data-borlabs-cookie-accept-all]",
"#borlabs-cookie-accept-all",
".borlabs-cookie-preference-accept-all",
# Cookiebot
"#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll",
"#CybotCookiebotDialogBodyButtonAccept",
"#CybotCookiebotDialogBodyLevelButtonAccept",
"#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowallSelection",
# OneTrust / CookiePro
"#onetrust-accept-btn-handler",
"button#onetrust-accept-btn-handler",
"#accept-recommended-btn-handler",
# Usercentrics
"button[data-testid='uc-accept-all-button']",
"#uc-btn-accept-banner",
"button#uc-btn-accept-banner",
"div#usercentrics-root button[data-testid='uc-accept-all-button']",
# Klaro
".klaro .cm-btn-accept",
".klaro .cm-btn-success",
"button.cm-btn-accept-all",
# CookieYes
"button.cky-btn-accept",
"[data-cky-tag='accept-button']",
# Real Cookie Banner
"button[data-consent-action='accept-all']",
".rcb-btn-accept-all",
# CCM19
".ccm--btn-accept-all",
"button.ccm--btn-accept-all",
"button[data-ccm-consent='accept-all']",
# Google Funding Choices
".fc-cta-consent",
"button.fc-cta-consent",
# Quantcast / InMobi
".qc-cmp2-summary-buttons button[mode='primary']",
# Iubenda
".iubenda-cs-accept-btn",
"button.iubenda-cs-btn-primary",
# Didomi
"#didomi-notice-agree-button",
# Osano
".osano-cm-accept-all",
"button.osano-cm-button--type_accept",
# Axeptio
"#axeptio_btn_acceptAll",
# Generic IDs, classes, and data attributes
"[data-action='accept-all']",
"[data-consent='accept-all']",
"[data-cookie-accept-all]",
"button[id*='cookie-accept']",
"button[id*='accept-cookies']",
"button[id*='cookieAccept']",
"button[class*='cookie-accept']",
"button[class*='accept-all-cookies']",
]
COOKIE_TEXT_PATTERNS = [
# German
r"^(alle\s+)?(cookies\s+)?akzeptieren$",
r"^alles\s+akzeptieren$",
r"^alle\s+annehmen$",
r"^alles\s+annehmen$",
r"^alle\s+ausw[aä]hlen\s+und\s+akzeptieren$",
r"^alle\s+zulassen$",
r"^cookies\s+zulassen$",
r"^einverstanden$",
r"^zustimmen$",
r"^allen\s+zustimmen$",
r"^alle\s+best[aä]tigen$",
r"^auswahl\s+best[aä]tigen$",
r"^einwilligen$",
r"^verstanden$",
r"^ok$",
# English
r"^(accept\s+all(\s+cookies)?)$",
r"^(allow\s+all(\s+cookies)?)$",
r"^i\s+accept$",
r"^i\s+agree$",
r"^agree(\s+to\s+all)?$",
r"^accept$",
r"^allow$",
r"^agree\s+&\s+proceed$",
r"^got\s+it$",
]
COOKIE_TEXT_REGEX_STR = "|".join(f"(?:{p})" for p in COOKIE_TEXT_PATTERNS)
async def auto_accept_cookies(page) -> bool:
"""
Attempts to detect and click cookie consent / banner acceptance buttons
using a fast, single-pass in-browser JavaScript evaluation.
Only targets actual buttons and known CMP selectors, preventing accidental link navigations.
"""
js_code = """
() => {
const selectors = """ + str(COOKIE_BANNER_SELECTORS) + """;
// 1. Try known CMP selectors on main page
for (const sel of selectors) {
try {
const el = document.querySelector(sel);
if (el && (el.offsetWidth > 0 || el.offsetHeight > 0 || el.getClientRects().length > 0)) {
el.click();
return { clicked: true, method: 'selector', text: sel };
}
} catch(e) {}
}
// 2. Check Shadow DOM (e.g. Usercentrics / custom web components)
try {
const ucRoot = document.querySelector('#usercentrics-root, div#usercentrics-cmp-ui');
if (ucRoot && ucRoot.shadowRoot) {
const ucBtn = ucRoot.shadowRoot.querySelector("button[data-testid='uc-accept-all-button'], button#uc-btn-accept-banner, button.uc-btn-accept");
if (ucBtn) {
ucBtn.click();
return { clicked: true, method: 'shadow-dom', text: 'usercentrics-shadow' };
}
}
} catch(e) {}
// 3. Text-based matching strictly on button / submit / role=button elements (never plain navigation links)
const textRegex = /""" + COOKIE_TEXT_REGEX_STR + """/i;
const candidates = document.querySelectorAll('button, input[type="button"], input[type="submit"], [role="button"], a.cmplz-accept, a._brlbs-accept-all, a.cky-btn-accept');
for (const btn of candidates) {
try {
const txt = (btn.innerText || btn.value || '').trim();
if (txt && (btn.offsetWidth > 0 || btn.offsetHeight > 0 || btn.getClientRects().length > 0) && textRegex.test(txt)) {
btn.click();
return { clicked: true, method: 'button-text', text: txt };
}
} catch(e) {}
}
return { clicked: false };
}
"""
try:
result = await asyncio.wait_for(page.evaluate(js_code), timeout=2.0)
if result and result.get("clicked"):
print(f" [Cookie] Auto-accepted cookies via {result.get('method')}: '{result.get('text')}'")
await page.wait_for_timeout(600) # Give tracking scripts time to transmit beacons
return True
except Exception:
pass
return False