La visibilidad orgánica ya no se entiende solo desde el ranking de Google. Cada vez tiene más sentido analizar si una URL está preparada para ser reutilizada, sintetizada o citada por sistemas generativos como ChatGPT, Gemini, Perplexity o Copilot. Ten en cuenta una cosa, y es que una agencia GEO no sustituye al SEO clásico, pero añade una capa que hasta hace poco apenas se medía: la capacidad real de un contenido para servir como base de respuesta en entornos generativos. En este artículo voy a explicar cómo montar una auditoría GEO de URLs con Screaming Frog utilizando JavaScript personalizado, una llamada por URL a OpenAI y una extracción posterior por XPath. La idea es que puedas replicarlo, adaptarlo y usarlo como metodología operativa en tus propios proyectos o en los de tus clientes.
 

Qué persigue este sistema de auditoría GEO

La lógica del sistema es sencilla: analizar cada URL del sitio y asignarle una serie de métricas que permitan estimar su potencial de reutilización por IA. En lugar de quedarse en un juicio subjetivo, el sistema devuelve columnas concretas que facilitan ordenar, filtrar y priorizar.

En este caso vamos a medir si la URL define pronto el concepto principal, si el contenido tiene claridad conceptual, si la estructura facilita la extracción de información, si existen bloques reutilizables y si el texto responde de forma directa al tema. Después, esas señales se sintetizan en un GEO_SCORE, que sirve como índice operativo.

Puntuación GEO en Screaming Frog

 

Por qué Screaming Frog encaja bien en una auditoría GEO

Screaming Frog es una herramienta especialmente útil para este enfoque porque combina tres capacidades muy valiosas en un mismo flujo: rastreo a nivel de URL, renderizado JavaScript y extracción personalizada del HTML resultante. No fue diseñada específicamente para GEO, pero encaja muy bien en este tipo de análisis.

La primera ventaja es evidente: puedes trabajar sobre decenas, cientos o miles de URLs sin depender de revisiones manuales. La segunda es más interesante todavía: el JavaScript personalizado permite ejecutar lógica en cada página durante el crawl. En este caso, esa lógica se utiliza para localizar el contenido principal, limpiarlo, enviarlo a la API, recibir una respuesta estructurada e inyectar el resultado en el DOM renderizado.

La tercera ventaja es operativa. Como los resultados quedan insertados en el HTML renderizado, Screaming Frog puede extraer cada métrica mediante XPath y convertirla en una columna independiente. Esto hace posible transformar una auditoría GEO en un sistema escalable y reproducible.

 

Resultados reales de una auditoría GEO en 4 URLs analizadas con Screaming Frog

La siguiente tabla muestra un ejemplo real de resultados obtenidos al ejecutar una auditoría GEO con Screaming Frog y un análisis de contenido mediante modelos generativos. Las puntuaciones indican el potencial de reutilización del contenido por sistemas de respuesta generativa.

 

Ejemplo de puntuación GEO en URLs analizadas
URL analizadaGEO ScoreClasificación GEONivel de optimización recomendado
/69Potencial GEO medioMejorar estructura explicativa
/estudios-seo/68Potencial GEO medioRefinar bloques citables
/consultor-seo/58Potencial GEO bajoReforzar definición y claridad
/presupuesto-seo/57Potencial GEO bajoReestructurar contenido

 

En este ejemplo aparecen dos grupos claros. La home de agencia de SEO y la URL de estudios SEO muestran un potencial GEO medio con margen de mejora, mientras que las páginas de consultor SEO y presupuesto SEO presentan mayor debilidad estructural para sistemas generativos. El patrón más repetido en las URLs con menor puntuación es la falta de definición temprana del tema, menor claridad explicativa y menor número de bloques reutilizables.

 

Qué métricas devuelve la auditoría GEO

El sistema trabaja con cuatro grupos de métricas: principales, derivadas, accionables y auxiliares.

screaming frog GEO audit

 

Métricas principales

  • IA_REUSE_SCORE: probabilidad general de reutilización por IA.
  • DEFINITION_SCORE: calidad y claridad de la definición principal.
  • CLARITY_SCORE: claridad conceptual y facilidad de síntesis.
  • STRUCTURE_SCORE: calidad estructural del contenido.
  • CITABLE_BLOCKS: número estimado de fragmentos reutilizables o citables.

 

