En esta página
Todos y cada uno de nuestros tests de amazon-product pasaban. La herramienta devolvía null en la moneda del precio, null en la valoración, null en todas las imágenes y la cadena "Brand: Amazon" donde debía ir la marca. En CI no parecía roto nada, porque los fixtures se habían escrito para encajar con los selectores en lugar de con el sitio.
Esa es la forma de casi toda la versión CrawlForge MCP v5.2. Durante dos días pasamos las 28 herramientas por sitios reales — Wikipedia, Hacker News, Amazon, tiendas Shopify, npm, YouTube, GitHub, Reddit, un PDF de la agencia tributaria estadounidense — y publicamos seis versiones: de la v5.2.0 a la v5.2.5, más cuatro versiones del paquete compartido crawlforge-extractors. Esto es todo el conjunto, ordenado por lo que hace por ti y no por número de versión.
El número de herramientas sigue en 28. Se movió un precio. No se renombró nada ni cambió ninguna forma de salida, así que la actualización es directa.
Tabla de contenidos
- La versión de un vistazo
- Por qué llegaron tantas correcciones a la vez
- Datos de producto: Shopify, Amazon, npm, YouTube
- Búsqueda en todo Reddit tras la caída de PullPush
- Seguimiento de cambios que sí se dispara
- Fidelidad del scraping: contenido que se estaba borrando
- LLM locales: Ollama por fin funciona
- Automatización de navegador y el atasco del navegador stealth
- Detección de idioma y tablas de PDF
- Infraestructura: cachés, transporte, Chromium
- Una sola copia de los extractores
- Coste en credits
- Cómo actualizar
La versión de un vistazo
| Cambio | Herramienta | Versión |
|---|---|---|
Nueva plantilla shopify-product que lee el JSON de la propia tienda | scrape_template | 5.2.0 |
| Las plantillas pueden pedir un endpoint legible por máquina en vez de HTML | scrape_template | 5.2.0 |
amazon-product reconstruida contra páginas reales | scrape_template | 5.2.0 |
responseTime en la respuesta | fetch_url | 5.2.0 |
cached y crawled_at en la respuesta | crawl_deep | 5.2.0 |
Endpoints Ollama remotos vía OLLAMA_API_KEY | herramientas con LLM | 5.2.0 |
| Los cambios de precio se puntúan por magnitud, no por espacio en la página | track_changes | 5.2.0 |
customSelectors acota de verdad la comparación | track_changes | 5.2.0 |
| El contenido servido en streaming por el framework ya no se borra | scrape | 5.2.0 |
| El contenido de los scripts ya no cuenta como texto de la página | scrape | 5.2.0 |
| El eco del esquema del LLM falla de forma visible en vez de pasar por datos | scrape | 5.2.0 |
| Ollama registrado como proveedor LLM; se elige el mejor modelo instalado | extract_structured, deep_research | 5.2.0 |
| Siete defectos de Playwright corregidos | scrape_with_actions | 5.2.0 |
| Fuga de renderizadores y recuperación de atascos | stealth_mode | 5.2.0 |
Se respeta la configuración de caché en disco; crawl_deep cachea solo en memoria | herramientas con caché | 5.2.0 |
shopify-product aparece en la descripción de herramienta que lee el modelo | scrape_template | 5.2.1 |
structuralSimilarity ya puede puntuar por debajo de 0,5 | track_changes | 5.2.1 |
| Las tablas de maquetación se convierten a markdown en vez de colarse como HTML | scrape, extract_text | 5.2.2 |
| Chino, griego, árabe, noruego y malayo pasan a ser detectables | analyze_content | 5.2.2 |
| Búsqueda por palabra clave en todo Reddit reconstruida con descubrimiento web e hidratación desde el archivo | reddit_search | 5.2.3 |
npm-package lee el documento del registro, no la página web | scrape_template | 5.2.3 |
reddit_search pasa de 2 a 5 credits | reddit_search | 5.2.4 |
customSelectors vuelve a indexar elementos en etiquetas que no son div; se informan las opciones de comparación ignoradas | track_changes | 5.2.5 |
youtube-video devuelve visualizaciones reales y añade "me gusta" | scrape_template | extractors 1.2.1 |
| La extracción de tablas de PDF devuelve las tablas que siempre tuvo | process_document | API alojada |
Por qué llegaron tantas correcciones a la vez
Porque dejamos de fiarnos de la suite de tests y apuntamos las herramientas a sitios reales.
El fallo de amazon-product de arriba es el caso más claro. Seis tests unitarios cubrían esa plantilla y los seis pasaban, porque cada selector que ejercitaban — una meta etiqueta priceCurrency, #acrPopover .a-size-base, img.a-thumbnail-image — no existe hoy en ninguna parte de Amazon, y el HTML del fixture se había escrito para contenerlos. Los tests comprobaban que nuestro código estaba de acuerdo consigo mismo.
El patrón se repitió. scrape_with_actions tenía siete defectos de Playwright invisibles para su suite porque las páginas falsas implementaban lo que el ejecutor llamara, incluidas APIs que Playwright no tiene. La plantilla youtube-video leía un atributo que no aparece en ninguna página de vídeo de YouTube mientras su fixture mantenía el test en verde. process_document leía un campo que la librería de PDF declara y nunca escribe.
Así que las suites nuevas se escribieron para fallar primero contra el código anterior a la corrección — 15 de 24 en amazon-product, 8 de 10 en el alcance de caché de crawl_deep, 7 en las APIs de Playwright de scrape_with_actions. La suite son ahora 1.122 tests unitarios con un 100 % de cumplimiento del protocolo MCP en las 28 herramientas, pero la cifra que importa es la anterior: cada corrección de aquí abajo la encontró una página real, no CI.
Datos de producto: Shopify, Amazon, npm, YouTube
Cambiaron cuatro de las plantillas de scrape_template, y tres van en la misma dirección: dejar de analizar la página renderizada y leer los datos estructurados que el sitio ya publica.
shopify-product, la nueva
El tema Dawn de Shopify envía en el marcado todas las etiquetas de precio sin condición y oculta con CSS de componente las que no aplican. Un scraper que lea el DOM ve una etiqueta de "Sold out" en un producto con cien unidades en stock. Pídele a un LLM el precio tachado de un producto que no lo tiene y producirá uno verosímil: en nuestras pruebas se inventó 27.99.
Shopify sirve esos mismos datos como JSON en /products/<handle>.json, en todas las tiendas incluidas las de dominio propio, así que la plantilla lee eso:
{
"tool": "scrape_template",
"params": {
"template": "shopify-product",
"url": "https://shop.example.com/products/kelpie-bandana"
}
}Obtienes precio exacto, precio tachado, on_sale, moneda, el rango de precios entre variantes, stock por variante, opciones, imágenes y etiquetas — sin analizar HTML y sin LLM por medio. Las peculiaridades de tienda encontradas en capturas reales están cubiertas: un precio tachado ausente es "" en una tienda y "0.00" en otra (ambos se leen como null, mientras que un producto realmente gratuito conserva su precio 0.00), las etiquetas llegan como array o como cadena separada por comas y, como el endpoint no trae un campo available, el stock se deduce de la gestión de inventario, la política y la cantidad — y reporta null en vez de suponer "en stock" cuando el payload no lo dice.
Para que eso fuera posible hizo falta un cambio un nivel más abajo: TemplateRegistry ganó dos hooks opcionales, resolveUrl(url) para redirigir la única petición de la herramienta y extractRaw(body, url) para procesar una respuesta que no sea HTML. La herramienta sigue siendo la dueña de la petición protegida contra SSRF y ahora informa de fetchedUrl cuando hubo reescritura. Las plantillas HTML no cambian.
amazon-product, reconstruida
Los selectores se volvieron a derivar de capturas reales de tres páginas — un dispositivo de la propia Amazon, una tienda de marca y un libro:
- La moneda sale del campo oculto del formulario de añadir al carrito, no de una meta etiqueta.
- La valoración se convierte a número desde el atributo
titlede#acrPopover, así que obtienes4.7y no"4.7 out of 5 stars". - El número de reseñas se interpreta tanto de
"(198,594)"como de"198,594 global ratings"— Amazon usa ambos — y devuelve198594. - La marca reduce las tres formas de la línea de autoría al nombre pelado.
- Las imágenes eliminan de la URL el token de tamaño de Amazon. La URL con token es una miniatura de 1 KB; esa misma URL sin él es el original de 16 KB.
npm-package, ahora leyendo el registro
npmjs.com responde con 403 a las peticiones HTTP simples y, cuando llegaba un cuerpo, los selectores se apoyaban en fragmentos de nombres de clase que ya no coinciden — la versión volvía null, las descargas semanales null y repository apuntaba al enlace de stargazers. Ahora resuelve la página del paquete a su documento de registro y lee ese, devolviendo versión, licencia, repositorio, homepage, mantenedores, keywords, dependencias y cualquier aviso de obsolescencia. Las URL de repositorio llegan como git+ssh://, git+https://, git:// o un simple owner/repo, y se normalizan a https navegable.
Las descargas semanales se omiten en lugar de devolverse como null, y es deliberado. Viven en un endpoint aparte, y el endpoint del registro que las trae junto a los datos del paquete es de búsqueda: pídele left-pad y responde con pad-left. Devolver el contador de descargas de otro paquete es peor que no devolver ninguno.
youtube-video, contando la estadística correcta
La plantilla leía meta[itemprop="interactionCount"], que no aparece en ninguna página de vídeo, así que las visualizaciones volvían null contra todos los vídeos reales. El marcado real es un bloque InteractionCounter de schema.org por estadística, distinguidos solo por un interactionType hermano — y YouTube emite primero el contador LikeAction, así que la corrección obvia devuelve en silencio los "me gusta" donde deberían ir las visualizaciones. La selección se hace ahora por interactionType, convertida a número para que cero visualizaciones se lea como 0 y no como null, y ya existe el campo likes que la descripción prometía.
Búsqueda en todo Reddit tras la caída de PullPush
Si tu pipeline de Reddit empezó a devolver 429 este mes, la solución no es dormir más entre peticiones, rotar el user agent ni meter un proxy. Probamos las tres. Ahora toda petición a PullPush devuelve:
{ "error": "Rate limit exceeded. This website does not provide free scraping resources for agents..." }Con cualquier user agent, sin él o con el de un navegador — da igual, y desde algunas IP de servidor llega un reto 403 de Cloudflare. Esto es una política, no un límite de tasa.
Importa más que un endpoint caído por lo que PullPush hacía y nadie más hace. Arctic Shift, el otro archivo público de Reddit, no puede buscar por palabra clave en todo Reddit por diseño. Pídele una consulta que no nombre ni subreddit ni autor y devuelve HTTP 400:
'query' query parameter requires one of: author, subreddit
Así que las búsquedas con ámbito (un subreddit, un usuario) seguían funcionando, y las que no lo tenían — aquellas en las que conoces la frase pero no la comunidad — se quedaron sin ningún backend. Y esa es justo la búsqueda que casi todo el mundo quiere, la que la propia API de Reddit nunca ha expuesto de forma limpia.
reddit_search la resuelve ahora en dos pasos. Descubrimiento: una búsqueda web restringida a reddit.com devuelve URLs de posts por orden de relevancia, y los IDs de post salen del segmento /comments/<id>/. Hidratación: esos IDs van al archivo de Arctic Shift en una única consulta por lotes, que devuelve las filas reales del post — puntuación, número de comentarios, subreddit, autor, marca de tiempo, selftext completo — y después el orden del archivo se reasigna al orden de relevancia.
La distinción que importa: son filas del archivo, no fragmentos de resultados de búsqueda. El descubrimiento solo aporta las direcciones. Las respuestas llevan source: "web_discovery" para que sepas qué ruta las sirvió.
Dos límites, ambos reportados en lugar de ocultados: after y before no se pueden aplicar en la ruta de descubrimiento, y la respuesta indica que el filtro se ignoró en vez de devolver resultados que parecen filtrados; y una búsqueda de comentarios sin ámbito ya no tiene ningún backend, así que pide un ámbito subreddit o author en lugar de fallar de forma genérica. PullPush ya no se intenta automáticamente en ningún sitio, tampoco como respaldo de una búsqueda con ámbito — ahí lo único que podía hacer era gastar una petición y enterrar el error real de Arctic Shift detrás de un segundo fallo. Pasar source: "pullpush" sigue llegando a él.
Cualquier página que todavía te diga que PullPush es la única herramienta capaz de buscar entre subreddits se escribió antes de agosto de 2026.
Seguimiento de cambios que sí se dispara
track_changes tenía siete defectos a lo largo de esta versión, y el primero significaba que la monitorización de precios — su caso de uso estrella — no funcionaba.
Los cambios de precio se puntuaban por el espacio que ocupan en la página. La significancia era puramente volumétrica, así que $19.99 → $29.99 y $19.99 → $99.99 puntuaban ambos como "minor", y sin ámbito el cambio ni siquiera se registraba. Con notificationThreshold por defecto en "moderate", un monitor montado de la forma obvia no se disparaba nunca. Ahora los importes monetarios se comparan directamente y su magnitud relativa eleva la significancia al menos a "moderate", o a "major" a partir del 20 %. Solo cuentan los números con moneda, así que los contadores de visitas y los totales de reseñas no disparan nada; los separadores de miles se interpretan, así que $1,299 se lee como 1299. El par aparece en details.valueChanges para que puedas ver por qué saltó un monitor.
customSelectors no acotaba nada. Solo se leía dentro del análisis por secciones, donde añadía hashes — así que acotar una comparación la empeoraba. En una página de producto de Amazon, acotar al bloque de precio subió los elementos modificados de 456 a 3204 y el payload de 5,35 MB a 6,18 MB, y reportaba cambios en una página cuyo precio no se había movido. El análisis ahora reduce el documento a los subárboles coincidentes, lo que acota a la vez el hashing, la similitud y los diffs. Un selector que no coincide con nada recurre al documento completo y avisa, en lugar de seguir en silencio sin vigilar nada.
structuralSimilarity mentía en las dos direcciones. Reportaba 0 cuando ni siquiera había medido — y cero es una puntuación real que significa "la estructura cambió por completo", así que desactivar el seguimiento estructural producía la señal más fuerte posible de que la estructura había cambiado. Ahora es null cuando no se ha medido. Y en la 5.2.1: la puntuación no podía bajar de 0,5, porque la mitad jerárquica de la media comparaba un objeto que se inicializaba vacío y nunca se escribía, devolviendo un 1 constante. Una página reconstruida con las mismas etiquetas en un anidamiento completamente distinto puntuaba un 1,0 perfecto — precisamente el caso que la métrica existe para detectar. Ahora es un histograma de recuento de elementos por profundidad comparado como un Jaccard ponderado.
Los payloads de diff están acotados. line_diff incrustaba el documento completo antes y después, porque el colapso de espacios en blanco degenera el diff en "borrar todo, añadir todo". Ambos diffs llevan ahora un marcador explícito omittedEntries.
Otros tres aparecieron en la 5.2.5, probando el seguimiento de precios en Zillow y Newegg.
Acotar a cualquier cosa que no fuera un puñado de etiquetas no vigilaba nada. El análisis por elementos indexaba una lista fija — h1-h6, p, div, span, a — así que un monitor acotado a address, td, li, tr o dd construía una línea base de cero elementos y no podía reportar nunca un cambio a nivel de elemento. Una página de Zillow acotada a ['address'] construía una línea base de 0 elementos a partir de 9 nodos coincidentes. Ahora también se hashean las coincidencias fuera de la lista; las etiquetas de la lista se omiten, así que los recuentos acotados a div no cambian.
Una comparación acotada se ejecutaba en silencio sin acotar. compareWithBaseline descarta las opciones de seguimiento de quien llama en favor de las de la línea base. Eso es correcto y necesario para un diff válido — ambos lados deben analizarse igual, y una línea base acotada ya no conserva el documento completo para volver a acotarlo — pero no lo decía, así que una comparación acotada devolvía resultados idénticos a una sin acotar y no había forma de saberlo. Las opciones ignoradas vuelven ahora en un array warnings.
Aparecía "Text content changed" en comparaciones que no encontraban cambios, porque el ruido de tokens por debajo del umbral sigue poblando textChanges aunque hasChanges sea false. El resumen ahora se remite a la significancia.
Verificado contra la página real de Zillow que los destapó: la línea base pasó de 0 a 9 elementos, y una comparación que afirmaba true/"moderate"/28 modificados ahora reporta false/"none"/0 modificados con un 100 % de similitud.
Fidelidad del scraping: contenido que se estaba borrando
Tres defectos de scrape compartían raíz: las pasadas de limpieza que eliminan contenido invisible estaban eliminando contenido visible.
Se borraba el contenido servido en streaming por el framework. La limpieza de contenido oculto eliminaba <div id="S:0" hidden>, que es donde el App Router de Next.js hace streaming de la página renderizada. En una página de precios ese contenedor es toda la página visible, así que el markdown volvía vacío y con él todos los precios. Ahora la salvaguarda del contenedor toma el mayor entre la proporción de texto y la de marcado.
El contenido de los scripts contaba como texto de la página. La salvaguarda de eliminación masiva medía los elementos con $('body').text(), que incluye el código fuente de cada <script> en línea. En una tienda Shopify el denominador eran 62.269 caracteres de los cuales 4.295 eran texto visible — así que un contenedor con toda la sección de producto quedaba por debajo del umbral y se borraba junto con el precio, tras lo cual la ruta json no tenía precio que extraer y el modelo se inventaba uno. Ambos lados del cociente excluyen ahora script/style/noscript/template.
El eco del esquema de un LLM se devolvía como datos. El formato json leía solo success de extract_with_llm y descartaba el resto, así que tres fallos llegaban a quien llamaba con aspecto de extracciones limpias: un documento de esquema devuelto en lugar de datos de la página (JSON bien formado y, sin campos obligatorios, también pasaba la validación), salida que no superaba la validación, y entrada recortada en silencio en el tope de 50.000 caracteres. extract_with_llm detecta ahora un eco de esquema, reintenta una vez y falla con un error accionable; scrape expone el desajuste de esquema y el recorte como advertencias sin perder los datos.
Y en la 5.2.2, el formato markdown dejó de devolver HTML en crudo. turndown-plugin-gfm solo convierte una tabla cuando su primera fila es enteramente <th>; cualquier otra cae en el filtro keep del plugin y se emite tal cual. Las páginas reales están llenas de tablas de maquetación — tablas usadas para colocar la página, no para presentar datos, y Hacker News está construido entero con ellas — así que scrape con formats: ["markdown"] y extract_text con output_format: "markdown" devolvían marcado <table>. Ahora hay reglas registradas después del plugin que capturan esas tablas primero y las aplanan a su contenido de celda. Las tablas con una fila de encabezado de verdad siguen renderizando como tablas GFM; comprobamos una tabla de datos de Wikipedia antes y después y es idéntica byte a byte.
LLM locales: Ollama por fin funciona
Ollama nunca estuvo registrado como proveedor LLM. LLMManager registraba solo OpenAI y Anthropic, ambos condicionados a una API key. Así que en una máquina con Ollama y sin claves de nube, extract_structured se saltaba por completo la extracción con LLM y reportaba css_fallback — produciendo valores como "$79.99$79.99" y perdiendo campos — y deep_research desactivaba en silencio la expansión de consultas, el ranking semántico y la síntesis. extract_with_llm tiene su propio cliente privado, que es por lo que funcionaba y tapaba el agujero. Una llamada fallida al LLM tampoco reporta ya extraction_method: "llm" con confianza 0,9.
El enrutado de modelos elige el mejor modelo instalado en lugar de usar siempre llama3.2. Medido contra tres páginas de producto reales con verdad de referencia verificada, gemma3:4b sacó 18/18 en 1040 ms mientras que llama3.2 sacó 16/18 — y los fallos son sistemáticos, no ruido de muestreo: en cinco ejecuciones llama3.2 se inventó un precio tachado las cinco veces. El número de parámetros no predijo la precisión; el modelo de 4B ganó a uno de 12B y a otro de 20B. selectOllamaModel() elige el modelo instalado mejor clasificado; OLLAMA_DEFAULT_MODEL sigue teniendo prioridad si lo defines.
Los endpoints Ollama remotos funcionan vía OLLAMA_API_KEY. Cada llamada HTTP a Ollama envía Authorization: Bearer cuando la variable está definida, así que un despliegue alojado puede apuntar a Ollama Cloud o a cualquier instancia con autenticación delante, sin ninguna clave de OpenAI ni de Anthropic. Sin definir, no cambia nada.
Automatización de navegador y el atasco del navegador stealth
scrape_with_actions arrastraba siete defectos de API de Playwright y de recuperación de errores, todos invisibles para la suite porque sus páginas falsas implementaban lo que el ejecutor llamara, incluidas APIs que Playwright no tiene:
scroll toElementllamaba ascrollIntoView(), que no existe en un handle ni en un locator, así que esa rama lanzaba error cada vez que se ejecutaba.- La acción
waitanunciabaenabled/disabled/stablepero se los pasaba a una API que los rechaza. - El
Promise.racepor acción compartía plazo con el trabajo contra el que competía y ganaba, sustituyendo el error real de Playwright por un escueto "Action timeout" — y dejaba un temporizador vivo por acción. - Los clics y las pulsaciones de tecla no esperaban al documento que reemplazaban.
- Un reintento de cadena se reproducía contra lo que hubiera dejado el intento fallido, sin recargar nunca.
- Todas las estrategias de recuperación estaban detrás de
retries > 0mientras el esquema poníaretriesa 0 por defecto, así que ninguna podía ejecutarse.
El navegador stealth se atascaba solo. stealth_mode create_page nunca cerraba su página, con lo que se fugaba un renderizador de Chromium por llamada hasta que la instancia se quedaba sin memoria. Un navegador atascado se reutilizaba luego para siempre detrás de comprobaciones de mera veracidad, y la limpieza se colgaba en llamadas de protocolo al navegador muerto, así que no se podía desatascar en remoto. Ahora hay detección de cadáveres con isConnected(), un manejador de desconexión, una limpieza que compite contra plazos de 5 s con SIGKILL de reserva y recreación del pool, y un mutex de arranque en vuelo.
Las imágenes alojadas ignoraban el Chromium del sistema. chromium.launch respeta ahora PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH — el Dockerfile lo lleva definiendo desde siempre, pero Playwright no lee variables de entorno por su cuenta — así que las rutas de navegador dejan de morir en Alpine buscando un chrome-headless-shell que nunca se descargó.
Detección de idioma y tablas de PDF
Cinco idiomas eran indetectables. analyze_content filtra la salida de franc contra un mapa de nombres de idioma, y cinco claves de ese mapa eran códigos ISO 639-2/B — chi, gre, ara, nor, msa — que franc, que emite ISO 639-3, nunca produce. El efecto era silencioso y total: una página escrita enteramente en chino devolvía null. También el griego, el árabe, el noruego y el malayo. Los códigos ahora son cmn, ell, arb, nob y zlm/zsm.
Detrás había un segundo bug: franc puntúa el sistema de escritura más frecuente, y una página en chino o japonés arrastra la ristra habitual de nombres de producto y ejemplos de código en inglés, así que puntuaba como prosa en alfabeto latino. La detección hace ahora un cortocircuito cuando la proporción de han, kana o hangul alcanza al menos el 10 % de las letras — medido sobre páginas reales, las de alfabeto latino se quedan en el 0 % y las CJK auténticas van del 24 % al 51 %.
La extracción de tablas de PDF devolvía [] para todos los documentos que pasaron por ella. La ruta alojada de process_document leía TableResult.mergedTables, un campo que la librería de PDF declara pero nunca escribe; las tablas están en result.pages[].tables. Contra tres PDF reales — el artículo de arXiv "Attention Is All You Need", el formulario W-9 y el 1040 de la agencia tributaria estadounidense — el campo que se leía tenía 0 tablas en los tres casos, mientras que los arrays por página tenían 7, 6 y 11. Junto a la corrección, se descartan las tablas con todas las celdas vacías (son cajas dibujadas — contornos de formulario y bordes de figura — no datos), cada tabla informa de su número de página, el tope de 20 tablas dice cuántas dejó fuera, y una tabla con rayado solo horizontal lleva una nota en vez de leerse como una tabla real de una columna.
Infraestructura: cachés, transporte, Chromium
CACHE_DIR y CACHE_ENABLE_DISK no hacían nada. Ambas llevan siempre expuestas en la configuración, pero CacheManager no leía ninguna: el directorio estaba fijado en el código y la caché en disco siempre activa. Cada proceso de test compartía por tanto un directorio que sobrevivía a todas las ejecuciones — se habían acumulado 1.778 ficheros — y como crawl_deep indexa por la URL rastreada mientras los servidores de test se enlazan a puertos efímeros que el sistema recicla, una ejecución podía recibir el rastreo de otro sitio hecho en una ejecución anterior. Esa era la causa de un fallo intermitente poco frecuente en ejecuciones paralelas cuya pista estaba en los tiempos: dos fallos tardaron 13 ms y 5 ms en tests que hacen dos rastreos completos cada uno. Los valores por defecto no cambian — las variables simplemente hacen lo que siempre dijeron.
El cacheEnabled: false de crawl_deep desactivaba solo la mitad del cacheo. BFSCrawler construye su propia caché y guardaba el cuerpo de cada página descargada sin forma de desactivarlo, así que a quien pedía explícitamente que no hubiera caché se le servían páginas cacheadas. Ambas cachés escribían además en disco pese a describirse en el código como propias de la sesión; en disco, los cuerpos sobrevivían al crawler una hora y cruzaban fronteras de proceso. Ahora ambas son solo de memoria.
Las herramientas con tareas fallaban por Streamable HTTP. cloneServerForSession perdía las capacidades y el almacén de tareas, así que tools/call sobre agent, crawl_deep, batch_scrape o deep_research lanzaba "No task store provided for task-capable tool."
Una sola copia de los extractores
El servidor MCP y la API REST de CrawlForge llevaban cada uno su propia copia de los extractores de plantillas, en dos lenguajes, sin nada que detectara la divergencia — y divergió dos veces en dos días. amazon-product se reparó en el lado MCP el 25 de agosto mientras la copia REST siguió devolviendo una valoración nula y "Brand: Amazon" hasta el 26, y shopify-product existía solo en un lado.
Ahora hay una única implementación, publicada como crawlforge-extractors, que instalan ambas superficies. TemplateRegistry se reexporta con una API sin cambios — nada de scrape_template cambia para quien lo llama. Es una dependencia de ejecución, no una peer dependency, así que npm install -g crawlforge-mcp-server la arrastra automáticamente; no hay nada extra que instalar.
Otros dos comportamientos se mudaron ahí porque una superficie los tenía y la otra no:
readBodydecodifica una respuesta usando su propio charset y se niega a bufferizar más allá de un tope. El servidor MCP hacía ambas cosas; la API REST llamaba aresponse.text(), que asume UTF-8 — así que las páginas servidas como Shift_JIS, GBK o ISO-8859-1 volvían como mojibake — y bufferizaba un cuerpo de cualquier tamaño dentro de una función serverless.structureSignatureidentifica un documento por su vocabulario de etiquetas más un histograma de recuento de elementos por profundidad, lo bastante pequeño como para guardarlo junto a una línea base de seguimiento de cambios, y acepta una raíz opcional para que quien vigila un selector CSS puntúe solo esa región.
La alternativa era un test de paridad que nos dijera a posteriori qué copia estaba mal. Borrar la segunda copia sale más barato que detectar su deriva.
Coste en credits
En esta versión se movió un precio. reddit_search pasó de 2 credits a 5, igualando a search_web.
El motivo es el mecanismo de arriba: una búsqueda en todo Reddit gasta ahora la misma llamada de búsqueda upstream que gasta search_web, y nos cuesta lo mismo. Una búsqueda con ámbito sigue consultando el archivo directamente y no nos cuesta nada extra, pero también factura 5, porque la herramienta tiene un precio único publicado y no uno por petición que tendrías que modelar. Con los 1.000 credits únicos del plan Free eso son 200 búsquedas en Reddit.
Todo lo demás no cambia: scrape_template cuesta 1 credit por llamada uses la plantilla que uses, fetch_url 1, track_changes 3, crawl_deep 4 y batch_scrape 5 por URL. Las peticiones fallidas nunca se cobran.
Cómo actualizar
npm install -g crawlforge-mcp-server@latest
crawlforge --version # 5.2.5Si tu cliente MCP arranca el servidor con npx, cogerá la versión en el siguiente reinicio. La 5.2.5 depende directamente de crawlforge-extractors@^1.2.1, así que la corrección de las visualizaciones de youtube-video viene incluida.
No hay cambios que rompan nada: no se renombró ninguna herramienta, no cambió ninguna forma de salida y solo reddit_search movió su precio. Lo único que conviene revisar antes de actualizar son tus cuentas de presupuesto si llamas a reddit_search dentro de un bucle: ahora son 5 credits, no 2.
Las mismas correcciones están activas en la API REST alojada, así que no hay nada que instalar si llamas a CrawlForge por HTTP.
¿Quieres probar algo de esto sin montar un servidor? Empieza gratis con 1.000 credits — sin tarjeta — y luego explora la referencia de la API de las 28 herramientas. Lectura relacionada: alternativas a la API de Reddit que siguen funcionando en 2026 y extraer datos con LLM locales y Ollama.
Pruébalo tú mismo — sin necesidad de registrarte
Ejecuta cualquiera de las 28 herramientas de scraping y extracción de CrawlForge en el playground y luego empieza gratis con 1,000 credits.
1,000 credits gratis • Por única vez • No se requiere tarjeta de crédito
Etiquetas
Sobre el autor
Mantente al día con los últimos artículos
Recibe tutoriales, novedades del producto y consejos de web scraping en tu bandeja de entrada.
Sin spam. Cancela tu suscripción cuando quieras.