Python, Web Scraping

Playwright Stealth: Real Problems Bypassing Cloudflare (What Actually Works)

My experience trying to bypass Cloudflare 5-second shield with Playwright stealth mode. What works, what doesn't, and why sometimes you just can't win.

March 10, 2026

How I Got Here

I needed to scrape a React app protected by Cloudflare. Thought it would be easy - just install playwright-stealth, configure it, and done. That's not what happened.

After weeks of trial and error, going through GitHub issues, trying different proxies, and failing countless times, I finally figured out what actually works. Spoiler: it's not just playwright-stealth.

This is what I learned, the problems I hit, and the solutions that actually worked in production.

Problem 1: Playwright-stealth Doesn't Install Properly

First problem I hit - couldn't even get playwright-stealth working. The package on PyPI is outdated. There's a notice on GitHub:

⚠️ GitHub Issue #40

"This repo is not the source for the PyPi package (playwright-stealth) anymore"

Source: github.com/AtuboDad/playwright_stealth/issues/40

What happened: I ran pip install playwright-stealth, then tried to import it:

from playwright_stealth import Stealth

Got ImportError. The package is broken. Multiple issues report this (#16, #22, #31).

What Actually Worked

I had to install from the original repo directly:

pip install git+https://github.com/Granitosaurus/playwright-stealth.git

Then use it like this:

from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    stealth_sync(page)  # This hides webdriver properties
    page.goto("https://example.com")

💡 Tip

Even with stealth installed, Cloudflare still detects you. More on that next.

Problem 2: Cloudflare Still Detects the Bot

Got stealth working, but Cloudflare still blocked me. Got the 5-second shield, then a 1020 error (access denied).

This is what everyone complains about on StackOverflow and Reddit. Even with:

Cloudflare still blocked the request. Why?

What Cloudflare Actually Checks

After digging through forums and GitHub issues, I found that Cloudflare checks:

Playwright-stealth only hides the navigator.webdriver property. It doesn't fix TLS fingerprints or IP reputation.

Problem 3: Residential Proxies Are Expensive

Everyone said "use residential proxies." Yeah, great. They cost $500/month for decent ones.

⚠️ Reality Check

Free proxies are either slow, already blacklisted by Cloudflare, or exit after 5 minutes. Residential proxy services cost serious money.

I tried:

What Actually Worked (Budget Solution)

For low-volume scraping, I used:

from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync

# Use datacenter proxies with rotation
proxies = [
    "http://proxy1.example.com:8000",
    "http://proxy2.example.com:8000",
    # ... more proxies
]

with sync_playwright() as p:
    for proxy in proxies:
        try:
            browser = p.chromium.launch(
                headless=True,
                proxy={"server": proxy}
            )
            page = browser.new_page()
            stealth_sync(page)

            # Add random delays to mimic human behavior
            page.wait_for_timeout(2000 + random.randint(0, 3000))

            page.goto("https://target-site.com")

            # If we get here, proxy works
            break

        except Exception as e:
            continue
            # Try next proxy

This isn't perfect. Cloudflare still blocks some datacenter IPs. But with enough rotation, some get through.

Problem 4: Headless Mode Gets Detected

GitHub Issue #30 and #21 - headless browsers fail detection tests. Cloudflare checks:

What Actually Worked

I had to run in headed mode on a remote server:

browser = p.chromium.launch(
    headless=False,  # headed mode
    args=[
        '--start-maximized',
        '--disable-blink-features=AutomationControlled'
    ]
)

# Set viewport size
page = browser.new_page(
    viewport={'width': 1920, 'height': 1080}
)

For servers without a display, use Xvfb:

# Install Xvfb
# sudo apt-get install xvfb

# Run Playwright with virtual display
xvfb-run python script.py

💡 Note

Modern Cloudflare checks are more sophisticated. Even headed mode can be detected through behavioral analysis.

Problem 5: reCAPTCHA Doesn't Show

GitHub Issue #38 - recaptcha not showing. Sometimes Cloudflare serves a CAPTCHA, but it never appears in Playwright.

What happens: The page loads, you see the "Checking your browser" message, then... nothing. Page stays blank.

What Actually Worked

This is usually because:

  1. JavaScript is blocked
  2. The iframe containing the CAPTCHA is blocked by privacy settings
  3. Cloudflare decided to block instead of challenge

I tried:

# Wait for the Cloudflare challenge to complete
page.wait_for_selector("title", timeout=30000)

# Or wait for specific element that appears after challenge
page.wait_for_selector("#main-content", timeout=30000)

Sometimes though, the site just doesn't want automated traffic. No workaround for that.

⚠️ Unsolved Problem

Some sites use Cloudflare's "Managed Challenge" which is invisible. If it fails, you just get blocked. No CAPTCHA to solve.

What Actually Works (in 2026)

After all this trial and error, here's my setup that usually works:

from playwright.sync_api import sync_playwright
from playwright_stealth import stealth_sync
import random
import time

def create_stealth_context(proxy_url):
    """Create a browser context with anti-detection measures"""
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,  # headed mode
            proxy={"server": proxy_url} if proxy_url else None,
            args=[
                '--disable-blink-features=AutomationControlled',
                '--disable-dev-shm-usage',
                '--no-sandbox',
            ]
        )

        context = browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            locale='en-US',
            timezone_id='America/New_York',
        )

        page = context.new_page()
        stealth_sync(page)

        return browser, page

def scrape_with_retry(url, max_retries=5):
    """Scrape with proxy rotation and retries"""
    proxies = load_proxies()  # Your proxy list

    for attempt in range(max_retries):
        proxy = random.choice(proxies) if proxies else None

        try:
            browser, page = create_stealth_context(proxy)

            # Random delay before request
            time.sleep(random.uniform(2, 5))

            # Navigate to page
            response = page.goto(url, wait_until="domcontentloaded", timeout=30000)

            # Wait for Cloudflare challenge
            time.sleep(5)

            # Check if we got blocked
            if response.status == 403 or response.status == 1020:
                browser.close()
                continue

            # Wait for content
            page.wait_for_selector("body", timeout=15000)

            # Extract data
            content = page.content()

            browser.close()
            return content

        except Exception as e:
            if 'browser' in locals():
                browser.close()
            continue

    raise Exception("All retries failed")

Key Points

⚠️ Reality Check

Even with all this, some sites will still block you. Cloudflare gets smarter every month. What works today might not work next month.

When to Just Give Up

Sometimes the best solution is to accept defeat. I've learned to move on when:

Alternatives I use:

Final Thoughts

Playwright-stealth helps, but it's not a magic bullet. Cloudflare is sophisticated, and they actively work to detect automated browsers.

The combination that works for me:

Not claiming this is the "right" way or that it works everywhere. It's just what I've learned through months of failing and trying again.

If you're dealing with Cloudflare in 2026, expect to spend time debugging and adjusting. The cat-and-mouse game never ends.

Related Articles

Sources & Further Reading