Volver al blog

El Árbol de Accesibilidad No Es una Página Más Barata: Midiendo Lo Que Los Agentes Lee

Michael Lee
Michael Lee

Expert Network Defense Engineer

07-Aug-2026

TL;DR:

  • El árbol de accesibilidad es lo que la mayoría de los agentes del navegador alimentan a su modelo en lugar de HTML en bruto, recuperado a través del Protocolo de Chrome DevTools con Accessibility.getFullAXTree.
  • No es una compresión de la página. En una página de catálogo en vivo: HTML en bruto 9,824 tokens, innerText 582, árbol de accesibilidad completo 5,951 — el árbol cuesta 10.2× texto plano.
  • La razón es visible en los roles de nodo: de 1,401 nodos, 267 son StaticText y 391 son InlineTextBox, por lo que la mayoría de las cadenas se almacenan dos veces.
  • Filtrar a roles interactivos da 742 tokens a través de 114 nodos — 1.3× innerText — manteniendo todo lo que un agente necesita para hacer clic.
  • Medido a través de Chromium local y a través del Navegador de Raspado Sin Esfuerzo, cada número fue idéntico, por lo que la representación de la página de un agente no se desplaza entre entornos.
  • El plan gratuito de Scrapeless cubre la ejecución del navegador en la nube en esta guía.

Cada agente de navegador tiene que responder a una pregunta antes de poder hacer algo: ¿qué le das al modelo? Un documento HTML de 51,004 caracteres no se ajusta a un presupuesto de indicaciones razonable, y una captura de pantalla cuesta tokens de imagen y pierde cadenas exactas. La tercera respuesta usual es el árbol de accesibilidad.

Esa respuesta es correcta sobre la forma y incorrecta sobre el precio. El árbol lleva roles y nombres — link, button, heading — que es exactamente lo que un agente necesita para decidir dónde hacer clic. También es, sin filtrar, un orden de magnitud más caro que el texto plano de la página.

Esta guía extrae el árbol a través de CDP, lo mide contra las alternativas en la misma página en vivo y muestra el filtro que hace que valga la pena enviarlo.

Qué es el Árbol de Accesibilidad

El navegador construye un segundo árbol junto al DOM, para lectores de pantalla. Cada nodo lleva un role — uno de los tipos de control definidos por la especificación WAI-ARIA — y un name, la cadena que un lector de pantalla anuncia, derivada por el algoritmo en el Cálculo de Nombre y Descripción Accesible. Los envoltorios presentacionales colapsan, se resuelven los atributos aria-*, y los elementos interactivos se etiquetan.

Esa estructura es la razón por la que a los agentes les gusta. link: Books to Scrape es directamente accionable de una manera que un <div class="col-sm-8 h1"><a href="..."> no lo es. El árbol es expuesto por el dominio de Accesibilidad de el Protocolo de Chrome DevTools, y es los mismos datos que Chrome muestra en su propio panel de accesibilidad. Si el CDP en sí es nuevo, el prólogo del protocolo cubre el transporte sobre el que viaja este artículo.

Instalar

bash Copy
pip install playwright tiktoken
playwright install chromium

tiktoken está aquí solo para contar tokens; la extracción necesita solo Playwright. La ejecución de verificación utilizó Playwright 1.59.0 y tiktoken 0.12.0.

Extraer el Árbol

Playwright expone una sesión CDP en bruto, que es cómo accedes a dominios que no envuelve:

python Copy
    cdp = page.context.new_cdp_session(page)
    nodes = cdp.send("Accessibility.getFullAXTree")["nodes"]

Cada nodo es un diccionario cuyos role y name son objetos con una clave value. La mayoría de los nodos no tienen nombre en absoluto — contenedores, nodos ignorados y cajas de diseño — por lo que la proyección útil es rol más nombre para los nombrados:

python Copy
def ax_lines(nodes, roles=None):
    lines = []
    for node in nodes:
        role = (node.get("role") or {}).get("value", "")
        name = ((node.get("name") or {}).get("value") or "").strip()
        if not name:
            continue
        if roles is not None and role not in roles:
            continue
        lines.append(f"{role}: {name}")
    return lines

En un catálogo de libros en vivo que genera líneas como:

text Copy
RootWebArea: All products | Books to Scrape - Sandbox
heading: All products
link: Books to Scrape
StaticText: We love being scraped!
link: Home
StaticText: /
InlineTextBox: /