Métricas derivadas

  • GEO_SCORE: índice global de potencial GEO.
  • GEO_PRIORITY: prioridad operativa de optimización.
  • GEO_STATUS: estado general del contenido desde una perspectiva GEO.
  • LLM_RISK: riesgo principal de ser ignorado o mal aprovechado por sistemas generativos.

 

Campos accionables

  • MAIN_ISSUES: principales problemas detectados.
  • ACTIONABLE_ADVICE: recomendaciones aplicables sobre esa URL.
  • TOP_PRIORITY_FIX: la mejora más importante que conviene aplicar primero.

 

Señales auxiliares

  • CONTENT_WORDS: número de palabras del contenido extraído.
  • H2_COUNT: número de H2.
  • LIST_COUNT: número de elementos de lista.
  • EXTRACTED_CHARS: caracteres totales enviados al análisis.

 

Qué significa cada métrica

 

IA_REUSE_SCORE

Es la puntuación general de 0 a 100 sobre probabilidad de reutilización por IA. Cuanto más alto es el valor, más probable es que el contenido tenga rasgos útiles para sistemas generativos.

 

DEFINITION_SCORE

Mide si la URL define de forma clara y suficientemente temprana el tema principal. Un contenido que tarda demasiado en explicar de qué trata suele rendir peor en este punto.

 

CLARITY_SCORE

Evalúa la claridad conceptual, la separación de ideas y la facilidad con la que un modelo puede sintetizar el contenido sin confusión.

 

STRUCTURE_SCORE

Valora jerarquía, organización semántica, presencia de listas, bloques reutilizables y facilidad de extracción.

 

CITABLE_BLOCKS

Es el número estimado de bloques que una IA podría reutilizar. Aquí suelen contar definiciones, clasificaciones, comparativas, listas de factores o fragmentos explicativos autónomos.

 

GEO_SCORE

Es una puntuación agregada que permite priorizar URLs. No pretende ser una verdad absoluta, sino una forma operativa de ordenar qué páginas conviene revisar antes.

 

GEO_PRIORITY

Clasifica la URL en cuatro niveles de actuación:

  • Tier 1 – AI Ready
  • Tier 2 – Improve
  • Tier 3 – Weak
  • Tier 4 – Rebuild

 

GEO_STATUS

Resume el estado general del contenido:

  • High GEO Potential
  • Medium GEO Potential
  • Low GEO Potential
  • Very Low GEO Potential

 

LLM_RISK

Indica el principal riesgo detectado. Puede señalar problemas como weak_definition, poor_structure, low_clarity o few_citable_blocks.

 

Arquitectura del sistema

El sistema se apoya en un flujo de ocho pasos:

  1. Screaming Frog rastrea una URL.
  2. El script localiza el contenido principal.
  3. Limpia ruido y extrae señales estructurales.
  4. Envía el contenido a OpenAI.
  5. OpenAI devuelve un JSON estructurado con las métricas.
  6. El script calcula GEO_SCORE y GEO_PRIORITY.
  7. Los resultados se insertan en un nodo oculto del DOM.
  8. Screaming Frog extrae esos valores con XPath y los convierte en columnas.

La clave está en que solo se realiza una llamada por URL y luego se reutiliza esa respuesta para poblar todas las columnas que necesitas.

 

Qué información se envía al modelo

No se envía el HTML completo. Eso sería menos eficiente, más caro y más ruidoso. Lo que se hace es construir un bloque de contenido con las partes más útiles de la página:

  • Title
  • Meta description
  • H1
  • H2
  • H3
  • Párrafos
  • Listas
  • Blockquotes

Además, se añaden señales cuantitativas:

  • Número de H2
  • Número de listas
  • Número de párrafos
  • Número de palabras
  • Número de caracteres

Con eso se consigue un equilibrio razonable entre contexto, coste y limpieza del input.

 

Recomendaciones previas antes de montarlo

 

Usa una API key nueva

No conviene reutilizar claves expuestas o usadas en otros entornos. Lo razonable es trabajar con una clave específica para este tipo de pruebas.

 

