Fork of AtuboDad's port of puppeteer-extra-plugin-stealth, with some improvements. Don't expect this to bypass anything but the simplest of bot detection methods. Consider this a proof-of-concept starting point.
I've merged some of the outstanding PRs, added some features, and cleaned up the API surface. See the changelog. The latest major version includes breaking changes.
Install from PyPi:
$ pip install playwright-stealth
import asyncio
from playwright.async_api import async_playwright
from playwright_stealth import Stealth
async def main():
# This is the recommended usage. All pages created will have stealth applied:
async with Stealth().use_async(async_playwright()) as p:
browser = await p.chromium.launch()
page = await browser.new_page()
webdriver_status = await page.evaluate("navigator.webdriver")
print("from new_page: ", webdriver_status)
different_context = await browser.new_context()
page_from_different_context = await different_context.new_page()
different_context_status = await page_from_different_context.evaluate("navigator.webdriver")
print("from new_context: ", different_context_status)
asyncio.run(main())
import asyncio
from playwright.async_api import async_playwright
from playwright_stealth import Stealth, ALL_EVASIONS_DISABLED_KWARGS
async def advanced_example():
# Custom configuration with specific languages
custom_languages = ("fr-FR", "fr")
stealth = Stealth(
navigator_languages_override=custom_languages,
init_scripts_only=True
)
async with async_playwright() as p:
browser = await p.chromium.launch()
context = await browser.new_context()
await stealth.apply_stealth_async(context)
# Test stealth on multiple pages
page_1 = await context.new_page()
page_2 = await context.new_page()
# Verify language settings
for i, page in enumerate([page_1, page_2], 1):
is_mocked = await page.evaluate("navigator.languages") == custom_languages
print(f"Stealth applied to page {i}: {is_mocked}")
# Example of selective evasion usage
no_evasions = Stealth(**ALL_EVASIONS_DISABLED_KWARGS)
single_evasion = Stealth(**{**ALL_EVASIONS_DISABLED_KWARGS, "navigator_webdriver": True})
print("Total evasions (none):", len(no_evasions.script_payload))
print("Total evasions (single):", len(single_evasion.script_payload))
asyncio.run(advanced_example())
- make this work with playwright.launch_persistent_context
- the difficult because sometimes we sniff the UA if an override isn't provided, and this is difficult to do when launch_persistent_context is launched
- sec-platform (we have navigator_platform)
- docs