# LangChain y LlamaIndex: un cargador de páginas web y una herramienta read_url

> Cargador de páginas web para LangChain y LlamaIndex: read_url, document loader e índice RAG desde un rastreo en Python. Sin KYC, paga con criptomonedas.

- URL: https://webreaderapi.com/es/docs/langchain/
- Última actualización: 2026-10-22
- Editor: WebReaderAPI (https://webreaderapi.com)

Dos formas de usar la API en un stack de IA en Python: cargar páginas como documentos para recuperación, o dar a un agente una herramienta `read_url`. Las mismas 20 líneas de código sirven para LangChain y LlamaIndex.

**Respuesta rápida — ¿Cómo cargo páginas web en LangChain o LlamaIndex como Markdown limpio?**

Llama al endpoint `/read` de **WebReaderAPI** y envuelve cada respuesta en un `Document`: el Markdown como contenido, `final_url` y el título como metadatos. Para agentes, expón la misma llamada como una herramienta `read_url`. Cada página cuesta 1 crédito, 2 con navegador, desde 0,07 US$/1K páginas a fecha de octubre de 2026. No se necesita ningún paquete nuestro. Sin KYC; paga con criptomonedas.

## ¿Por qué documentos Markdown en lugar de HTML sin procesar?

Porque la recuperación funciona sobre texto, y Markdown conserva la estructura que importa mientras descarta el resto. Los encabezados se mantienen, así que un splitter puede cortar por cambios de tema. Las listas y las tablas se mantienen, así que los pasos y las especificaciones permanecen juntos. La navegación, los banners de cookies y los anuncios desaparecen, así que no contaminan tus embeddings ni desperdician tokens.

Los metadatos vienen de la misma llamada: `final_url` como fuente a citar, `title`, `published_at` y `language` para filtrar.

*reader.py: el helper compartido (guárdalo junto a tu código)*

```python
import os

import requests

API = "https://webreaderapi.com/api/v1/read"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}


class PageError(Exception):
    pass


def read_page(url, **options):
    resp = requests.get(API, headers=HEADERS, params={"url": url, **options}, timeout=75)
    body = resp.json()
    if resp.status_code != 200:
        raise PageError(body["error"]["code"])
    return body

```

## ¿Cómo construyo un document loader de LangChain?

Convierte cada página en un `langchain_core.documents.Document` y luego divídela por encabezados con `MarkdownHeaderTextSplitter` del paquete `langchain-text-splitters`. Cada fragmento conserva la ruta de encabezados en sus metadatos, y añades la URL de origen para que cada respuesta pueda señalar su página.

Las páginas omitidas, como las rechazadas por robots.txt, lanzan `PageError` con el código; regístralas y continúa.

*LangChain: cargar y dividir páginas*

```python
from langchain_core.documents import Document
from langchain_text_splitters import MarkdownHeaderTextSplitter

from reader import PageError, read_page

splitter = MarkdownHeaderTextSplitter(headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")])


def load_pages(urls):
    chunks = []
    for url in urls:
        try:
            page = read_page(url)
        except PageError as exc:
            print("skipped", url, exc)
            continue
        for part in splitter.split_text(page["markdown"]):
            part.metadata.update({"source": page["final_url"], "title": page.get("title", "")})
            chunks.append(part)
    return chunks


docs = load_pages(["https://example.com/docs/install", "https://example.com/docs/configure"])
print(len(docs), "chunks", isinstance(docs[0], Document) if docs else "")

```

## ¿Cómo doy a un agente de LangChain una herramienta read_url?

Decora una función con `@tool` de `langchain_core.tools`. El docstring se convierte en la descripción de la herramienta que lee el modelo, así que indica qué devuelve la herramienta y cuándo usarla. Devuelve texto plano: una línea de título, la fuente y el Markdown, recortado con `max_chars` para adaptarse a tu modelo.

Vincula la herramienta a cualquier modelo de chat que admita tool calling, o pásala al constructor de tu agente. El modelo entonces llama a `read_url` con una URL de la conversación o de un resultado de búsqueda.

*LangChain: la herramienta read_url*

```python
import os

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool

from reader import PageError, read_page


@tool
def read_url(url: str, max_chars: int = 20000) -> str:
    """Read a public web page and return its main content as Markdown, with its title and source URL."""
    try:
        page = read_page(url, max_chars=max_chars)
    except PageError as exc:
        return "Could not read the page: " + str(exc)
    return "# " + (page.get("title") or url) + "\nSource: " + page["final_url"] + "\n\n" + page["markdown"]


model = init_chat_model(os.environ.get("CHAT_MODEL", "openai:gpt-4.1-mini")).bind_tools([read_url])
reply = model.invoke("Summarise https://example.com/blog/first-post in three bullet points.")
print(reply.tool_calls)

```

## ¿Cómo hago lo mismo en LlamaIndex?

LlamaIndex usa su propia clase `Document` con `text` y `metadata`, y `FunctionTool.from_defaults` para convertir una función en herramienta. El helper sigue siendo el mismo. Construye un índice a partir de los documentos con `VectorStoreIndex.from_documents`, que usa el modelo de embeddings que indiquen tus ajustes de LlamaIndex.

*LlamaIndex: indexar un rastreo y una herramienta read_url*

```python
import os
import time

import requests
from llama_index.core import Document, VectorStoreIndex
from llama_index.core.tools import FunctionTool

from reader import HEADERS, PageError, read_page


def crawl_pages(start_url, limit=200):
    job = requests.post("https://webreaderapi.com/api/v1/crawl", headers=HEADERS, json={"url": start_url, "limit": limit}, timeout=30).json()
    pages, cursor = [], None
    while True:
        params = {"cursor": cursor} if cursor else {}
        res = requests.get("https://webreaderapi.com/api/v1/jobs/" + job["job_id"], headers=HEADERS, params=params, timeout=30).json()
        pages += [r for r in res["results"] if "error" not in r]
        cursor = res.get("next_cursor")
        if not cursor and res["status"] == "done":
            return pages
        if not cursor:
            time.sleep(5)


pages = crawl_pages("https://docs.example.com/")
docs = [Document(text=p["markdown"], metadata={"source": p["final_url"], "title": p.get("title", "")}) for p in pages]
index = VectorStoreIndex.from_documents(docs)
print(index.as_query_engine().query("How do I install the product?"))


def read_url(url: str, max_chars: int = 20000) -> str:
    """Read a public web page and return its main content as Markdown."""
    try:
        return read_page(url, max_chars=max_chars)["markdown"]
    except PageError as exc:
        return "Could not read the page: " + str(exc)


read_tool = FunctionTool.from_defaults(fn=read_url, name="read_url")
print(os.environ.get("CHAT_MODEL", "set CHAT_MODEL for your agent"), read_tool.metadata.name)

```

## ¿Qué tamaño de rastreo necesita un índice RAG pequeño?

Menos de lo que crees. La documentación de un producto suele tener unos cientos de páginas, y un rastreo con `include_paths` ajustado a la sección de docs lee solo esas. A 1 crédito por página, un sitio de docs de 300 páginas cuesta unos 300 créditos; 10.000 páginas (3,75 US$) lo actualiza muchas veces.

Vuelve a rastrear con una frecuencia que coincida con la frecuencia con que cambia la fuente, y compara `final_url` y `word_count` con la ejecución anterior para re-embeddear solo las páginas que cambiaron. Los créditos prepago nunca caducan: las páginas sin usar permanecen en tu cuenta hasta que las uses.

## LangChain y LlamaIndex: preguntas frecuentes

### ¿Existe un paquete de WebReaderAPI para LangChain o LlamaIndex?

No. El helper y la herramienta de esta página son toda la integración, así que puedes adaptarlos libremente. WebReaderAPI es una API HTTPS simple que cualquier cliente HTTP de Python puede llamar.

### ¿Qué versión de LangChain necesita esto?

Cualquiera reciente con `langchain_core`, `langchain-text-splitters` y `init_chat_model`. El decorador de herramientas y la clase `Document` han sido estables en las versiones recientes.

### ¿Cómo evito volver a leer páginas que no cambiaron?

Guarda `final_url`, `word_count` y la fecha de cada lectura. Vuelve a leer con una frecuencia determinada y re-embeddéa una página solo cuando cambie su recuento de palabras o el hash de su contenido. Cada relectura cuesta un crédito, así que elige un ritmo que coincida con la fuente.

### ¿Y si algunas páginas son rechazadas?

Lanzan `PageError` con un código como `blocked_by_robots`, y no cuestan nada. Si un sitio rechaza nuestra solicitud, devolvemos su respuesta: nunca resolvemos CAPTCHAs, iniciamos sesión en cuentas ni intentamos saltar un bloqueo o un paywall.

---

WebReaderAPI: WebReaderAPI es una API de lectura web para agentes de IA que convierte cualquier página web pública en Markdown limpio, pagando con criptomonedas y sin subir ningún documento de identidad. Los créditos prepago desde 0,07 US$/1K páginas nunca caducan y se respeta robots.txt. Sin KYC: ni documento de identidad, ni selfie, ni documentos. Paga con criptomonedas.
