En esta página
LlamaIndex es el framework de referencia para RAG en producción, pero viene con readers de HTML que se desmoronan en sitios con mucho JavaScript y páginas protegidas por Cloudflare. Cámbialos por CrawlForge y tu pipeline de LlamaIndex manejará cualquier URL: HTML estático, SPA o muro anti-bot.
from llama_index.core import Document
from crawlforge_llamaindex import CrawlForgeReader
reader = CrawlForgeReader(api_key="cf_live_your_key")
docs: list[Document] = reader.load_data(urls=["https://docs.stripe.com/api"])Esta guía muestra cómo usar el web scraping con LlamaIndex y CrawlForge como tu fuente de datos: desde loaders de una sola página hasta pipelines RAG completos y herramientas de agentes.
Índice
- Por qué LlamaIndex necesita un mejor lector web
- Requisitos previos
- Paso 1: Instala las dependencias
- Paso 2: Crea un lector de CrawlForge
- Paso 3: Indexa páginas web en vivo
- Paso 4: Consulta el índice
- Ejemplo completo: RAG de docs con actualizaciones en vivo
- Avanzado: herramientas de CrawlForge para agentes de LlamaIndex
- Solución de problemas
- Preguntas frecuentes
Por qué LlamaIndex necesita un mejor lector web
El SimpleWebPageReader y el BeautifulSoupWebReader integrados de LlamaIndex están bien para entradas de blog estáticas, pero fallan en:
- Contenido renderizado con JavaScript (apps de React, Vue, Angular)
- Páginas protegidas por Cloudflare / DataDome / Akamai (la mayoría de docs SaaS)
- Sitios que devuelven 403 a User-Agents genéricos
- Páginas donde el contenido principal está dentro de un hermano
<main>, no extraíble de forma trivial
CrawlForge resuelve los cuatro casos. Su herramienta extract_content usa un algoritmo de legibilidad afinado para páginas de artículos, docs y productos. stealth_mode maneja el anti-bot. scrape_with_actions ejecuta JavaScript. Las 26 herramientas devuelven texto o markdown limpio listo para chunking. Para entender por qué esto importa en RAG, consulta nuestra guía de pipelines RAG.
Requisitos previos
- Python 3.9+ --
python --version - LlamaIndex --
pip install llama-index-core llama-index-readers-web - Cuenta de CrawlForge -- gratis en crawlforge.dev/signup, 1.000 credits incluidos
- API key de OpenAI o Anthropic para las llamadas al LLM de LlamaIndex (o usa cualquier proveedor compatible)
Paso 1: Instala las dependencias
pip install llama-index-core llama-index-embeddings-openai requestsExporta tus claves:
export CRAWLFORGE_API_KEY="cf_live_your_key_here"
export OPENAI_API_KEY="sk-..."Paso 2: Crea un lector de CrawlForge
Los readers de LlamaIndex heredan de BaseReader y devuelven objetos Document. Aquí tienes un reader mínimo que envuelve el endpoint extract_content de CrawlForge:
# crawlforge_reader.py
import os
from typing import List, Optional
import requests
from llama_index.core.readers.base import BaseReader
from llama_index.core.schema import Document
class CrawlForgeReader(BaseReader):
"""LlamaIndex reader that uses CrawlForge for web scraping."""
BASE_URL = "https://crawlforge.dev/api/v1/tools"
def __init__(self, api_key: Optional[str] = None, use_stealth: bool = False):
self.api_key = api_key or os.environ["CRAWLFORGE_API_KEY"]
self.use_stealth = use_stealth
def load_data(self, urls: List[str]) -> List[Document]:
documents = []
tool = "stealth_mode" if self.use_stealth else "extract_content"
for url in urls:
response = requests.post(
f"{self.BASE_URL}/{tool}",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
json={
"url": url,
"options": {"format": "markdown"},
},
timeout=30,
)
response.raise_for_status()
data = response.json()
documents.append(
Document(
text=data.get("content", ""),
metadata={
"source": url,
"title": data.get("title"),
"scraped_at": data.get("scraped_at"),
},
)
)
return documentsCoste: 2 credits por URL con extract_content, 5 credits con stealth_mode.
Paso 3: Indexa páginas web en vivo
Conecta el reader a un pipeline estándar de LlamaIndex:
# build_index.py
from llama_index.core import VectorStoreIndex
from crawlforge_reader import CrawlForgeReader
reader = CrawlForgeReader()
docs = reader.load_data(urls=[
"https://docs.stripe.com/api/charges/create",
"https://docs.stripe.com/api/payment_intents/create",
"https://docs.stripe.com/api/refunds/create",
])
index = VectorStoreIndex.from_documents(docs)
index.storage_context.persist(persist_dir="./storage/stripe_docs")Ahora tienes un índice persistido de la API de Stripe creado a partir de docs en vivo. Coste: 6 credits (3 URLs x 2).
Paso 4: Consulta el índice
# query_index.py
from llama_index.core import StorageContext, load_index_from_storage
storage = StorageContext.from_defaults(persist_dir="./storage/stripe_docs")
index = load_index_from_storage(storage)
query_engine = index.as_query_engine()
response = query_engine.query(
"What are the required fields to create a Charge in Stripe's API?"
)
print(response)
# -> "To create a Charge, you must provide amount (integer in cents) and
# currency (three-letter ISO code). Additionally you need a source
# (payment method) or customer."Ejemplo completo: RAG de docs con actualizaciones en vivo
Júntalo todo: un RAG de docs de Stripe que se refresca cada noche:
# docs_rag.py
import os
from datetime import datetime
from llama_index.core import VectorStoreIndex, StorageContext, load_index_from_storage
from crawlforge_reader import CrawlForgeReader
PERSIST_DIR = "./storage/stripe_docs"
TARGET_URLS = [
"https://docs.stripe.com/api/charges/create",
"https://docs.stripe.com/api/payment_intents/create",
"https://docs.stripe.com/api/refunds/create",
"https://docs.stripe.com/api/customers/create",
"https://docs.stripe.com/api/subscriptions/create",
]
def refresh_index() -> VectorStoreIndex:
"""Re-scrape sources and rebuild the index."""
reader = CrawlForgeReader()
docs = reader.load_data(urls=TARGET_URLS)
index = VectorStoreIndex.from_documents(docs)
index.storage_context.persist(persist_dir=PERSIST_DIR)
print(f"Indexed {len(docs)} docs at {datetime.utcnow().isoformat()}Z")
return index
def load_index() -> VectorStoreIndex:
"""Load the persisted index from disk."""
storage = StorageContext.from_defaults(persist_dir=PERSIST_DIR)
return load_index_from_storage(storage)
def ask(question: str) -> str:
index = load_index() if os.path.exists(PERSIST_DIR) else refresh_index()
return str(index.as_query_engine().query(question))
if __name__ == "__main__":
# Refresh once a day in a cron job: python docs_rag.py --refresh
import sys
if "--refresh" in sys.argv:
refresh_index()
else:
print(ask("How do I create a refund for a charge?"))Coste del refresco nocturno: 10 credits (5 URLs x 2). En 30 días son 300 credits, holgadamente dentro del plan gratuito.
Avanzado: herramientas de CrawlForge para agentes de LlamaIndex
El sistema de agentes de LlamaIndex acepta definiciones de FunctionTool arbitrarias. Envuelve las llamadas a CrawlForge como herramientas y tu agente podrá hacer scraping bajo demanda:
# crawlforge_tools.py
from llama_index.core.tools import FunctionTool
from crawlforge_reader import CrawlForgeReader
def scrape_url(url: str) -> str:
"""Scrape a URL and return its main content as markdown."""
reader = CrawlForgeReader()
docs = reader.load_data(urls=[url])
return docs[0].text if docs else ""
def search_and_scrape(query: str, n: int = 3) -> list[str]:
"""Search the web and return content from the top N results."""
import os, requests
resp = requests.post(
"https://crawlforge.dev/api/v1/tools/search_web",
headers={"Authorization": f"Bearer {os.environ['CRAWLFORGE_API_KEY']}"},
json={"query": query, "limit": n},
timeout=30,
).json()
urls = [r["url"] for r in resp.get("results", [])]
reader = CrawlForgeReader()
return [d.text for d in reader.load_data(urls=urls)]
scrape_tool = FunctionTool.from_defaults(fn=scrape_url)
search_tool = FunctionTool.from_defaults(fn=search_and_scrape)Después pasa [scrape_tool, search_tool] a cualquier agente de LlamaIndex:
from llama_index.core.agent import ReActAgent
from llama_index.llms.openai import OpenAI
agent = ReActAgent.from_tools(
tools=[scrape_tool, search_tool],
llm=OpenAI(model="gpt-4o-mini"),
verbose=True,
)
response = agent.chat(
"Research the current state of Anthropic's MCP protocol adoption in 2026. "
"Cite at least 3 sources."
)
print(response)Desglose del coste en credits
| Operación | Herramienta | Credits |
|---|---|---|
| Ingerir una página estática | extract_content | 2 |
| Ingerir una página con mucho JS | scrape_with_actions | 5 |
| Ingerir una protegida por Cloudflare | stealth_mode | 5 |
| Búsqueda + scraping del agente (3 URLs) | search_web + 3x extract_content | 11 |
| Deep research completo | deep_research | 10 |
Solución de problemas
Document.text vacío en algunas URLs -- La página probablemente requiere JavaScript. Instancia con use_stealth=True o crea una variante del reader que llame a scrape_with_actions.
requests.exceptions.HTTPError: 429 -- Estás alcanzando el límite de velocidad de CrawlForge. Añade reintentos con backoff o divide las cargas masivas en lotes de 10 URLs.
La indexación de LlamaIndex es lenta -- Procesa en lotes las llamadas a tu reader con concurrent.futures.ThreadPoolExecutor (vinculado a E/S, el GIL no es un bloqueo). Lo habitual es un aumento de velocidad de 10x en 50+ URLs.
Faltan metadatos del Document -- El endpoint scrape_structured de CrawlForge no rellena title igual que lo hace extract_content. Usa extract_content para la ingesta de RAG; usa scrape_structured solo para la extracción de campos tipados.
El coste de embeddings se dispara -- LlamaIndex vuelve a generar embeddings en cada llamada a VectorStoreIndex.from_documents. Persiste con index.storage_context.persist() y carga con load_index_from_storage() para evitar trabajo repetido.
Próximos pasos
- Lee la guía de pipelines RAG para patrones de recuperación de extremo a extremo
- Explora otros frameworks en nuestra entrada sobre integración con LangChain
- Consulta los docs de primeros pasos para la API REST completa
- Compara proveedores de scraping en alternativa a Firecrawl
Empieza gratis con 1.000 credits en crawlforge.dev/signup. No se requiere tarjeta de crédito.
Pruébalo tú mismo — sin necesidad de registrarte
Ejecuta cualquiera de las 27 herramientas de scraping y extracción de CrawlForge en el playground y luego empieza gratis con 1,000 credits.
1,000 credits gratis • Se recargan cada mes • 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.