Playwright & Puppeteer
Clearcote es simplemente Chromium, así que funciona como reemplazo directo del navegador en las herramientas de automatización que ya usas.
Con el SDK (recomendado)
El paquete clearcote (npm, PyPI y NuGet) convierte las opciones de identidad en argumentos con nombre y devuelve objetos normales de Playwright. En el primer uso descarga el navegador y verifica su SHA-256 —el build abierto sin clave de licencia, el build con licencia más reciente con una (consulta Instalación)— y aplica los valores por defecto que se describen más abajo.
# pip install clearcote
from clearcote import launch
browser = launch(fingerprint="seed-123", platform="windows", brand="Chrome")
page = browser.new_page()
page.goto("https://example.com")
browser.close()SDK actual: 0.31.1. Desde la 0.23, launch() en Python y Node se ejecuta sobre un directorio de perfil real y desechable (se borra al cerrar) en lugar de en modo incógnito, de modo que la superficie que depende del perfil coincide con la de un Chrome real y widevine: true puede cargar el módulo DRM. Devuelve un handle que se comporta como un navegador: newPage() funciona como siempre, pero newContext() devuelve ese mismo contexto de perfil en lugar de uno aislado. Si necesitas cookies separadas, lanza navegadores separados; pasa ephemeralProfile: false / ephemeral_profile=False para obtener el Browser en modo incógnito de antes, o userDataDir / user_data_dir para conservar el perfil.
La API asíncrona (clearcote.async_api) acepta las mismas opciones de persona y proxy dentro de un loop de asyncio y devuelve objetos asíncronos de Playwright; su launch() es en modo incógnito, así que usa launch_persistent_context() para tener un perfil (y para widevine=True). El SDK de .NET cubre LaunchEphemeralProfileAsync (recomendado: LaunchAsync en .NET es en modo incógnito y no puede ajustar una ventana headless), LaunchPersistentContextAsync, ServeAsync, la descarga verificada, las licencias, Geoip y la entrada humanizada (llamadas explícitas a HumanClickAsync / HumanTypeAsync / HumanSelectOptionAsync en lugar de un flag de lanzamiento). Los perfiles guardados, profile: "auto", las comprobaciones de coherencia de renderizado, Widevine y los helpers para agentes son, por ahora, solo para Python & Node. Para flujos completos listos para copiar y pegar, consulta Ejemplos.
Puppeteer y otros clientes CDP (serve)
El SDK no tiene un launcher para Puppeteer. En su lugar, serve() inicia Clearcote con la configuración de lanzamiento del SDK (persona, proxy, valores por defecto) y un endpoint CDP en loopback, y cualquier cliente CDP se conecta a él: Puppeteer, connectOverCDP de Playwright, browser-use, Crawl4AI, Stagehand. Funciona con el build con licencia, y nada agrega --enable-automation. humanize funciona del lado de Playwright, así que no se aplica a un cliente conectado de esta forma.
import { serve } from "clearcote";
import puppeteer from "puppeteer-core";
const srv = await serve({ fingerprint: "seed-123", platform: "windows" });
const browser = await puppeteer.connect({ browserURL: srv.cdpUrl, defaultViewport: null });
const page = await browser.newPage();
await page.goto("https://example.com");
await browser.disconnect();
await srv.close();En modo headless, serve() le da al navegador una pantalla de tamaño real y ajusta su ventana al área de trabajo antes de que se conecte ningún cliente (0.31+); pasa windowSize / window_size si quieres una ventana más pequeña. Desde una shell, clearcote serve hace lo mismo como servicio permanente, y puede darle a cada conexión su propio navegador, con la identidad, el proxy, la zona horaria y el idioma tomados de la URL de conexión. Consulta Despliegue.
Usar el binario directamente (build abierto)
También puedes apuntar tu propio launcher al build abierto con executablePath (Node) o executable_path (Python) y pasar las opciones de identidad como args. El build con licencia no arranca de esta forma: necesita el token de licencia que obtiene el SDK, así que usa launch() o serve(), descritos arriba. Quita --enable-automation como hace el SDK (Puppeteer y las versiones anteriores de Playwright lo agregan; pone a Chromium en su modo de automatización). Por esta vía no se aplica ninguno de los valores por defecto del SDK (idioma, política de WebRTC, geometría de la ventana).
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
executable_path=r"C:\clearcote\chrome.exe",
headless=False,
ignore_default_args=["--enable-automation"],
args=[
"--fingerprint=seed-123",
"--fingerprint-platform=windows",
"--timezone=America/New_York",
],
)
page = browser.new_page()
page.goto("https://abrahamjuliot.github.io/creepjs/")
browser.close()Resolver o descargar de antemano el binario verificado
El SDK resuelve el navegador en este orden: un executablePath / executable_path explícito, luego CLEARCOTE_BINARY, luego una version que hayas pedido (o CLEARCOTE_BROWSER_VERSION), luego el build con licencia si encuentra una clave de licencia (la opción licenseKey, CLEARCOTE_LICENSE_KEY o ~/.clearcote/license.key) y, por último, el build abierto fijado en esta versión del SDK. Si indicas una ruta, se comprueba antes del lanzamiento que no falten archivos ni haya archivos truncados. Llama a download / executable_path cuando quieras precargar la caché sin lanzar el navegador, usar un directorio de caché personalizado u optar por el build abierto más reciente de GitHub en tiempo de ejecución.
from clearcote import download, launch
chrome = download(cache_dir=r"C:\clearcote-cache", auto_update=True)
browser = launch(executable_path=chrome, fingerprint="seed-123")El modo fijado verifica los valores SHA-256 integrados en el SDK. autoUpdate / auto_update es opcional (opt-in) y verifica el manifiesto de checksums de la versión; si gpg está disponible, también comprueba el manifiesto firmado contra la huella fijada de la clave de firma de Clearcote. Solo se aplica al build abierto: con una clave de licencia, download() descarga en su lugar el build con licencia actual (elige uno con version o releaseChannel; consulta cómo elegir un build). En .NET, DownloadAsync solo descarga el build abierto; para descargar de antemano el build con licencia, usa ExecutablePathAsync(new LaunchOptions { LicenseKey = … }).
Importar un perfil de un Chrome real
En lugar de la persona sintética derivada del seed, puedes hacer que Clearcote reporte los valores capturados de una máquina real. Captura uno desde un Chrome donante con el recolector de tools/fingerprint-collect (abre collect.html, haz clic en Capture y se descarga un perfil JSON), o parte del dataset open source de 10k registros chrome-fingerprints con el conversor incluido convert_dataset.py. La captura abarca navigator, la geometría de la pantalla, vendor/renderer de WebGL + los límites de getParameter, Web Audio, las voces de síntesis de voz, las fuentes, los códecs y las características de CSS @media.
Pásale el perfil al SDK como ruta de archivo, objeto o string JSON: el SDK se encarga del empaquetado gzip + base64 por ti. Los campos presentes en el perfil reemplazan lo que el navegador reportaría de otro modo; los campos ausentes usan los valores por defecto del navegador. El SDK también deriva Accept-Language del navigator.languages del perfil cuando no defines uno explícitamente. Ambos builds leen perfiles importados; en el build con licencia se aplican por completo desde 151 r19.
No pases un seed de fingerprint junto con un perfil. El seed activa la capa de farbling, que un scoring estricto interpreta como manipulación del canvas, y no te aporta nada que el perfil no proporcione ya. Usa uno u otro. Un perfil solo sustituye los valores reportados: nunca cambia lo que renderiza tus píxeles, así que dos cuentas con dos perfiles distintos en una misma máquina siguen produciendo un canvas idéntico. Si necesitas separar el renderizado por cuenta, usa un seed por cuenta y ningún perfil.
from clearcote import launch
browser = launch(fingerprint_profile="profile.json", disable_gpu_fingerprint=True, fingerprint_noise=False)
page = browser.new_page()
page.goto("https://example.com")
browser.close()Coherente por defecto (sin flags adicionales)
Lances con lo que lances, el motor mantiene las superficies secundarias en concordancia con la persona que elegiste; con una persona de Windows, con lo que reporta un Chrome real de escritorio en Windows. La plataforma de la persona es, por defecto, el sistema operativo de tu host, así que en un host Linux estas superficies siguen a una persona de Linux, a menos que pases platform. Los límites de getParameter de WebGL (WebGL1 + WebGL2) reportan los valores de la persona, con un tope en lo que la GPU de esta máquina realmente puede ofrecer, y UNMASKED_RENDERER / UNMASKED_VENDOR son constantes durante la sesión (la misma GPU en todos los sitios, acorde con la persona). Con una persona de Windows, navigator.getBattery() reporta un equipo de escritorio conectado a la corriente, navigator.connection una conexión residencial y AudioContext la frecuencia de muestreo y la latencia de Windows WASAPI correspondientes. getScreenDetails() reporta un único monitor, y @media (pointer: fine) / (hover: hover) corresponden a un equipo de escritorio con mouse.
Comprobado en 153 r26, en modo headed en Windows (septiembre de 2026): BrowserScan no reporta ningún bot y CreepJS muestra 0% stealth. Detrás de un proxy, agrega geoip para que la zona horaria y WebRTC coincidan con la salida.
Eventos de consola y de errores de página
El motor no reenvía los eventos de consola ni de errores de página a los clientes de automatización, así que page.on("console") y page.on("pageerror") no reciben nada. Es así por diseño: reenviarlos es precisamente lo que mide una sonda de presencia de automatización. Los handlers window.onerror y unhandledrejection dentro de la página se disparan con normalidad, así que, para capturar la salida de la consola, recógela dentro de la página y léela con page.evaluate(). Los SDK de Python y Node muestran un aviso sobre esto una sola vez, al lanzar.
Ajuste automático a la región del proxy (geoip)
Pasa geoip junto con un proxy y el SDK resuelve la IP de salida del proxy —consultándola en la base de datos offline geoip-all-in-one— y configura para esa región una combinación coherente de zona horaria + idioma principal de navigator + Accept-Language + IP de WebRTC. Se acabó ajustar a mano la zona horaria de cada proxy:
from clearcote import launch
browser = launch(
fingerprint="user-7423",
proxy={"server": "http://host:8080", "username": "u", "password": "p"},
geoip=True, # timezone + language auto-matched to the proxy's region
)También configura la geolocalización y la lista completa de navigator.languages para la región, funciona con proxies HTTP y SOCKS5 (credenciales incluidas) y está en los tres SDK (Geoip = true en .NET). La primera ejecución descarga la base de datos (unos 50 MB). Si no se puede resolver la región, el lanzamiento se detiene con un GeoipError en lugar de arrancar sin avisar con el reloj y el idioma de esta máquina; define tanto timezone como acceptLanguage para lanzar de todos modos. La consulta tiene 20 segundos de plazo (CLEARCOTE_GEOIP_TIMEOUT_SECONDS); en .NET el error es GeoipException. Sin geoip ni un timezone explícito, la zona horaria sigue al idioma: en-US significa Nueva York, incluso detrás de un proxy alemán.
¿Prefieres configurarlo tú? Usa acceptLanguage (Node) / accept_language (Python), por ejemplo "en-US,en": define el header Accept-Language, el array completo navigator.languages y navigator.language, y Intl / toLocaleString también lo siguen.
Entrada humanizada (humanize & showCursor)
Pasa humanize y toda la entrada —mover, hacer clic, arrastrar, hacer scroll y escribir— sigue un mismo estándar humanizado, tanto a nivel de página (page.click / hover / type / fill / mouse.* / keyboard.type) como a nivel de locator (locator.click / type / fill / pressSequentially / dragTo / …). Los movimientos siguen una trayectoria bézier cúbica ligeramente curvada que parte de la última posición del cursor y se recorre como una suma de submovimientos de jerk mínimo (un movimiento balístico principal + uno correctivo: la velocidad con varios picos de un gesto real de alcance, no una única campana simétrica), y todo se despacha como eventos reales y confiables (isTrusted === true, y navigator.webdriver sigue en false). En Pro, los clics por coordenadas (mouse.click(x, y)) siguen movimientos grabados de personas reales; todo lo demás, y cada clic en el build abierto y en Gratis con GitHub, usa trayectorias generadas. Agrega showCursor para dibujar un punto que sigue el movimiento y así poder verlo.
Como los movimientos usan entrada nativa, un botón presionado con mouse.down() se mantiene presionado durante el movimiento, así que down → move → up es un arrastre real con el botón presionado (los controles de arrastrar hasta una posición, como los sliders, reciben un arrastre realmente presionado), y locator.dragTo también está humanizado. La escritura va tecla por tecla, con tiempos entre teclas aleatorios + pausas entre palabras y alguna que otra corrección de dedazo; el scroll usa inercia ease-out con alguna pausa ocasional de lectura. fill enfoca el campo y escribe el valor (los valores de más de ~200 caracteres se mantienen atómicos para que los rellenados masivos no se eternicen).
from clearcote import launch
browser = launch(fingerprint="seed-123", humanize=True, show_cursor=True)
page = browser.new_page()
page.goto("https://example.com")
page.click("text=Sign in") # eased curve, then a trusted click
page.fill("#email", "you@example.com") # focus + key-by-key human typing
page.locator("#password").type("s3cr3t") # locators are humanized too
# held-button drag (e.g. a slider): the press stays held across the move
x0, y0, x1 = 100, 300, 400 # the handle's start, and where to release it
page.mouse.move(x0, y0); page.mouse.down()
page.mouse.move(x1, y0); page.mouse.up()
browser.close()Comprobación de coherencia del backend de renderizado (checkRenderCoherence)
Una persona puede declarar una GPU, pero si en realidad la página la pinta un rasterizador por software (SwiftShader / llvmpipe, algo habitual en headless sin GPU), un detector estricto puede notarlo. Analiza una página en vivo: la función lee el vendor/renderer de WebGL (sin enmascarar) que la página ve realmente, señala un fallback a rasterizador por software (una señal fatal de headless: activa el canvas bridge o ejecuta en modo headed sobre una GPU real) o un par vendor/renderer incoherente, y devuelve un veredicto estructurado. Pasa la GPU declarada para comprobar también la familia renderizada. Disponible en síncrono, en asíncrono y en Node.
from clearcote import launch, check_render_coherence
browser = launch(fingerprint="seed-123")
page = browser.new_page(); page.goto("about:blank")
verdict = check_render_coherence(page) # {'renderer', 'software_suspected', 'coherent', 'warnings'}
if not verdict["coherent"]:
print(verdict["warnings"]) # e.g. software rasterizer / incoherent GPU family
browser.close()Perfiles & persistencia
Mantén una identidad estable entre ejecuciones reutilizando el mismo seed de fingerprint, y conserva las cookies y el almacenamiento con un directorio de datos de usuario:
from clearcote import launch_persistent_context
ctx = launch_persistent_context(r"C:\clearcote\profiles\acme", fingerprint="acme-tenant-7", headless=False)
page = ctx.pages[0] if ctx.pages else ctx.new_page()Las cookies de un directorio de perfil se cifran con una clave ligada a la máquina que lo creó. Para copiar un perfil a otra máquina con sus cookies intactas, pasa portableProfile: true / portable_profile=True (la clave viaja con el perfil) o encryptionKey / encryption_key (la clave se deriva de tu secreto y no se escribe nada sensible en disco). Build con licencia, Python & Node.
El SDK también tiene personas guardadas: un Profile guarda las opciones de huella digital, la configuración del proxy, la del canvas bridge y otras opciones de lanzamiento como JSON en ~/.clearcote/profiles (puedes cambiarlo con CLEARCOTE_PROFILE_DIR).
from clearcote import Profile, launch, launch_persistent_context
Profile("acct-1", {
"fingerprint": "acct-1",
"gpu_vendor": "Google Inc. (Intel)",
"gpu_renderer": "ANGLE (Intel, Intel(R) UHD Graphics ... D3D11)",
"canvas_bridge": {"url": "ws://127.0.0.1:8443", "auth": "user:secret"},
}).save()
ctx = launch_persistent_context(r"C:\clearcote\profiles\acct-1", profile="acct-1")
browser = launch(profile="acct-1", headless=False)Los perfiles guardados están en texto plano y pueden contener credenciales como canvasBridge.auth. Trata los archivos de perfil como entrada de confianza y no los incluyas en un commit ni los compartas.Más opciones de lanzamiento
extensions: una lista de rutas a directorios de extensiones desempaquetadas (genera--load-extension+--disable-extensions-except).disablePrivacySandbox/disable_privacy_sandbox: ponlo entruepara desactivar las APIs de Privacy Sandbox (Topics, FLEDGE / Protected Audience, Shared Storage, Private Aggregation, Fenced Frames). Desactivado por defecto desde la 0.23: la persona por defecto se presenta como Google Chrome, que incluye todas ellas. Actívalo solo cuando la persona sea un Chromium sin servicios de Google. WebUSB no se ve afectado.agentTyping/agent_typing: la cadencia de tecleo del agente (humanpor defecto /fast/instant). Consulta Agente.tlsProfile: mantiene el TLS ClientHello coherente con la versión de Chrome que declara la persona, para que la capa de red siga al UA (y no al TLS nativo del build). El valor por defecto,"match-persona", sigue abrandVersion;"native"lo deja sin tocar;"chrome-<major>"fija una versión mayor. Consulta Flags de huella digital.platform: "android": una persona móvil en la medida de lo posible (táctil, puntero grueso, pantalla/DPR móvil, WebGL Mali/Adreno, viewport de teléfono). En un motor de escritorio, el renderizado de la GPU sigue siendo de escritorio; combínalo con el canvas bridge para lograr coherencia de renderizado.storageQuota,fingerprintProfile,canvasBridge,webrtcIp,acceptLanguage,disableGpuFingerprint,fingerprintNoise: consulta Flags de huella digital.
Opciones más recientes (build con licencia)
Estas opciones necesitan el build con licencia (Gratis con GitHub o Pro). Entre corchetes se indica la revisión del motor que necesita cada una; con un motor más antiguo, el SDK omite las de 152 r22 con una advertencia y el motor ignora el resto.
allowThirdPartyCookies/allow_third_party_cookies: permite las cookies de terceros, como hace Chrome estándar. La base sin servicios de Google las bloquea por defecto, lo que rompe los frames embebidos de inicio de sesión, de pago y de desafío que dependen de ellas. [152 r22]transparentProxy/transparent_proxy: oculta el proxy en los headers de las solicitudes y en los tiempos de conexión (las solicitudes HTTP sin cifrar no llevan ningún header de proxy; las conexiones a través del proxy reportan tiempos como los de una conexión reutilizada). Necesita un proxy. [152 r22]fingerprintVoices: false/fingerprint_voices=False: conserva las voces de síntesis de esta máquina en lugar de la lista de la persona. [152 r22]fingerprint: "off": lanza sin ninguna persona, para diagnosticar problemas. [152 r22]socks5Udp/socks5_udp: lleva el UDP de WebRTC a través de un proxysocks5://, para que las conexiones de voz, video y peer-to-peer funcionen y sigan saliendo desde la dirección del proxy. El proxy tiene que permitirlo; muchos pools residenciales no lo hacen. [151 r17]portableProfile/encryptionKey: perfiles que puedes copiar entre máquinas (ver arriba). [151 r14; Python & Node]personaSchema: 2/persona_schema=2: un modelo de identidad opcional en el que la pantalla y el chip gráfico corresponden al procesador y la memoria que declara la persona. Desactivado por defecto, así que cada seed existente conserva su identidad. AgregarealGpuHost/real_gpu_hostsolo en una máquina con una tarjeta gráfica real. [151 r19; Python & Node]shaderDialect: "hlsl": consulta Dialecto de shaders. [151 r15]profile: "auto": lanza una huella digital real capturada, elegida para esta máquina, en lugar de un seed; ajústala conprofileSelect/profile_select. Python & Node. En Node funciona conlaunch()por defecto,launchPersistentContext()yserve()a partir del SDK 0.31.1; con 0.31.0 y anteriores hacía faltaephemeralProfile: falsey fallaba conserve().
No dependen de un build del motor:
version/releaseChannel: elige un build; consulta cómo elegir un build.licenseKey/license_key/LicenseKey: la clave de licencia, si no está enCLEARCOTE_LICENSE_KEYni en~/.clearcote/license.key.licenseThroughProxy/license_through_proxy(oCLEARCOTE_LICENSE_THROUGH_PROXY=1): envía las llamadas de licencia a través del proxy del lanzamiento en lugar de hacerlas desde esta máquina.ephemeralProfile/userDataDirenlaunch(): consulta la nota de Con el SDK, más arriba.widevine: true: reproducción con DRM (ambos builds); consulta Widevine & DRM.quiet: silencia las advertencias de lanzamiento y la salida de progreso del SDK.
Valores por defecto coherentes (se pueden cambiar)
El SDK aplica algunos valores por defecto correctos desde el punto de vista del stealth para que no se cuelen las señales más obvias:
- Los lanzamientos en modo headed no emulan el viewport por defecto (
viewport: null/no_viewport=True), así quewindow.innerWidthsigue a la ventana real del sistema operativo: un 1280×720 emulado dentro de una ventana real es una señal de ventana imposible. Pasa unviewportexplícito para cambiarlo. - WebRTC usa por defecto
disable_non_proxied_udp, así que ningún tráfico UDP sale por fuera del proxy y la dirección propia de tu máquina se mantiene privada. Solo lo cambia tu propio--webrtc-ip-handling-policyenargsy, en el build abierto,webrtcIp/geoip, que vuelven a dejar salir el UDP de WebRTC por tu propia conexión (el build con licencia bloquea el UDP de WebRTC en cualquier caso). Con esa política y sinwebrtcIp, una página no obtiene ningún candidato ICE; detrás de un proxy, pasageoip(owebrtcIp) para que WebRTC reporte la dirección del proxy, osocks5Udppara llevar UDP real a través de un proxy SOCKS5. - Detrás de un proxy, QUIC / HTTP-3 está desactivado, igual que en un Chrome real detrás de un proxy, así que no se intenta enviar UDP por fuera de él.
- La plataforma de la persona es, por defecto, el sistema operativo de tu host, y la marca, Google Chrome. Sin
timezonenigeoip, la zona horaria sigue al idioma (en-US→ Nueva York). - Las credenciales del proxy van al navegador, no a Playwright, donde el motor lo soporta: SOCKS5 siempre (Playwright no puede autenticarse en SOCKS5 en absoluto) y HTTP(S) en 151 r19+, para que la caché de páginas siga activa (Python & Node). Ver más abajo.
humanizeejecuta una comprobación previa de accionabilidad antes de cada clic confiable (visible / habilitado / estable + una comprobación deelementFromPointpara ver si está tapado) y, si no la supera, recurre al clic nativo, así que un clic confiable nunca se dispara bajo un overlay ni en mitad de una animación.
SOCKS5 con credenciales
Chromium estándar no puede autenticarse en absoluto en un proxy SOCKS5 —no implementa la subnegociación de usuario/contraseña—, así que la solución habitual es un relay local que guarda las credenciales. El build con licencia lo implementa en el motor (RFC 1929), así que no hace falta ningún relay. Pasa el usuario y la contraseña como campos separados o dentro de la dirección (socks5://user:pass@host:port); de cualquiera de las dos formas, el SDK se los pasa al motor. Las credenciales dentro de la dirección requieren el SDK 0.31.1 o posterior: los SDK anteriores dejaban que Playwright las descartara, y el proxy no recibía ningún login.
from clearcote import launch_persistent_context
ctx = launch_persistent_context(
"./profile",
proxy={"server": "socks5://proxy.example.net:1080", "username": "user", "password": "pass"},
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto("https://api.ipify.org?format=json") # confirm the exit IP is the proxy'sRequiere el build con licencia (Gratis con GitHub o Pro, motor 151 r14 o posterior); el build abierto no puede autenticarse en un proxy SOCKS5, así que con él usa un relay local o un proxy HTTP.
Verifica siempre la dirección de salida antes de confiar en una sesión. Un proxy que falla en modo abierto sin avisar envía el tráfico desde tu propia IP, y todas las demás precauciones dejan de servir. Compruébala una vez al lanzar contra un servicio que te devuelva tu dirección, en lugar de darla por supuesta.
Consejo: deriva el seed del id de tu propia cuenta/tenant para que cada identidad sea reproducible: mismo seed, misma huella digital del navegador, siempre. Consulta la lista completa de switches en Flags de huella digital.