Empieza con pocas URLs

Antes de lanzar el sistema a cientos de páginas, valida el flujo con una muestra pequeña. Eso te permite comprobar que las columnas se llenan bien y que los resultados que nos da tienen sentido.

 

Ajusta el renderizado JavaScript

Si el crawl va demasiado rápido, puede que algunos apartados no lleguen a completarse. En ese caso, conviene aumentar el tiempo de espera y reducir la velocidad.

 

Controla el coste

Cada URL genera una llamada a la API. Si vas a analizar cientos o miles de páginas, es mejor estimar antes el volumen, tokens y presupuesto.

 

Script JavaScript completo para Screaming Frog

Este es el script completo que se integra en JavaScript personalizado dentro de Screaming Frog. Su función es localizar el contenido principal, limpiarlo, enviarlo a OpenAI, calcular métricas derivadas e insertar los resultados en el DOM renderizado.

/*
  GEO Audit Injector for Screaming Frog
  1 API call per URL
  Injects values into rendered DOM as data attributes
  Then extract each metric with Custom Extraction

  BEFORE USE:
  - Replace YOUR_NEW_OPENAI_API_KEY
  - Enable Config > Spider > Rendering > JavaScript
*/
const OPENAI_API_KEY = "YOUR_NEW_OPENAI_API_KEY";
const MODEL = "gpt-5-mini";
const OPENAI_URL = "https://api.openai.com/v1/responses";

const MAX_CHARS = 12000;
const MIN_CONTENT_CHARS = 400;
const FETCH_TIMEOUT_MS = 45000;

function cleanText(text) {
  return (text || "")
    .replace(/\s+/g, " ")
    .replace(/\u00A0/g, " ")
    .trim();
}

function safeSlice(text, maxChars) {
  if (!text || text.length <= maxChars) return text || "";
  return text.slice(0, maxChars);
}

function countWords(text) {
  const t = cleanText(text);
  if (!t) return 0;
  return t.split(/\s+/).filter(Boolean).length;
}

function uniqueTextFromNodes(nodes) {
  const seen = new Set();
  const parts = [];

  nodes.forEach((node) => {
    const text = cleanText(node.innerText || node.textContent || "");
    if (text && !seen.has(text)) {
      seen.add(text);
      parts.push(text);
    }
  });

  return parts.join(" ");
}

function getMetaDescription() {
  const el = document.querySelector('meta[name="description"]');
  return el ? cleanText(el.getAttribute("content")) : "";
}

function getTitleText() {
  return cleanText(document.title || "");
}

function getHeadings(selector, scope = document) {
  return Array.from(scope.querySelectorAll(selector))
    .map((el) => cleanText(el.innerText || el.textContent || ""))
    .filter(Boolean);
}

function getMainContentNode() {
  const selectors = [
    "main article",
    "article",
    "main",
    '[role="main"]',
    ".post-content",
    ".entry-content",
    ".article-content",
    ".content",
    ".main-content",
    "#main-content",
    "#content",
    ".post",
    ".entry"
  ];

  for (const selector of selectors) {
    const node = document.querySelector(selector);
    if (node && cleanText(node.innerText || "").length >= MIN_CONTENT_CHARS) {
      return node;
    }
  }

  const candidates = Array.from(document.querySelectorAll("main, article, section, div"))
    .map((el) => ({
      el,
      len: cleanText(el.innerText || "").length
    }))
    .filter((x) => x.len >= MIN_CONTENT_CHARS)
    .sort((a, b) => b.len - a.len);

  return candidates.length ? candidates[0].el : null;
}

