Publicado en nicolasbillia.com — Mayo 2026

Disclaimer
Este contenido fue generado con ayuda de IA a partir de la ingesta de contexto: media docena de proyectos trabajados en Claude Code usando la API de Antonio Maculus (link en el segundo párrafo) + registro de logs de la sesión de iteración con el MCP. Todo el desglose técnico sale de los logs que me devolvió Claude Code; las ideas de análisis tienen una base propia + brainstorming en el momento de armar el post.
Hoy se anunció el MCP oficial de Screaming Frog. Lo conecté a Claude Code apenas salió y armé un smoke test sobre nicolasbillia.com (40 URLs) para medir, endpoint por endpoint, qué hace bien y cuánto “pesa” el output cuando viaja al contexto del LLM.
En paralelo, la librería Python de Antonio Atilio Maculus (repo en GitHub) la venimos usando desde principios de año en media docena de auditorías reales — e-commerce, retail premium, edu, moda, medios. Eso es la base de comparación: el MCP recién salido contra una herramienta probada en producción.
La pregunta natural es “cuál es mejor”, pero después de correr ambas en paralelo la respuesta es otra: no son lo mismo, no compiten, y combinándolas se desbloquean flujos que con cualquiera sola no salen.
Este post es una prueba de usuario, no autoridad técnica. Antonio sabe diez veces más de su librería que yo, y del MCP oficial recién llevo unas horas. Lo que aporto es la mirada del SEO que las usa para auditar sitios reales: la metodología del experimento de hoy, catálogo de capacidades con números absolutos, scoring por categoría de análisis, 20 ideas de uso combinado, setup paso a paso, queries SQL listas, y cómo cruzar el crawl con GSC y GA4.
Contenido del post
- 0. Cómo medí — metodología del smoke test
- 1. Catálogo de capacidades — qué saca cada uno
- 2. Scoring 1-3 por categoría de análisis (semáforo)
- 3. Decision tree — qué herramienta uso
- 4. Setup paso a paso
- 5. 20 ideas de uso combinado MCP + API
- 6. Workflow combinado clásico (3 pasos)
- 7. Pipeline 3-en-1: SF + GSC + GA4 con código real
- 8. Glosario de tablas Derby APP.* (~25)
- 9. Recetario — 10 queries SQL listas para copiar
- 10. Tokens consumidos en Claude Code por endpoint MCP
- 11. Gotchas reales que me trabaron
- 12. FAQ
- Cierre
0. Cómo medí — metodología del smoke test
Quería un sitio chico para no quemar crédito ni tiempo, y conocido para validar los resultados. Elegí nicolasbillia.com (mi sitio personal, ~40 URLs HTML, WordPress block theme, hreflang ES/EN).
Setup del experimento:
- Screaming Frog con MCP server activado en
http://localhost:11435/mcp. - Claude Code conectado al MCP vía
claude mcp add seospider --transport http http://localhost:11435/mcp. - API Python de Antonio cargando el mismo crawl con
Crawl.load(crawl_id, db_id_backend="derby", csv_fallback=False).
Endpoints MCP medidos (6 calls representativas): sf_crawl, sf_crawl_progress, sf_generate_report (Redirects:All), sf_export_seo_element_urls (Canonicals:Missing), sf_bulk_export_page_content (visible_text), sf_export_embeddings.
Qué medí en cada llamada:
- Tamaño del archivo en disco (KB) — output completo guardado a archivo.
- Tamaño del output que vuelve al contexto LLM (chars / KB) — lo que Claude efectivamente “ve” después de la tool call.
- Tokens estimados consumidos en el contexto (chars / 4, criterio conservador).
- Errores y dependencias: si el endpoint falla, qué config previa exige (PageSpeed API key, hreflang crawl flag, Database storage, etc.).
Lo que el smoke test NO mide:
- No es un benchmark formal — N=1 sitio, 40 URLs, una sola corrida.
- Los tokens NO escalan linealmente: cuando hay
file_path, el sample devuelto es ~1 URL y no crece con el sitio. Sinfile_path, sí escala con el sitio entero. - No medí latencia del crawl en sí (SF tarda igual con o sin MCP). Lo que medí es el costo en contexto LLM y la “ergonomía” del endpoint.
De dónde sale la conclusión “uno escala a 100K+ URLs y el otro no”
Aclaración importante: no probé el MCP sobre un sitio de 100K URLs todavía. El MCP lleva pocos días desde su lanzamiento — no hubo tiempo material de correrlo en una auditoría real de ese tamaño, y el smoke test de hoy fue justamente sobre un sitio chico para mapear la mecánica básica de cada endpoint. La librería de Antonio Maculus, en cambio, sí venía con contexto de uso previo: la corrimos en auditorías reales de decenas de miles de URLs durante los últimos meses. La conclusión sobre escalabilidad es, entonces, inferencia desde la arquitectura de cada solución, validada parcialmente con esos casos reales en la librería de Antonio y no validada todavía con el MCP en sitios grandes.
Por qué la librería de Antonio escala mejor en volumen (razonamiento arquitectónico):
- Lee directo de Apache Derby, la base de datos interna de SF. No hay parseo intermedio de NDJSON ni CSV.
- Las queries usan SQL nativo: planner, índices, JOINs eficientes. La complejidad del query no infla el output que viaja al LLM — solo viaja el resultado del SELECT, no el dataset completo.
- Tablas pre-computadas como
APP.INLINK_COUNTSevitan agregar sitewide en runtime. - Lazy evaluation + pandas: el resultado se materializa solo cuando lo pedís.
- Validación parcial en producción: corrimos auditorías sobre sitios de decenas de miles de URLs sin que la librería se rompa, aplicando los workarounds documentados (Derby forzado,
APP.INLINK_COUNTSen vez delinks("in").collect()).
Por qué el MCP oficial tiene un techo más bajo en volumen (razonamiento arquitectónico + dato del smoke test):
- Los outputs viajan como NDJSON o CSV. No hay SQL ni indexación: es un stream de filas.
- Si pasás
file_path, el archivo se guarda a disco y al LLM le vuelve un sample chico (esto lo medimos hoy). Si NO pasásfile_path, el archivo entero viaja al contexto. - En el smoke test sobre 40 URLs,
sf_export_embeddingsgeneró 539 KB. Extrapolación lineal a 100K URLs: del orden de 1,3 GB. El archivo se genera, pero el LLM no puede leerlo directo: hay que parsearlo afuera con un script. - El MCP no resuelve cross-table queries (no hay SQL). Para cruzar dos bulk exports en un sitio grande, terminás escribiendo Python igual.
Conclusión honesta: el techo del MCP en volumen es el context window del LLM, no SF. SF crawlea millones de URLs sin problema; el MCP hace de puente, y ese puente tiene ancho de banda limitado. La API de Antonio salta el puente y va directo al fondo de la pileta (la DB Derby), por eso aguanta mejor.
1. Catálogo de capacidades — qué saca cada uno
1.1 MCP oficial de Screaming Frog
- 61 reports nativos en 13 grupos:
- Crawl Overview / Issues / Segments (3)
- Redirects (4): All, Chains, Redirect & Canonical Chains, Redirects to Error
- Canonicals (2): Chains, Non-Indexable
- Pagination (2): Non-200, Unlinked
- Hreflang (7): All, Non-200, Unlinked, Missing Return, Inconsistent Language, Non-Canonical Return, Noindex Return
- Insecure Content / SERP Summary / Orphan Pages (3)
- Structured Data (5): Validation Errors Summary, Validation Errors, Parse Errors, Rich Results Summary, Rich Results
- JavaScript Console Log Summary (1)
- PageSpeed (28): Opportunities Summary, CSS/JS Coverage, Minify, Reduce Unused, Render Blocking, LCP, CLS, Fonts, DOM Size, etc.
- Mobile (4): Viewport, Target Size, Content Sized, Illegible Font
- Accessibility Violations Summary (1)
- Cookies Summary (1)
- 130+ bulk exports en 16 grupos: Queued, Links (12), Web Headers / Cookies (3), Path Type (4), Security (6), Response Codes (31), Content (6), Images (8), Canonicals (12), Directives (17), JavaScript (3), AMP (7), Structured Data (6), Sitemaps (4), Custom Search / Extraction (5), URL Inspection (3), Accessibility (12).
- 5 utilidades extra: screenshots de URL, embeddings export (vectores), bulk page content (raw HTML o visible text), Node.js runner para post-procesos custom, browser opener.
Lo que no pude usar como usuario (limitaciones que detecté):
- PageSpeed reports — requieren tener PageSpeed Insights API key configurada en SF antes del crawl. Sin esa key, los 28 reports vuelven vacíos.
- Accessibility WCAG — requiere activar el módulo Axe en SF Config > Spider > Crawl > Accessibility antes del crawl.
- URL Inspection (Rich Results, Referring Pages, Sitemaps) — requiere conexión a GSC vía OAuth dentro de SF.
- Custom Search / Custom Extraction — dependen de patrones (XPath, regex) definidos en SF antes del crawl.
- Change Detection — requiere 2 crawls del mismo sitio comparables.
- Hreflang reports — el sitio crawleado debe haberse crawleado con “Crawl Hreflang” activado (mi caso del benchmark estaba apagado).
1.2 API Python de Antonio Maculus
- 159 filtros GUI distribuidos en 12 módulos de SEO element:
- response_codes (32 filtros)
- directives (18)
- hreflang (15)
- canonicals (12)
- headings H1/H2 (12)
- internal content-types (12)
- structured_data (12)
- pagination (11)
- page_titles (10)
- meta_description (9)
- images (8)
- meta_keywords (4)
- 9 reports de auditoría pre-armados:
broken_links_report,broken_inlinks_report,nofollow_inlinks_report,title_meta_audit,indexability_audit,orphan_pages_report,security_issues_report,canonical_issues_report,hreflang_issues_report. - ~25 tablas Derby APP.* accesibles por SQL directo (ver glosario en sección 8).
- 6 backends: Derby (fast, requiere Java), DuckDB (cache analítico), CSV (sin deps externas), SQLite, CLI, Hybrid (Derby + CSV fallback).
- Outputs: pandas / polars / dicts / iteradores lazy / método
.to_sql()para inspeccionar la query generada. - Cobertura: 601/628 tabs mapeados (95,7%), 15.490/15.589 campos (99,4%).
Lo que no pude usar o se cuelga:
crawl.summary()se cuelga en crawls grandes (100K+ URLs). Workaround: SQL directo sobre tablas APP.*canonical_issues_report()eindexability_audit()con backend default. Forzardb_id_backend="derby"+csv_fallback=False.links("in").collect()sitewide en sitios grandes. UsarAPP.INLINK_COUNTSpre-computado.- Filtros documentados como TODO en el código fuente: pixel-width titles/metas, “Is Relative” canonicals, “Incorrect Language Codes” hreflang, “Background Images”, varios Pagination y Structured Data sub-filtros.
2. Scoring 1-3 por categoría de análisis (semáforo)
Escala: 3 super útil — 2 sirve con caveats — 1 no recomendado.
Criterio por columna y origen de cada score:
- Velocidad: tiempo desde la query hasta tener el dato listo para analizar (de la llamada a pandas o al archivo). Score basado en el smoke test (MCP) + auditorías reales acumuladas (API).
- Facilidad: curva de aprendizaje y fricción típica de cada endpoint. Score basado en el smoke test (MCP) + experiencia de uso en producción (API).
- Volumen: capacidad inferida de procesar 100K+ URLs sin romper. Este score es inferencia arquitectónica (ver subsección anterior sobre infraestructura), validado parcialmente con la API en auditorías reales de decenas de miles de URLs, pero no probado todavía con el MCP en sitios de ese tamaño.
Importante: lo que sigue es una matriz de inferencias razonadas, no conclusiones cerradas. Cada celda la vamos a ir validando en sitios reales y este post se va a actualizar a medida que tengamos data dura. Si tenés un caso de uso o un sitio donde una celda no se sostiene, escribime y lo incorporo.
| Categoría de análisis | MCP Vel | MCP Fac | MCP Vol | API Vel | API Fac | API Vol |
|---|---|---|---|---|---|---|
| Response codes | ||||||
| Redirects (chains, loops) | ||||||
| Canonicals | ||||||
| Hreflang | ||||||
| Directivas robots | ||||||
| Page titles / Meta description | ||||||
| Headings H1/H2 | ||||||
| Inlinks / Outlinks (granular) | ||||||
| Content duplicates (exact / near / similar) | ||||||
| Imágenes (alt, peso, dimensiones) | ||||||
| Structured data / Rich Results | ||||||
| PageSpeed / Core Web Vitals | ||||||
| Mobile usability | ||||||
| Accessibility (WCAG) | ||||||
| Screenshots / Embeddings / Node.js |
Lectura rápida: para inlinks granulares y queries cross-tabla, API gana. Para PageSpeed, accessibility, screenshots y embeddings, el MCP es exclusivo o claramente mejor. Para el grueso de análisis SEO estándar (canonicals, hreflang, directivas, titles), ambas funcionan bien — la elección depende más del workflow que de la herramienta.
3. Decision tree — qué herramienta uso
¿Querés lanzar el crawl desde el LLM?
├── Sí ────────────────────────────→ MCP (la API solo lee)
└── No (crawl ya existe)
│
└── ¿Qué tipo de análisis?
│
├── PageSpeed / WCAG / Screenshots / Embeddings → MCP exclusivo
│
├── Cross-tabla (canonical × hreflang × inlinks) → API (SQL directo)
│
├── Volumen alto (1M+ inlinks / 100K+ URLs)
│ ├── Solo extracción → API (pandas, sin parsear NDJSON)
│ └── Reports nativos formato cliente → MCP con file_path
│
├── Pipeline reutilizable (varios clientes)
│ └── API (pandas + SQL más limpios de mantener)
│
└── Reports SF de catálogo
├── Para entregable cliente → MCP (formato CSV nativo)
└── Para análisis intermedio → cualquiera
4. Setup paso a paso
4.1 Claude Desktop
Referencia oficial: Screaming Frog SEO Spider — Configuration / MCP Server. Al momento de escritura, la sección pública específica del MCP server todavía no estaba detallada en esa URL — el setup que documentamos abajo es el que funcionó en nuestra instalación.
- Abrir SF y activar el MCP server desde
Configuration > API Access > MCP Server(la opción aparece en versiones recientes). - Confirmar que el servidor está escuchando en
http://localhost:11435/mcp(puerto default). - Editar
~/Library/Application Support/Claude/claude_desktop_config.jsony agregar:
{
"mcpServers": {
"seospider": {
"type": "http",
"url": "http://localhost:11435/mcp"
}
}
}
- Restart Claude Desktop (cerrar y abrir).
- En el chat, escribí “list available tools” — deberían aparecer los
sf_*.
4.2 Claude Code (el flujo que usamos)
SF tiene que estar abierto y con el MCP server activado (mismos pasos 1 y 2 de arriba).
# Agregar el server al config de Claude Code:
claude mcp add seospider --transport http http://localhost:11435/mcp
# Verificar que quedó registrado:
claude mcp list
Alternativa equivalente — editar manualmente ~/.claude.json:
{
"mcpServers": {
"seospider": {
"type": "http",
"url": "http://localhost:11435/mcp"
}
}
}
Una vez registrado, las herramientas aparecen como mcp__seospider__sf_* dentro de Claude Code. Un crawl básico:
sf_crawl(crawl_url="https://example.com", crawl_name="Auditoría inicial")
sf_crawl_progress()
sf_generate_report(
category="Hreflang:Missing Return Links",
export_type="CSV",
file_path="hreflang.csv"
)
Atajo lúdico pero real: si esto te parece denso, copiá la URL de este post y pasásela a Claude Code con /url <link>, después pedile “implementame el setup del MCP de Screaming Frog según las instrucciones del post”. Es legítimo. Es exactamente el caso de uso para el que se diseñó MCP.
5. 20 ideas de uso combinado MCP + API
| # | Idea | Combinación | Output |
|---|---|---|---|
| 1 | Auditoría técnica integral one-shot | MCP lanza crawl + API genera DataFrame con issues priorizados | CSV ordenado por impacto + crawl_id reutilizable |
| 2 | Canibalización semántica con valor comercial | MCP exporta embeddings + API cruza con GA4 conversiones | Lista de URLs a redirigir/mergear con justificación revenue |
| 3 | Diagnóstico hreflang post-deploy | MCP lanza crawl + API SQL sobre MULTIMAP_HREF_LANG_* | Tabla de fixes específicos por variante regional |
| 4 | Orphan pages con tráfico | API extrae orphans + MCP descarga screenshots | Lista visual + recomendación de internal linking |
| 5 | Audit canonicals cross-path post-migración | API SQL custom + MCP confirma con bulk export | Lista validada de redirects 301 a implementar |
| 6 | Detección cloaking por User-Agent | MCP script Node.js (curl + UA spoof) + API compara HTML hash | Reporte de URLs con respuesta divergente browser vs Googlebot |
| 7 | Auditoría WCAG agrupada por template | MCP genera Accessibility:All Violations + API agrupa por path | Lista priorizada por template, no por URL |
| 8 | Zombie pages con impressions GSC | MCP exporta Directives:Noindex Inlinks + API cruza con GSC | URLs noindex que aún reciben tráfico, candidatas a remoción real |
| 9 | Internal linking gaps en clusters semánticos | MCP embeddings + API APP.INLINK_COUNTS + cosine similarity | Pares de URLs altamente similares sin enlace mutuo |
| 10 | Structured data errors por template | MCP Structured Data:Validation Errors + API agrupa por URL pattern | Errores comunes al template, no a la URL individual |
| 11 | Chains de redirects internos | MCP bulk Response Codes:3xx Inlinks + API SQL traversal | Mapa de chains internos con número de hops |
| 12 | Imágenes sin alt priorizadas por tráfico | MCP Images:Missing Alt + API cruza con APP.INLINK_COUNTS y GA4 | Lista priorizada de imágenes a corregir según impacto |
| 13 | PageSpeed comparativo template vs template | MCP PageSpeed reports + API agrupa por path pattern | Templates más lentos identificados, no URLs sueltas |
| 14 | JS rendering issues clasificados | MCP JavaScript Console Log + API filtra por severity | Lista de páginas con JS roto agrupada por tipo de error |
| 15 | Duplicados near vs exact por sección | API SQL sobre APP.NEAR_DUPLICATE / DUPLICATES_TITLE | Decisión consolidación con criterio de proximidad |
| 16 | Canonicals contradictorios con hreflang | API SQL multi-tabla (URLS + MULTIMAP_HREF_LANG_*) | Conflictos en política de consolidación |
| 17 | Rich Results elegibilidad | MCP Rich Results Features + API estima impacto SERP | URLs elegibles para snippet enriquecido priorizadas |
| 18 | Diff pre/post deploy | API CrawlDiff entre 2 crawls + MCP genera reports diff | Validación QA post-deploy automatizada |
| 19 | Mapeo cookies por path (GDPR) | MCP All Cookies bulk + API agrupa por path | Audit GDPR / consent con coverage |
| 20 | Mixed content por sección | MCP Security:Mixed Content + API agrupa por path | Priorización por crawl depth y tráfico |
6. Workflow combinado clásico (3 pasos)
- MCP lanza el crawl:
sf_crawl(crawl_url=...)+sf_crawl_progress()hasta 100%. - API lee el resultado con SQL:
Crawl.load(crawl_id, db_id_backend="derby").sql(...). Cruces multi-tabla, agregaciones complejas. - MCP exporta los reports finales al entregable cliente con
sf_generate_report(category=..., file_path=...).
El LLM en Claude orquesta los tres pasos en una sola conversación.
7. Pipeline 3-en-1: SF + GSC + GA4 con código real
Un crawl solo cuenta la mitad de la historia. La otra mitad la tienen GSC (qué Google ve) y GA4 (qué hace el usuario). Una vez que el dato del crawl está en pandas, cruzarlo con GSC y GA4 es un join SQL.
import pandas as pd
from screamingfrog import Crawl
from search_console_connect import authenticate as gsc_auth, get_client as gsc_client
from extract_ga4 import get_client as ga4_client, extract_landing_pages
# 1) Crawl SF -> DataFrame de URLs
crawl = Crawl.load("CRAWL_ID", db_id_backend="derby", csv_fallback=False)
df_sf = crawl.sql("""
SELECT u.URL_PATH AS url,
u.STATUS_CODE,
u.INDEXABILITY,
u.CANONICAL_LINK_ELEMENT,
ic.INLINKS_COUNT
FROM APP.URLS u
LEFT JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
WHERE u.CONTENT_TYPE = 'text/html'
""").to_pandas()
# 2) GSC 90 dias -> clicks / impressions / position por URL
gsc = gsc_client(gsc_auth())
df_gsc = (
pd.DataFrame(gsc.searchanalytics().query(
siteUrl="sc-domain:ejemplo.com",
body={"startDate":"2026-02-19","endDate":"2026-05-19","dimensions":["page"]}
).execute()["rows"])
.rename(columns={"keys":"url"})
)
df_gsc["url"] = df_gsc["url"].str[0]
# 3) GA4 30 dias -> sessions / conversions por landing
df_ga4 = extract_landing_pages(ga4_client(), property_id="123456789",
start_date="30daysAgo", end_date="today")
# 4) Merge sobre URL normalizada
df = (df_sf.merge(df_gsc, on="url", how="left")
.merge(df_ga4, on="url", how="left"))
print(df.head())
Output: una tabla con url | status_code | indexability | canonical | inlinks_count | gsc_clicks_90d | gsc_impressions_90d | ga4_sessions_30d | ga4_conversions_30d. Sobre eso se filtra cualquier caso del top 20 (orphans con tráfico, canibalización con conversiones, zombies con impressions, etc.).
8. Glosario de tablas Derby APP.* (~25)
| Tabla | Qué contiene |
|---|---|
| APP.URLS | URLs crawleadas con metadata completa (status, indexability, canonical, hreflang flags, content type) |
| APP.LINKS | Grafo de links raw (SRC_ID, DST_ID, LINK_TYPE). LINK_TYPE=13 = hreflang HTML |
| APP.UNIQUE_URLS | Mapping URL_ID → URL string normalizada |
| APP.INLINK_COUNTS | Conteos pre-computados de inlinks por URL (rápido, no requiere agregación) |
| APP.DUPLICATES_TITLE | URLs con título duplicado entre sí |
| APP.DUPLICATES_META_DESCRIPTION | URLs con meta description duplicada |
| APP.DUPLICATES_H1 | URLs con H1 duplicado |
| APP.DUPLICATES_H2 | URLs con H2 duplicado |
| APP.MULTIMAP_CANONICALS_PENDING_LINK | Canonicals sin link confirmado en HTML |
| APP.MULTIMAP_HREF_LANG_NON_200_LINK | Hreflang apuntando a URLs non-200 |
| APP.MULTIMAP_HREF_LANG_MISSING_CONFIRMATION | Hreflang sin return links recíprocos |
| APP.MULTIMAP_HREF_LANG_INCONSISTENT_LANGUAGE_CONFIRMATION | Return links con código de idioma inconsistente |
| APP.MULTIMAP_HREF_LANG_CANONICAL_CONFIRMATION | Hreflang sin canonical en el return |
| APP.MULTIMAP_HREF_LANG_NO_INDEX_CONFIRMATION | Hreflang apuntando a URLs noindex |
| APP.MULTIMAP_PAGINATION_PENDING_LINK | Pagination sin link en anchor |
| APP.MULTIMAP_PAGINATION_SEQUENCE_ERROR | Errores de secuencia rel=prev/next |
| APP.MISSING_ALT_TEXT_TRACKER | Imágenes sin alt text |
| APP.MISSING_ALT_ATTRIBUTE_TRACKER | Imágenes sin atributo alt |
| APP.ALT_TEXT_OVER_X_CHARACTERS_TRACKER | Alt text excedido en longitud |
| APP.MISSING_SIZE_ATTRIBUTES | Imágenes sin width/height (CLS) |
| APP.HTML_VALIDATION_DATA | Validación HTML, ubicación de tags (in/outside head) |
| APP.URL_INSPECTION | Datos de GSC URL Inspection (si la integración está activada) |
| APP.PAGE_SPEED_API | PageSpeed Insights scores/metrics (si la API key está configurada) |
| APP.AXE_CORE_RESULTS | Resultados auditoría accesibilidad Axe (si activado) |
| APP.COSINE_SIMILARITY | Similaridad de contenido entre URLs (requiere activar Content similarity) |
| APP.NEAR_DUPLICATE | Near duplicates con threshold configurable |
| APP.LOW_RELEVANCE | URLs con contenido de baja relevancia / valor bajo |
9. Recetario — 10 queries SQL listas para copiar
Todas asumen crawl = Crawl.load(crawl_id, db_id_backend="derby", csv_fallback=False) previo.
1. Orphan pages indexables
crawl.sql("""
SELECT u.URL_PATH FROM APP.URLS u
LEFT JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
WHERE u.INDEXABILITY = 'Indexable'
AND (ic.INLINKS_COUNT IS NULL OR ic.INLINKS_COUNT = 0)
""")
2. Páginas con near-duplicates > 0.9
crawl.sql("""
SELECT u1.URL_PATH AS url_a, u2.URL_PATH AS url_b, nd.SIMILARITY
FROM APP.NEAR_DUPLICATE nd
JOIN APP.UNIQUE_URLS u1 ON u1.URL_ID = nd.URL_ID_A
JOIN APP.UNIQUE_URLS u2 ON u2.URL_ID = nd.URL_ID_B
WHERE nd.SIMILARITY > 0.9
ORDER BY nd.SIMILARITY DESC
""")
3. Hreflang missing return links
crawl.sql("SELECT * FROM APP.MULTIMAP_HREF_LANG_MISSING_CONFIRMATION")
4. Distribución de inlinks (P50/P90/P99)
crawl.sql("""
SELECT
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY INLINKS_COUNT) AS p50,
PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY INLINKS_COUNT) AS p90,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY INLINKS_COUNT) AS p99
FROM APP.INLINK_COUNTS
""")
5. Canonical chains de 2+ hops
crawl.sql("""
SELECT u.URL_PATH, u.CANONICAL_LINK_ELEMENT, c.URL_PATH AS canonical_path
FROM APP.URLS u
JOIN APP.URLS c ON c.URL_PATH = u.CANONICAL_LINK_ELEMENT
WHERE c.CANONICAL_LINK_ELEMENT IS NOT NULL
AND c.CANONICAL_LINK_ELEMENT <> c.URL_PATH
""")
6. URLs noindex con inlinks (zombi en construcción)
crawl.sql("""
SELECT u.URL_PATH, ic.INLINKS_COUNT
FROM APP.URLS u
JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
WHERE u.META_ROBOTS LIKE '%noindex%' AND ic.INLINKS_COUNT > 0
ORDER BY ic.INLINKS_COUNT DESC
""")
7. Títulos duplicados con word_count bajo
crawl.sql("""
SELECT u.URL_PATH, u.WORD_COUNT, u.TITLE
FROM APP.URLS u
JOIN APP.DUPLICATES_TITLE dt ON dt.URL_ID = u.URL_ID
WHERE u.WORD_COUNT < 300
ORDER BY u.WORD_COUNT
""")
8. Top 100 URLs por inlinks
crawl.sql("""
SELECT u.URL_PATH, ic.INLINKS_COUNT
FROM APP.URLS u
JOIN APP.INLINK_COUNTS ic ON ic.URL_ID = u.URL_ID
ORDER BY ic.INLINKS_COUNT DESC
FETCH FIRST 100 ROWS ONLY
""")
9. Internal redirects 3xx con anchor text descriptivo
crawl.sql("""
SELECT src.URL_PATH AS from_url, dst.URL_PATH AS to_url, l.ANCHOR_TEXT
FROM APP.LINKS l
JOIN APP.URLS src ON src.URL_ID = l.SRC_ID
JOIN APP.URLS dst ON dst.URL_ID = l.DST_ID
WHERE dst.STATUS_CODE BETWEEN 300 AND 399
AND LENGTH(l.ANCHOR_TEXT) > 10
""")
10. URLs por tipo de Schema
crawl.sql("""
SELECT SCHEMA_TYPE, COUNT(*) AS urls
FROM APP.URLS
WHERE SCHEMA_TYPE IS NOT NULL
GROUP BY SCHEMA_TYPE
ORDER BY urls DESC
""")
10. Tokens consumidos en Claude Code por endpoint MCP
Benchmark real sobre nicolasbillia.com (sitio chico, 40 URLs crawleadas). Todos los endpoints corridos con file_path seteado (modo "guardar en disco"). Los números no escalan linealmente con sitios más grandes — el sample que devuelven es siempre 1 URL aprox.
| Endpoint | Archivo en disco | Output al contexto LLM | Tokens aprox |
|---|---|---|---|
sf_crawl |
— | ~37 chars | ~10 |
sf_crawl_progress |
— | ~60 chars | ~15 |
sf_generate_report (Redirects:All) |
3,3 KB / 4 filas | ~1,2 KB (header + path) | ~310 |
sf_export_seo_element_urls (Canonicals:Missing) |
1,4 KB / 4 URLs | ~600 chars (sample 1 URL) | ~150 |
sf_bulk_export_page_content (visible_text) |
128 KB / 18 URLs | ~5,7 KB (sample 1 URL completa) | ~1.425 |
sf_export_embeddings |
539 KB / 40 URLs × 1.536 dims | ~200 chars (status + path) | ~50 |
Aviso operativo crítico: si NO pasás file_path a los bulk exports, el archivo entero viaja al contexto del LLM:
sf_bulk_export_page_contentsin file_path en este crawl: ~128 KB ≈ 32.000 tokenssf_export_embeddingssin file_path: ~539 KB ≈ 135.000 tokens (rompe la context window de Sonnet)
Regla práctica: para exports de más de ~5 KB, siempre file_path. Después leés el archivo con un script o un grep dirigido.
11. Gotchas reales que me trabaron
API Python de Antonio:
Crawl.load(...)sindb_id_backend="derby"activa el cache DuckDB y se cuelga en crawls grandes. Forzar siempre Derby +csv_fallback=False.- De los 9 reports pre-armados,
crawl.summary(),canonical_issues_report()eindexability_audit()se cuelgan en algunos crawls. Workaround: SQL directo sobreAPP.URLS. links("in").collect()sitewide en sitios 100K+ URLs se cuelga. UsarAPP.INLINK_COUNTSque es pre-computado.- Filtros con TODO en el código fuente (Antonio lo documenta): pixel-width titles/metas, "Is Relative" canonicals, "Background Images", varios.
- Requiere Java 21 (bundled en SF). En macOS:
export JAVA_HOME="/Applications/Screaming Frog SEO Spider.app/Contents/jre".
MCP oficial:
- Base directory restringido. En mi instalación es
/Users/. Para escribir output en otra carpeta hay que copiar después./seo_spider_mcp_server/ - 5 reports de PageSpeed devuelven vacío si no hay API key de PageSpeed Insights configurada en SF.
- Hreflang reports devuelven error si el crawl no se hizo con "Crawl Hreflang" activado en SF Config > Spider.
- El crawl debe haberse hecho con Storage = Database (no Memory). Sino algunos reports no tienen los datos.
- SF debe estar abierto siempre. Si SF se cierra a mitad del crawl, se pierde.
12. FAQ
¿Puedo usar la API de Antonio sin tener SF abierto?
Para crawls activos (en ~/.ScreamingFrogSEOSpider/ProjectInstanceData/), SF debe estar abierto porque Derby tiene lock exclusivo. Para archivos .seospider o .dbseospider exportados, no hace falta SF abierto.
¿Sirve en Linux / Windows?
API: sí (Python + Java cross-platform). MCP: sí, SF corre en Win/Mac/Linux, el endpoint HTTP es el mismo.
¿Cuánto pesa un crawl en disco?
En este benchmark, 40 URLs ocuparon ~6 MB en formato Derby. En sitios grandes (100K URLs) escala a 2-4 GB típicamente.
¿Antonio acepta PRs en el repo?
El repo es alpha pública y activo. Mejor canal: abrir issue primero, después PR. Link al repo.
¿El MCP funciona sin licencia paga de SF?
SF Free permite crawls de hasta 500 URLs. El MCP debería funcionar dentro de ese límite, pero los reports requieren modo Database (no Memory), que es licencia paga.
¿Puedo automatizar todo en un cron?
Sí, vía la CLI de SF (sin GUI). El MCP requiere SF abierto; para cron headless conviene la CLI de SF + la API de Antonio leyendo el output.
¿Qué versión mínima de SF necesito?
Para MCP server: las versiones más recientes (revisar el changelog oficial). Para la API de Antonio: cualquier versión que escriba crawls a Derby/CSV — la cobertura de tabs depende de la versión.
Cierre
El comparativo no es "cuál gana". Es "qué hace cada uno bien", para saber cuándo elegir cuál o cuándo combinarlos. En auditorías reales (e-commerce 100K+ URLs, medios con templates fragmentados, multi-país con hreflang complejo) terminé usando los dos.
Crédito explícito a Antonio Atilio Maculus por construir la librería Python y mantenerla open source. Si trabajás con SF programáticamente, su repo es lectura obligada.
Si probaste alguna de las dos (o las dos), me interesa saber qué workflows armaron. Dejá un comentario o escribime.