
Playwright que los anti-bots no pueden ver, así que sin captchas: misma API, Firefox headless sigiloso anti-detección, huella indetectable, elude la detección de bots, web scraping y automatización de navegador en Python.

Los anti-bots hacen dos preguntas, y reCAPTCHA, hCaptcha y Cloudflare Turnstile puntúan las respuestas. invisible_playwright responde que sí a ambas.
1. ¿Es este un navegador real? Sí. Es Firefox, parcheado a nivel del código fuente en C++.
2. ¿Lo está usando una persona real? Sí. Las acciones están humanizadas en el driver.
Windows, en PowerShell:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
$env:Path = "$env:USERPROFILE\.local\bin;$env:Path"
uv init --python ">=3.11" my-project
cd my-project
uv add invisible-playwright
uv run invisible-playwright fetch # one-time download, sha256-verified: 240 MB, about 550 MB unpacked
Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv init --python ">=3.11" my-project
cd my-project
uv add invisible-playwright
uv run invisible-playwright fetch # one-time download, sha256-verified: 262 MB (x86_64) / 253 MB (arm64), about 550 MB unpacked
Plataformas compatibles: Windows x86_64, Linux x86_64 / arm64.
Luego guarda uno de los ejemplos a continuación como main.py en my-project y ejecútalo con uv run main.py.
La API de Playwright, síncrona y asíncrona, sin cambios en el código. Los mismos objetos, las mismas llamadas, los mismos valores de retorno. Algunas superficies quedan fuera del alcance —tracing, HAR, CDP, el contexto de solicitudes de la API— y cada una de ellas rechaza la operación con una frase que explica el motivo en lugar de comportarse de forma incorrecta de manera silenciosa. Si ya usas Playwright, cambiar es cuestión de dos líneas:
- from playwright.sync_api import sync_playwright
- with sync_playwright() as p:
- browser = p.firefox.launch()
+ from invisible_playwright import InvisiblePlaywright
+ with InvisiblePlaywright() as browser:
Cada sesión obtiene una huella digital distinta (GPU, audio, fuentes, pantalla, ~200 campos) y movimiento del ratón con curvas de Bézier.
Sincronización
from invisible_playwright import InvisiblePlaywright
with InvisiblePlaywright(proxy={"server": "socks5://...", "username": "u", "password": "p"}) as browser:
page = browser.new_page()
page.goto("https://example.com")
page.click("#submit") # mouse arcs to the button on a Bezier curve
Asíncrono
from invisible_playwright.async_api import InvisiblePlaywright
async with InvisiblePlaywright(proxy={"server": "socks5://...", "username": "u", "password": "p"}) as browser:
page = await browser.new_page()
await page.goto("https://example.com")
await page.click("#submit")
El objeto browser es un playwright.sync_api.Browser / playwright.async_api.Browser - cada método de Playwright funciona tal cual.
Registra la semilla para reproducir una ejecución:
sf = InvisiblePlaywright()
with sf as browser:
print("seed =", sf.seed)
# ...
with InvisiblePlaywright(seed=42) as browser:
... # same GPU, same canvas hash, same audio context, every run
proxy = {
"server": "socks5://gate.example.com:1080",
"username": "user",
"password": "pass",
}
with InvisiblePlaywright(proxy=proxy) as browser:
...
Esquemas compatibles: socks5, socks4, http, https. El DNS se enruta a través del proxy de forma predeterminada, sin fuga local.
La zona horaria del navegador sigue a timezone=:
# default: timezone is auto-derived from the egress IP (proxy egress if a
# proxy is set, otherwise the host's own public IP)
with InvisiblePlaywright(proxy=proxy) as browser:
...
# explicit IANA zone always wins, the only way to force a specific zone
with InvisiblePlaywright(proxy=proxy, timezone="America/New_York") as browser:
...
Por defecto, todo proviene de seed. Para forzar valores específicos mientras el resto permanece derivado de la semilla:
with InvisiblePlaywright(
seed=42,
pin={
"gpu.renderer": "ANGLE (AMD, Radeon R9 200 Series Direct3D11 vs_5_0 ps_5_0, D3D11)",
"gpu.vendor": "Google Inc. (AMD)",
"screen.width": 2560,
"screen.height": 1440,
"hardware.concurrency": 16,
},
) as browser:
...
Un pin de GPU selecciona una de las personas validadas en lugar de establecer una cadena libre, porque los ~81 valores de getParameter y la lista de extensiones viajan con el nombre del renderizador y un detector los verifica de forma cruzada contra él. Un nombre que el pool no contiene genera un error, y el error enumera los que sí contiene.
La lista completa de claves fijables, cómo interactúa el pinning con el muestreador bayesiano y los patrones comunes están en docs/pinning.md.
El comando instalado es invisible-playwright, con un guion. python -m invisible_playwright funciona de forma idéntica y no necesita nada en el PATH.
invisible-playwright fetch # download the engine if missing, check every cached
# one against the seal, print the path
invisible-playwright version # wrapper, core and engine versions, and where the
# engine is cached