function extractContentData() {
  const mainNode = getMainContentNode();

  if (!mainNode) {
    return { error: "No main content found." };
  }

  const clone = mainNode.cloneNode(true);

  const removeSelectors = [
    "script",
    "style",
    "noscript",
    "form",
    "iframe",
    "nav",
    "aside",
    "footer",
    ".sidebar",
    ".related",
    ".related-posts",
    ".share",
    ".social",
    ".newsletter",
    ".comments",
    ".comment-respond",
    ".ads",
    ".ad",
    ".advertisement",
    ".breadcrumbs",
    "#cookie-law-info-bar",
    ".cookie",
    ".cookies",
    ".cky-consent-container",
    ".cli-bar-container"
  ];

  removeSelectors.forEach((selector) => {
    clone.querySelectorAll(selector).forEach((el) => el.remove());
  });

  const title = getTitleText();
  const metaDescription = getMetaDescription();
  const h1 = getHeadings("h1", clone);
  const h2 = getHeadings("h2", clone);
  const h3 = getHeadings("h3", clone);
  const paragraphs = uniqueTextFromNodes(clone.querySelectorAll("p"));
  const listItems = uniqueTextFromNodes(clone.querySelectorAll("li"));
  const blockquotes = uniqueTextFromNodes(clone.querySelectorAll("blockquote"));

  let bodyText = [
    title ? `TITLE: ${title}` : "",
    metaDescription ? `META_DESCRIPTION: ${metaDescription}` : "",
    h1.length ? `H1: ${h1.join(" | ")}` : "",
    h2.length ? `H2: ${h2.join(" | ")}` : "",
    h3.length ? `H3: ${h3.join(" | ")}` : "",
    paragraphs ? `PARAGRAPHS: ${paragraphs}` : "",
    listItems ? `LIST_ITEMS: ${listItems}` : "",
    blockquotes ? `QUOTES: ${blockquotes}` : ""
  ]
    .filter(Boolean)
    .join("\n\n");

  bodyText = safeSlice(cleanText(bodyText), MAX_CHARS);

  return {
    content: bodyText,
    features: {
      url: window.location.href,
      title,
      metaDescription,
      h1_count: h1.length,
      h2_count: h2.length,
      h3_count: h3.length,
      list_count: clone.querySelectorAll("ul li, ol li").length,
      paragraph_count: clone.querySelectorAll("p").length,
      words: countWords(bodyText),
      chars: bodyText.length
    }
  };
}

function fetchWithTimeout(url, options, timeoutMs) {
  return Promise.race([
    fetch(url, options),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(`Timeout after ${timeoutMs}ms`)), timeoutMs)
    )
  ]);
}

function extractTextOutput(data) {
  if (data.output_text && typeof data.output_text === "string") {
    return data.output_text.trim();
  }

  if (Array.isArray(data.output)) {
    const parts = [];
    data.output.forEach((item) => {
      if (Array.isArray(item.content)) {
        item.content.forEach((c) => {
          if (typeof c.text === "string") parts.push(c.text);
        });
      }
    });
    if (parts.length) return parts.join("\n").trim();
  }

  return "";
}

function clampScore(n) {
  const v = Number(n);
  if (isNaN(v)) return 0;
  if (v < 0) return 0;
  if (v > 100) return 100;
  return Math.round(v);
}

function normalizeString(v) {
  return cleanText((v || "").toString());
}

function buildGeoStatus(score) {
  if (score >= 80) return "High GEO Potential";
  if (score >= 60) return "Medium GEO Potential";
  if (score >= 40) return "Low GEO Potential";
  return "Very Low GEO Potential";
}

function buildLlmRisk(definitionScore, structureScore, clarityScore, citableBlocks) {
  const risks = [];

  if (definitionScore < 50) risks.push("weak_definition");
  if (structureScore < 50) risks.push("poor_structure");
  if (clarityScore < 50) risks.push("low_clarity");
  if (citableBlocks < 2) risks.push("few_citable_blocks");

  if (!risks.length) return "low_risk";
  if (risks.length <= 2) return risks.join("|");
  return "high_risk:" + risks.join("|");
}

function buildGeoPriority(geoScore) {
  if (geoScore >= 80) return "Tier 1 - AI Ready";
  if (geoScore >= 60) return "Tier 2 - Improve";
  if (geoScore >= 40) return "Tier 3 - Weak";
  return "Tier 4 - Rebuild";
}

function upsertGeoNode(data) {
  let node = document.querySelector("#sf-geo-audit");
  if (!node) {
    node = document.createElement("div");
    node.id = "sf-geo-audit";
    node.style.display = "none";
    document.body.appendChild(node);
  }

  Object.entries(data).forEach(([key, value]) => {
    node.setAttribute(`data-${key}`, String(value ?? ""));
  });

  node.textContent = JSON.stringify(data);
}

