# Playwright Stealth in Python

> Playwright stealth in Python: install playwright-stealth, what automation signals it hides, why detectors still catch it, and pairing stealth with residential proxies.

[Home](https://quanticdata.io/)/[Blog](https://quanticdata.io/blog/)/Playwright Stealth in Python

# Playwright stealth in Python: what it patches, what it doesn't, and what actually works

GuidesJul 30, 2026·5 min read·QuanticData Team

On this page [What playwright-stealth does](/blog/playwright-stealth-in-python/#what-playwright-stealth-does) [What it doesn't do — and why you still get caught](/blog/playwright-stealth-in-python/#what-it-doesn-t-do-and-why-you-still-get-caught) [What actually keeps a Playwright scraper alive](/blog/playwright-stealth-in-python/#what-actually-keeps-a-playwright-scraper-alive) [Headless vs headed, and the maintenance tax](/blog/playwright-stealth-in-python/#headless-vs-headed-and-the-maintenance-tax) [When to stop fighting the arms race](/blog/playwright-stealth-in-python/#when-to-stop-fighting-the-arms-race)

Playwright stealth in Python means running the `playwright-stealth` library to patch the obvious signals that give away an automated browser — `navigator.webdriver`, missing plugins, headless giveaways. It defeats naive bot checks, but it does not make you invisible: sophisticated detectors combine IP reputation, TLS fingerprint and behavior, which stealth can't touch. Knowing that boundary is what separates a scraper that works from one that gets silently flagged.

## What playwright-stealth does

A vanilla Playwright browser leaks that it is automated in a dozen small ways that JavaScript on the page can read: `navigator.webdriver` is `true`, the plugins array is empty, `window.chrome` is missing or malformed, WebGL reports a headless vendor string. The `playwright-stealth` package injects scripts that patch these tells so the page's detection JavaScript sees values consistent with a real Chrome. Install and apply it in a few lines:

```
pip install playwright-stealth
```

```
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)              # patch the automation tells
    page.goto("https://example.com")
    print(page.title())
    browser.close()
```

That is the whole integration. Against basic detection — scripts that just check `navigator.webdriver` — this is often enough, and it is worth applying by default when scraping any site that shows signs of bot checks.

## What it doesn't do — and why you still get caught

Stealth patches client-side JavaScript signals. It has no effect on the three things advanced anti-bot systems weight most heavily:

| Signal | What detectors read | Can stealth fix it? |
| --- | --- | --- |
| IP reputation | Datacenter ranges, known-proxy lists, request rate per IP | No — it's your network, not the browser |
| TLS/HTTP fingerprint | The TLS handshake signature (JA3/JA4), header order | No — below the JavaScript layer |
| Behavior | Mouse paths, typing cadence, scroll patterns, timing | No — you have to generate human-like behavior yourself |

This is why teams apply stealth, watch it work for a day, then get blocked: they patched the JavaScript tells but kept hammering from a flagged datacenter IP with a robotic request pattern. As [Scrapfly's analysis of Playwright stealth](https://scrapfly.io/blog/posts/how-to-avoid-web-scraping-blocking-playwright) puts it, stealth is one layer of an arms race, not a solution — and the layers it can't reach are the ones sophisticated systems check first.

## What actually keeps a Playwright scraper alive

Stealth is necessary but not sufficient. The combination that survives real anti-bot systems has three parts, and stealth is only one:

1. **Stealth** to pass the client-side JavaScript checks — the cheap, obvious layer.

2. **A trusted IP.** Route the browser through [residential proxies](https://quanticdata.io/residential-proxies/) so the request comes from a real home IP, not a datacenter range every detector blocklists. This fixes the single biggest tell stealth can't touch — set it at launch: `p.chromium.launch(proxy={"server": "http://pr.quanticdata.io:7777", "username": "USER", "password": "KEY"})`.

3. **Human-like behavior.** Add realistic delays, avoid teleporting the mouse, don't fire requests at machine speed. A perfect fingerprint on a robotic timing pattern still reads as a bot.

Get all three right and a headless Playwright session is genuinely hard to distinguish from a real user — which is the same recipe we describe for [browser agents](https://quanticdata.io/blog/how-to-build-an-ai-browser-agent/) and general [Playwright scraping](https://quanticdata.io/blog/how-to-use-playwright-for-scraping/).

## Headless vs headed, and the maintenance tax

One detail worth knowing: running Playwright in headless mode leaks additional signals beyond what stealth patches — some detectors specifically probe for headless Chrome quirks. Running headed (a real, visible browser window, often under a virtual display like Xvfb on a server) removes a whole category of those tells, at the cost of more CPU and memory per session. If stealth plus residential proxies still isn't passing a stubborn target, switching from headless to headed is the next lever to try before assuming the site is unbeatable. It is not a fix on its own, but combined with the three-part recipe it closes gaps headless leaves open.

The uncomfortable reality of all of this is the maintenance. `playwright-stealth` is community-maintained and lags behind detector updates; a patch that hid a tell last month can be stale this month, and you only find out when your success rate quietly drops. Budget for the fact that a stealth-based scraper is never "done" — it needs monitoring of success rates and periodic library updates, which is real engineering time spent on evasion rather than on the data you actually wanted.

## When to stop fighting the arms race

Stealth plus residential proxies plus behavior works, but it is an arms race you have to keep maintaining — detectors update, the stealth library lags, and you spend engineering time on evasion instead of on your actual data. At some volume that maintenance cost exceeds the value of running the browser yourself. The alternative is to offload the whole fight: a [scraping API](https://quanticdata.io/web-scraping-api/) handles the browser, the residential proxies, the fingerprint and the anti-block server-side, and returns clean Markdown or JSON — you send a URL and get data, with no stealth library to keep patched. The pragmatic split: run Playwright with stealth for the interactive, logged-in flows where you need direct browser control, and hand the high-volume, get-blocked-a-lot scraping to an API built to win that fight. Most serious pipelines end up doing both.

### Sources & further reading

- [Scrapfly — Playwright stealth: bypass bot detection in Python and Node.js](https://scrapfly.io/blog/posts/how-to-avoid-web-scraping-blocking-playwright)

- [playwright-stealth on PyPI](https://pypi.org/project/playwright-stealth/)

## FAQ

Quick answers on how to use playwright stealth python.

[Something else? Ask us →](mailto:hello@quanticdata.io)

### Does playwright-stealth actually work?

Against basic bot detection — scripts checking navigator.webdriver and similar JavaScript tells — yes, it reliably passes them. Against advanced systems that also weigh IP reputation, TLS fingerprint and behavior, no: stealth only patches client-side signals and can't touch those layers. Treat it as one necessary layer, not a complete solution.

### How do I install and use Playwright stealth in Python?

Run pip install playwright-stealth, import stealth_sync (or stealth_async for async), create your page, then call stealth_sync(page) before navigating. It injects scripts that patch automation tells like navigator.webdriver and empty plugins so page-side detection JavaScript sees values consistent with a real Chrome browser.

### Why do I still get blocked with Playwright stealth?

Because stealth only fixes client-side JavaScript signals. Detectors also read your IP reputation (a flagged datacenter range), TLS fingerprint, and behavior (robotic timing and mouse patterns) — none of which stealth touches. The usual fix is adding residential proxies and human-like pacing on top of stealth, not more stealth.

### Is playwright-stealth enough to avoid detection?

No, on its own. It's necessary for the client-side checks but insufficient against real anti-bot systems. The combination that survives is stealth plus a trusted residential IP plus human-like behavior. A perfect browser fingerprint from a blocklisted IP with machine-speed timing still gets flagged.

### Should I use Playwright stealth or a scraping API?

Use Playwright with stealth for interactive or logged-in flows where you need direct browser control. Use a scraping API for high-volume scraping that keeps getting blocked — it handles the browser, residential proxies, fingerprint and anti-block server-side, so you stop maintaining an evasion arms race. Many pipelines use both.

## Stop maintaining the evasion arms race

Hand the high-volume, gets-blocked scraping to an API that runs the browser, residential proxies and anti-block for you — clean Markdown or JSON out, pay per success. $2 free every month.

[Start free — $2/month included](https://app.quanticdata.io/register)[Explore Web Scraping API](https://quanticdata.io/web-scraping-api/)

## Related reading

[MCP & agents How to Create an MCP Server Pick the SDK, define tools with clear schemas, choose a transport, test in a client — plus the tool-description rules that decide whether a model actually calls it. Read →](https://quanticdata.io/blog/how-to-create-an-mcp-server/) [Data for AI How to Feed Data to an LLM Context window, RAG, tool calls or fine-tuning — the four ways to give an LLM your data, how each works, when to pick it, and how to keep the source fresh. Read →](https://quanticdata.io/blog/how-to-feed-data-to-an-llm/) [Guides How to Use a Proxy in n8n Two ways to route n8n through a proxy — per-node in the HTTP Request node or globally with env vars — the gotchas that trip people up, and rotation for scraping. Read →](https://quanticdata.io/blog/how-to-use-a-proxy-in-n8n/)

---

Source: https://quanticdata.io/blog/playwright-stealth-in-python/ · Site index for AI: https://quanticdata.io/llms.txt
