Scraping Central is reader-supported. When you buy through links on our site, we may earn an affiliate commission.

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:

  1. Runtime.Enable Artifacts, CDP commands leave traces in the JavaScript runtime that detection scripts check
  2. webdriver Flag, The navigator.webdriver property is set to true in automated browsers
  3. CDP-Specific Properties, Properties like window.cdc_adoQpoasnfa76pfcZLmcfl_* (ChromeDriver artifacts) leak control info
  4. 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=False mode when possible as it reduces detection surface
  • Apply stealth patches via playwright-stealth or custom init scripts
  • For production use, managed APIs eliminate the maintenance burden of keeping stealth patches current