async function callOpenAI(content, features) {
  const schema = {
    type: "object",
    additionalProperties: false,
    properties: {
      IA_REUSE_SCORE: { type: "number" },
      DEFINITION_SCORE: { type: "number" },
      CLARITY_SCORE: { type: "number" },
      STRUCTURE_SCORE: { type: "number" },
      CITABLE_BLOCKS: { type: "number" },
      MAIN_ISSUES: { type: "string" },
      ACTIONABLE_ADVICE: { type: "string" },
      TOP_PRIORITY_FIX: { type: "string" }
    },
    required: [
      "IA_REUSE_SCORE",
      "DEFINITION_SCORE",
      "CLARITY_SCORE",
      "STRUCTURE_SCORE",
      "CITABLE_BLOCKS",
      "MAIN_ISSUES",
      "ACTIONABLE_ADVICE",
      "TOP_PRIORITY_FIX"
    ]
  };

  const systemPrompt = `
You are an expert GEO and LLM content auditor.

Evaluate how likely the content is to be reused, synthesized or cited by generative systems such as ChatGPT, Gemini, Perplexity or Copilot.

Score strictly using only the provided content.

Criteria:
1. IA_REUSE_SCORE (0-100)
2. DEFINITION_SCORE (0-100)
3. CLARITY_SCORE (0-100)
4. STRUCTURE_SCORE (0-100)
5. CITABLE_BLOCKS (integer)
6. MAIN_ISSUES (brief, specific)
7. ACTIONABLE_ADVICE (brief, actionable)
8. TOP_PRIORITY_FIX (single top action)

Rules:
- No extra text outside JSON.
- Penalize weak or late definitions, poor structure, long repetitive paragraphs, weak information density, promotional language, lack of lists/comparisons.
- Reward early definitions, answer-first structure, reusable blocks, lists, comparisons and direct explanations.
`.trim();

  const userPrompt = `
URL: ${features.url}

EXTRACTED SIGNALS:
- title: ${features.title || ""}
- meta_description: ${features.metaDescription || ""}
- h1_count: ${features.h1_count}
- h2_count: ${features.h2_count}
- h3_count: ${features.h3_count}
- list_count: ${features.list_count}
- paragraph_count: ${features.paragraph_count}
- word_count: ${features.words}
- char_count: ${features.chars}

CONTENT:
${content}
`.trim();

  const payload = {
    model: MODEL,
    input: [
      {
        role: "system",
        content: [{ type: "input_text", text: systemPrompt }]
      },
      {
        role: "user",
        content: [{ type: "input_text", text: userPrompt }]
      }
    ],
    text: {
      format: {
        type: "json_schema",
        name: "geo_audit",
        strict: true,
        schema
      }
    }
  };

  const response = await fetchWithTimeout(
    OPENAI_URL,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "Authorization": `Bearer ${OPENAI_API_KEY}`
      },
      body: JSON.stringify(payload)
    },
    FETCH_TIMEOUT_MS
  );

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`OpenAI API ${response.status}: ${errorText}`);
  }

  const data = await response.json();
  const rawText = extractTextOutput(data);

  if (!rawText) {
    throw new Error("No structured response returned.");
  }

  const parsed = JSON.parse(rawText);

  const iaReuse = clampScore(parsed.IA_REUSE_SCORE);
  const definition = clampScore(parsed.DEFINITION_SCORE);
  const clarity = clampScore(parsed.CLARITY_SCORE);
  const structure = clampScore(parsed.STRUCTURE_SCORE);
  const citableBlocks = Math.max(0, parseInt(parsed.CITABLE_BLOCKS || 0, 10) || 0);

  const mainIssues = normalizeString(parsed.MAIN_ISSUES);
  const advice = normalizeString(parsed.ACTIONABLE_ADVICE);
  const topFix = normalizeString(parsed.TOP_PRIORITY_FIX);

  const geoScore = Math.round(
    iaReuse * 0.40 +
    definition * 0.20 +
    clarity * 0.15 +
    structure * 0.15 +
    citableBlocks * 2
  );

  const geoStatus = buildGeoStatus(iaReuse);
  const geoPriority = buildGeoPriority(geoScore);
  const llmRisk = buildLlmRisk(definition, structure, clarity, citableBlocks);

  return {
    ia_reuse_score: iaReuse,
    definition_score: definition,
    clarity_score: clarity,
    structure_score: structure,
    citable_blocks: citableBlocks,
    geo_score: geoScore,
    geo_priority: geoPriority,
    geo_status: geoStatus,
    llm_risk: llmRisk,
    main_issues: mainIssues,
    actionable_advice: advice,
    top_priority_fix: topFix,
    content_words: features.words,
    h2_count: features.h2_count,
    list_count: features.list_count,
    extracted_chars: features.chars
  };
}

