Guide
How to Avoid CDP Detection in Playwright Scrapers
Learn how anti-bot systems detect Chrome DevTools Protocol (CDP) usage and how to avoid CDP detection in Playwright-based web scrapers.
Anti-bot systems can detect that a browser is being controlled via the Chrome DevTools Protocol (CDP). This is a major detection vector for Playwright-based scrapers. Here is how it works and how to mitigate it.
How CDP Detection Works
When Playwright controls a Chromium browser, it communicates via CDP WebSocket connections. Anti-bot scripts detect this through:
Runtime.EnableArtifacts, CDP commands leave traces in the JavaScript runtime that detection scripts checkwebdriverFlag, Thenavigator.webdriverproperty is set totruein automated browsers- CDP-Specific Properties, Properties like
window.cdc_adoQpoasnfa76pfcZLmcfl_*(ChromeDriver artifacts) leak control info - Missing User Gestures, CDP-driven clicks lack the trusted event flag that real user interactions have
Basic Playwright Stealth Setup
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False,
args=[
"--disable-blink-features=AutomationControlled",
"--no-first-run",
"--no-default-browser-check"
]
)
context = browser.new_context(
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
)
# Remove webdriver flag
context.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
""")
page = context.new_page()
page.goto("https://bot.sannysoft.com")
page.screenshot(path="stealth_test.png")
browser.close()
Using playwright-stealth Plugin
The playwright-stealth package patches multiple detection vectors.
from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
# Apply stealth patches
stealth_sync(page)
page.goto("https://nowsecure.nl")
page.wait_for_timeout(5000)
print(page.title())
browser.close()
Advanced: Patching CDP Leak Points
context.add_init_script("""
// Hide CDP Runtime.enable artifacts
const originalCall = Function.prototype.call;
const originalApply = Function.prototype.apply;
// Mask the permission query for notifications
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (params) =>
params.name === 'notifications'
? Promise.resolve({ state: Notification.permission })
: originalQuery(params);
// Chrome-specific patches
window.chrome = {
runtime: {},
loadTimes: function() {},
csi: function() {},
app: {}
};
""")
The Managed API Alternative
Maintaining stealth patches is an ongoing effort since detection methods evolve constantly. ScraperAPI and ScrapingAnt handle all CDP detection evasion automatically, which is more practical for production scrapers.
Key Takeaways
- CDP leaves detectable artifacts in the browser environment
- Use
headless=Falsemode when possible as it reduces detection surface - Apply stealth patches via
playwright-stealthor custom init scripts - For production use, managed APIs eliminate the maintenance burden of keeping stealth patches current