Dos de esas siete líneas son la misma barra. Esa duplicación es toda la historia del costo.

Mídelo Contra las Alternativas

Cuenta tokens para las tres representaciones de la misma página:

python Copy
def measure(page, label):
    html = page.content()
    text = page.evaluate("document.body.innerText")
    cdp = page.context.new_cdp_session(page)
    nodes = cdp.send("Accessibility.getFullAXTree")["nodes"]

    full = "\n".join(ax_lines(nodes))
    acts = "\n".join(ax_lines(nodes, INTERACTIVE))
    roles = [(n.get("role") or {}).get("value", "") for n in nodes]
text Copy
  raw html            tokens=  9824  chars=51004
  innerText           tokens=   582  chars=2029
  AX full             tokens=  5951  named_nodes=864
  AX interactive only tokens=   742  nodes=114
  AX total nodes      1401
  StaticText nodes    267
  InlineTextBox nodes 391
  price in html/text/ax: True/True/True

El árbol de accesibilidad completo cuesta 5,951 tokens contra innerText's 582. Es 10.2× el texto plano de la misma página, y alrededor del 60% del HTML en bruto que se suponía que debía reemplazar.

Los conteos de roles lo explican. De 1,401 nodos, 267 son StaticText y 391 son InlineTextBox — 658 nodos, casi la mitad del árbol, dedicados a texto que la página ya contiene una vez. Los nodos InlineTextBox son fragmentos de diseño: una sola oración dividida en dos líneas renderizadas se convierte en dos de ellos. Enviar el árbol completo significa pagar por cada cadena al menos dos veces.

Vale la pena decirlo claramente: no se perdió nada. El precio £51.77 está presente en el HTML, en innerText, y en el árbol de accesibilidad. En esta página, el árbol es más caro, no menos completo.

Filtrar a Lo que un Agente Puede Actuar

Un agente que lee una página necesita texto. Un agente que opera una página necesita las cosas en las que puede hacer clic y escribir. Esos son un pequeño conjunto de roles:

python Copy
INTERACTIVE = {"link", "button", "textbox", "combobox", "checkbox", "radio", "menuitem", "tab"}

Pasar ese conjunto a la misma función colapsa el árbol a 114 nodos y 742 tokens — 1.3× innerText, y 8× más barato que la versión sin filtrar. Cada enlace y control mantiene su nombre accesible, que es a lo que se refiere una instrucción de clic.
Eso sugiere una división más que una elección. Utiliza innerText cuando el modelo tiene que leer y extraer, usa el árbol filtrado por rol cuando tiene que decidir qué operar, y envía ambos cuando la tarea necesita ambos; juntos son 1,324 tokens, aún un octavo del HTML sin procesar. La guía del bucle del agente cubre lo que sucede después de que se toma esa decisión.

Nada de lo anterior necesita un navegador local. El Navegador de Scraping Scrapeless habla el mismo protocolo, por lo que la sesión de CDP y la llamada de accesibilidad no cambian; solo difiere la conexión:

python Copy
    endpoint = (
        "wss://browser.scrapeless.com/api/v2/browser"
        f"?token={os.environ['SCRAPELESS_API_KEY']}&sessionTTL=180&proxyCountry=ANY"
    )
    with sync_playwright() as p:
        browser = p.chromium.connect_over_cdp(endpoint, timeout=90000)
        page = browser.new_page()
        page.goto(URL, wait_until="domcontentloaded")
        measure(page, "Scrapeless Scraping Browser")
        browser.close()

La ejecución en la nube devolvió cifras idénticas en cada métrica: 9,824 / 582 / 5,951 / 742 tokens, 1,401 nodos, la misma cantidad de StaticText y InlineTextBox. Eso tiene una consecuencia práctica. Una representación de página que cambia entre tu laptop y producción haría que el comportamiento del agente fuera irreproducible; este no lo hace. Mantén la clave en el entorno como SCRAPELESS_API_KEY, y consulta la introducción al Navegador de Scraping para los parámetros de sesión restantes.

Comenzar toma un minuto — crea una cuenta gratuita en Scrapeless y el plan gratuito cubre esta ejecución.

Ejecútalo

bash Copy
export SCRAPELESS_API_KEY="your-api-key"
python3 ax_demo.py

La salida completa de la ejecución de verificación:

text Copy
playwright 1.59.0 | tiktoken 0.12.0
[local chromium]
  raw html            tokens=  9824  chars=51004
  innerText           tokens=   582  chars=2029
  AX full             tokens=  5951  named_nodes=864
  AX interactive only tokens=   742  nodes=114
  AX total nodes      1401
  StaticText nodes    267
  InlineTextBox nodes 391
  price in html/text/ax: True/True/True

[Scrapeless Scraping Browser]
  raw html            tokens=  9824  chars=51004
  innerText           tokens=   582  chars=2029
  AX full             tokens=  5951  named_nodes=864
  AX interactive only tokens=   742  nodes=114
  AX total nodes      1401
  StaticText nodes    267
  InlineTextBox nodes 391
  price in html/text/ax: True/True/True

ratios vs innerText: html=16.9x  ax_full=10.2x  ax_interactive=1.3x

Solución de Problemas

Accessibility.getFullAXTree devuelve muy pocos nodos. El árbol se construye de manera perezosa. Navega y espera contenido antes de solicitarlo, y llama a Accessibility.enable primero si tu cliente no lo hace implícitamente.

Cada nodo tiene un nombre vacío. Estás leyendo node["name"] directamente. Tanto role como name son objetos; la cadena está en node["name"]["value"].

Las cuentas de nodos difieren entre dos ejecuciones de la misma página. Los nodos InlineTextBox siguen el ajuste de línea, por lo que un ancho de viewport diferente cambia cuántos hay. Establece un viewport explícito cuando el conteo mismo importa.

El árbol omite algo visible en la pantalla. El contenido marcado aria-hidden, y el texto generado puramente por CSS ::before/::after, está intencionalmente ausente. Lee esos desde el DOM en su lugar; el árbol de accesibilidad no es un sustituto de eso.

Los roles se ven extraños. StaticText, InlineTextBox y RootWebArea son roles internos de Chrome en lugar de roles ARIA, por eso filtrar en una lista permitida solo de ARIA elimina la mayoría del árbol. Las Mapeos de la API de Accesibilidad Central definen qué roles se requiere que exponga un navegador; cualquier cosa más allá de ese conjunto es específica del motor.

Conclusión

El árbol de accesibilidad es una buena respuesta a lo que se debe dar a un agente, y un mal valor predeterminado en su forma bruta. En la página medida aquí cuesta 5,951 tokens — diez veces el texto plano que a menudo se supone que comprime — porque casi la mitad de sus nodos existen para describir un texto que la página ya declaró una vez.

Filtrado a los roles sobre los cuales un agente puede actuar, el mismo árbol es de 742 tokens y aún nombra cada control. Esa es la versión que poner en un prompt, emparejada con innerText cuando la tarea también requiere lectura. Mide ambos en tu propio objetivo antes de elegir: los números aquí provienen de una página de catálogo, y la proporción depende completamente de cuánto de la página es prosa y cuánto son controles.

¿Listo para intentarlo? Comienza con el plan gratuito de Scrapeless y consulta los precios actuales para volúmenes más altos.

FAQ

P: ¿Debería enviar el árbol de accesibilidad en lugar de HTML?

Envía una versión filtrada de él. Sin filtrar midió 5,951 tokens frente a 9,824 para el HTML sin procesar; un ahorro, pero mucho menos de lo esperado. Restringido a roles interactivos, cae a 742, que es donde está la verdadera reducción.

P: ¿Es el árbol de accesibilidad más barato que el texto plano?

No, y esta es la concepción errónea común. En la página medida aquí costó 10.2× innerText. El valor del árbol son las etiquetas de rol que añade, no un payload más pequeño.

P: ¿Por qué hay tantos nodos InlineTextBox?
Son fragmentos de diseño: uno por cada línea de texto renderizada. Una oración que se extiende a través de dos líneas produce dos de ellos, sobre el nodo StaticText que sostiene la misma cadena. Esa es la razón por la cual 658 de 1,401 nodos en la página de prueba eran duplicados de texto.

P: ¿El árbol contiene todo en la página?

No del todo. El contenido aria-hidden y el texto generado por pseudo-elementos CSS son deliberadamente excluidos. En la página medida aquí, nada necesario estaba ausente: el precio aparecía en las tres representaciones; pero confirma eso en tu propio objetivo en lugar de asumirlo.

P: ¿Necesito un Chrome local para leer el árbol de accesibilidad?

No. La ejecución en la nube en esta guía produjo números idénticos en bytes a Chromium local, porque ambos hablan el mismo protocolo. Solo cambia la línea de conexión.

En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.

Artículos más populares

Catalogar