return (async () => {
  try {
    if (!OPENAI_API_KEY || OPENAI_API_KEY === "YOUR_NEW_OPENAI_API_KEY") {
      return seoSpider.error("Add a valid OpenAI API key.");
    }

    const extracted = extractContentData();

    if (extracted.error) {
      upsertGeoNode({
        ia_reuse_score: "",
        definition_score: "",
        clarity_score: "",
        structure_score: "",
        citable_blocks: "",
        geo_score: "",
        geo_priority: "Error",
        geo_status: "Error",
        llm_risk: "Error",
        main_issues: extracted.error,
        actionable_advice: "",
        top_priority_fix: "",
        content_words: 0,
        h2_count: 0,
        list_count: 0,
        extracted_chars: 0
      });
      return seoSpider.data(extracted.error);
    }

    const { content, features } = extracted;

    if (!content || content.length < MIN_CONTENT_CHARS) {
      const msg = "Content too short for reliable GEO audit.";
      upsertGeoNode({
        ia_reuse_score: "",
        definition_score: "",
        clarity_score: "",
        structure_score: "",
        citable_blocks: "",
        geo_score: "",
        geo_priority: "Error",
        geo_status: "Error",
        llm_risk: "Error",
        main_issues: msg,
        actionable_advice: "",
        top_priority_fix: "",
        content_words: features.words || 0,
        h2_count: features.h2_count || 0,
        list_count: features.list_count || 0,
        extracted_chars: features.chars || 0
      });
      return seoSpider.data(msg);
    }

    const audit = await callOpenAI(content, features);
    upsertGeoNode(audit);

    return seoSpider.data(
      `GEO=${audit.geo_score} | PRIORITY=${audit.geo_priority} | IA=${audit.ia_reuse_score} | DEF=${audit.definition_score} | CLA=${audit.clarity_score} | STR=${audit.structure_score} | RISK=${audit.llm_risk}`
    );
  } catch (error) {
    upsertGeoNode({
      ia_reuse_score: "",
      definition_score: "",
      clarity_score: "",
      structure_score: "",
      citable_blocks: "",
      geo_score: "",
      geo_priority: "Error",
      geo_status: "Error",
      llm_risk: "Error",
      main_issues: cleanText(error.message),
      actionable_advice: "",
      top_priority_fix: "",
      content_words: 0,
      h2_count: 0,
      list_count: 0,
      extracted_chars: 0
    });

    return seoSpider.error(`GEO audit error: ${error.message}`);
  }
})();

Explicación del JavaScript paso a paso

 

Definición de constantes

El script comienza definiendo la API key, el modelo, el endpoint, el número máximo de caracteres y el tiempo de espera. Esto permite controlar el coste, la longitud y la estabilidad.

 

Limpieza del texto

La función cleanText() normaliza los espacios y deja el texto en un formato más limpio para el análisis.

 

Recorte del contenido

safeSlice() evita enviar textos demasiado largos, lo que ayuda a controlar el coste.

 

Conteo de palabras

countWords() se utiliza para registrar una señal auxiliar que luego puede ser útil al interpretar resultados.

 

Extracción de contenido único

uniqueTextFromNodes() recorre distintos nodos y evita duplicados exactos.

 

Lectura de title y meta description

El script añade estos elementos porque aportan contexto valioso al modelo.

 

Detección del contenido principal

getMainContentNode() busca el contenido usando varios selectores habituales. Si no encuentra un bloque claro, se queda con el contenedor que tenga más texto útil.

 

Limpieza del bloque principal

Se eliminan scripts, estilos, formularios, navegación, sidebars, comentarios, banners de cookies y otros elementos con poco valor para la auditoría.

 

Construcción del contenido a analizar

Después se unen title, meta description, H1, H2, H3, párrafos, listas y citas en un solo bloque de texto.

 

Llamada a la API

callOpenAI() envía el contenido y las señales estructurales junto con un JSON Schema estricto para forzar una respuesta estructurada.

 

Normalización de métricas

Las puntuaciones se convierten en números consistentes y se limpian los campos textuales.

 

Cálculo de GEO_SCORE

La fórmula utilizada en este ejemplo es:

  • IA_REUSE_SCORE × 0,40
  • DEFINITION_SCORE × 0,20
  • CLARITY_SCORE × 0,15
  • STRUCTURE_SCORE × 0,15
  • CITABLE_BLOCKS × 2

Es un punto de partida razonable, no una fórmula cerrada. Puede ajustarse según el tipo de proyecto.

 

Clasificación y riesgos

El script genera GEO_STATUS, GEO_PRIORITY y LLM_RISK para convertir el análisis en algo operativo.

 

Inyección en el DOM

Por último, inserta todos los resultados en un nodo oculto con id sf-geo-audit. Ese es el punto del que luego tiran los XPath.

 

Configuración exacta de la auditoría GEO en Screaming Frog

 

1. Activar JavaScript rendering

Ve a Config > Spider > Rendering > JavaScript.

 

2. Añadir el script

Ve a Configuration > Custom > JavaScript y pega el script completo.

 

3. Ajustar el tiempo de espera

Si algunas URLs aparecen vacías porque el script no termina a tiempo, aumenta el wait time del renderizado.

 

4. Reducir velocidad de crawl si hace falta

Cuando trabajas con llamadas a API dentro del rastreo no conviene forzar demasiada velocidad.

 

5. Crear las extracciones XPath

Una vez añadido el script, el siguiente paso es crear una extracción por cada métrica.

 

XPath completos para extraer todas las columnas

Auditoría GEO con Screaming Frog

Para todas las extracciones usa:

  • Tipo: XPath
  • Modo: Extraer HTML interna

 

IA_REUSE_SCORE

//*[@id='sf-geo-audit']/@data-ia_reuse_score

 

DEFINITION_SCORE

//*[@id='sf-geo-audit']/@data-definition_score

 

CLARITY_SCORE

//*[@id='sf-geo-audit']/@data-clarity_score

 

STRUCTURE_SCORE

//*[@id='sf-geo-audit']/@data-structure_score

 

CITABLE_BLOCKS

//*[@id='sf-geo-audit']/@data-citable_blocks

 

GEO_SCORE

//*[@id='sf-geo-audit']/@data-geo_score

 

GEO_PRIORITY

//*[@id='sf-geo-audit']/@data-geo_priority

 

GEO_STATUS

//*[@id='sf-geo-audit']/@data-geo_status

 

LLM_RISK

//*[@id='sf-geo-audit']/@data-llm_risk

 

MAIN_ISSUES

//*[@id='sf-geo-audit']/@data-main_issues

 

ACTIONABLE_ADVICE

//*[@id='sf-geo-audit']/@data-actionable_advice

 

TOP_PRIORITY_FIX

//*[@id='sf-geo-audit']/@data-top_priority_fix

 

CONTENT_WORDS

//*[@id='sf-geo-audit']/@data-content_words

 

H2_COUNT

//*[@id='sf-geo-audit']/@data-h2_count

 

LIST_COUNT

//*[@id='sf-geo-audit']/@data-list_count

 

EXTRACTED_CHARS

//*[@id='sf-geo-audit']/@data-extracted_chars

Cómo validar que todo funciona

  1. Prueba primero con un grupo pequeño de URLs.
  2. Verifica que se rellenan al menos IA_REUSE_SCORE, GEO_SCORE, GEO_PRIORITY y LLM_RISK.
  3. Ordena por GEO_SCORE ascendente.
  4. Revisa manualmente una URL alta, una media y una baja para comprobar si el diagnóstico encaja con la realidad del contenido.

Si el sistema funciona bien en una muestra pequeña, ya puedes escalarlo.

 

Cómo interpretar los resultados

 

Cuando GEO_SCORE es alto

Suele indicar una definición clara, buena organización semántica, bloques reutilizables y contenido fácil de sintetizar.

 

Cuando GEO_SCORE es medio

Lo habitual es que la URL sea útil, pero tenga puntos de mejora, como que el concepto se explica demasiado tarde, exceso de narrativa o falta de listas y bloques comparativos.

 

Cuando GEO_SCORE es bajo

Suele coincidir con páginas poco claras, excesivamente promocionales, mal estructuradas o con escasa densidad informativa.

 

Cómo usar esto en una auditoría real

 

Detectar páginas prioritarias

Ordena por GEO_SCORE ascendente.

 

Detectar falta de definición

Filtra por URLs con DEFINITION_SCORE por debajo de 50.

 

Detectar problemas estructurales

Filtra por LLM_RISK cuando incluya poor_structure.

 

Detectar contenido poco reutilizable

Filtra por CITABLE_BLOCKS inferiores a 2.

 

Montar un backlog editorial

Usa MAIN_ISSUES, ACTIONABLE_ADVICE y TOP_PRIORITY_FIX para convertir el crawl en una lista priorizada de acciones.

 

Casos en los que este sistema aporta más valor

 

Blogs con contenido informativo

Permite detectar qué piezas explican bien un tema y cuáles no.

 

Sitios B2B

Muchas páginas de servicios están escritas para vender, pero no para ser reutilizadas por IA. Esta auditoría ayuda a detectar ese desequilibrio.

 

Proyectos con estrategia GEO

Es especialmente útil cuando quieres priorizar qué URLs merecen optimización específica para buscadores generativos.

 

Auditorías de contenido heredado

Sirve para revisar de forma rápida URLs antiguas cuyo valor explicativo es bajo o irregular.

 

Limitaciones del sistema

 

No mide citación real

Este sistema estima probabilidad de reutilización. No confirma que una URL vaya a ser citada en un entorno real.

 

Depende de la extracción del contenido principal

Si la página tiene una estructura poco convencional y el contenido no se localiza bien, la evaluación pierde precisión.

 

Los pesos del GEO_SCORE son ajustables

La fórmula no es universal. Puede afinarse según vertical, intención del contenido y objetivos del proyecto.

 

Tiene coste por URL

Cada página genera una llamada a la API, así que conviene tener esto en cuenta al escalar.

 

No sustituye una auditoría SEO

La capa GEO suma valor, pero no reemplaza análisis de indexación, enlazado, intención, autoridad o arquitectura.

 

Recomendaciones prácticas para usarlo bien

  • Empieza por un muestreo reducido.
  • Ajusta los pesos de GEO_SCORE cuando tengas datos propios.
  • Segmenta por tipo de URL: blog, servicios, landings, recursos o categorías.
  • No uses esta auditoría como sustituto del SEO clásico, sino como capa adicional.
  • Valida manualmente una muestra antes de escalar a todo el sitio.

 

Qué mejoras puedes añadir después

Una vez validado el sistema, puedes ampliarlo con nuevas métricas como AI_SNIPPET_SCORE, ENTITY_CLARITY_SCORE o ANSWERABILITY_SCORE.

También puedes crear una segunda capa que no solo audite, sino que proponga una reescritura parcial del primer bloque, nuevas estructuras de H2 o recomendaciones de bloques citables. Ahí es donde una auditoría GEO empieza a convertirse en una herramienta de optimización editorial real.

 

Ten esto en cuenta

La visibilidad ya no se explica solo desde el ranking. Cada vez tiene más sentido medir si una URL puede servir como materia prima para una respuesta generativa. Eso obliga a incorporar nuevas preguntas en la auditoría de contenidos:

  • ¿Define pronto el tema?
  • ¿Es fácil de sintetizar?
  • ¿Tiene bloques reutilizables?
  • ¿Puede una IA usar esta URL para responder?

Montar una auditoría GEO de URLs con Screaming Frog permite empezar a responder esas preguntas de forma operativa, escalable y accionable.

Y ese cambio, bien trabajado, puede convertirse en una ventaja real para cualquier proyecto que quiera entender cómo ganar presencia en entornos generativos más allá del SEO tradicional.