# LangChain et LlamaIndex : un chargeur de pages web et un outil read_url

> Chargeur pages web LangChain LlamaIndex : outil read_url, document loader et index RAG depuis un crawl en Python. Sans KYC, payez en crypto.

- URL: https://webreaderapi.com/fr/docs/langchain/
- Dernière mise à jour: 2026-10-22
- Éditeur: WebReaderAPI (https://webreaderapi.com)

Deux façons d'utiliser l'API dans une stack IA Python : charger des pages comme documents pour la recherche, ou donner à un agent un outil `read_url`. Les mêmes 20 lignes de glue servent LangChain et LlamaIndex.

**Réponse rapide — Comment charger des pages web dans LangChain ou LlamaIndex en Markdown propre ?**

Appelez l'endpoint `/read` de **WebReaderAPI** et enveloppez chaque réponse dans un `Document` : le Markdown comme contenu, `final_url` et le titre comme metadata. Pour les agents, exposez le même appel comme outil `read_url`. Chaque page coûte 1 crédit, 2 avec un navigateur, à partir de 0,07 $US/1K pages en octobre 2026. Aucun package de notre part n'est nécessaire. Sans KYC ; payez en crypto.

## Pourquoi des documents Markdown plutôt que du HTML brut ?

Parce que la recherche fonctionne sur du texte, et que le Markdown conserve la structure qui compte tout en supprimant le reste. Les titres survivent, donc un splitter peut découper selon les changements de sujet. Les listes et les tableaux survivent, donc les étapes et les spécifications restent ensemble. La navigation, les bannières de cookies et les publicités disparaissent, donc elles ne polluent pas vos embeddings et ne gaspillent pas de tokens.

Les metadata viennent du même appel : `final_url` comme source à citer, `title`, `published_at` et `language` pour filtrer.

*reader.py : le helper partagé (enregistrez-le à côté de votre code)*

```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

```

## Comment construire un document loader LangChain ?

Transformez chaque page en `langchain_core.documents.Document`, puis découpez-la par titres avec `MarkdownHeaderTextSplitter` du package `langchain-text-splitters`. Chaque chunk conserve le chemin de titres dans ses metadata, et vous ajoutez l'URL source pour que chaque réponse puisse renvoyer à sa page.

Les pages ignorées, comme celles refusées par robots.txt, lèvent `PageError` avec le code ; journalisez-les et continuez.

*LangChain : charger et découper des pages*

```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 "")

```

## Comment donner à un agent LangChain un outil read_url ?

Décorez une fonction avec `@tool` de `langchain_core.tools`. La docstring devient la description de l'outil que lit le modèle, alors indiquez ce que l'outil renvoie et quand l'utiliser. Renvoyez du texte brut : une ligne de titre, la source et le Markdown, coupé avec `max_chars` selon votre modèle.

Liez l'outil à n'importe quel chat model qui prend en charge le tool calling, ou passez-le au constructeur de votre agent. Le modèle appelle ensuite `read_url` avec une URL issue de la conversation ou d'un résultat de recherche.

*LangChain : l'outil 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)

```

## Comment faire la même chose dans LlamaIndex ?

LlamaIndex utilise sa propre classe `Document` avec `text` et `metadata`, et `FunctionTool.from_defaults` pour transformer une fonction en outil. Le helper reste le même. Construisez un index à partir des documents avec `VectorStoreIndex.from_documents`, qui utilise le modèle d'embedding nommé dans vos réglages LlamaIndex.

*LlamaIndex : indexer un crawl, et un outil 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)

```

## Quelle taille de crawl faut-il pour un petit index RAG ?

Moins que vous ne le pensez. La documentation d'un produit fait souvent quelques centaines de pages, et un crawl avec `include_paths` réglé sur la section docs ne lit que celles-là. À 1 crédit la page, un site de docs de 300 pages coûte environ 300 crédits ; 10 000 pages (3,75 $US) le rafraîchit de nombreuses fois.

Relancez le crawl selon un rythme qui correspond à la fréquence de modification de la source, et comparez `final_url` et `word_count` avec l'exécution précédente pour ne ré-embedder que les pages qui ont changé. Les crédits prépayés n'expirent jamais : les pages non utilisées restent sur votre compte jusqu'à ce que vous les utilisiez.

## LangChain et LlamaIndex : questions fréquentes

### Existe-t-il un package WebReaderAPI pour LangChain ou LlamaIndex ?

Non. Le helper et l'outil de cette page constituent toute l'intégration, vous pouvez donc les adapter librement. WebReaderAPI est une API HTTPS simple que n'importe quel client HTTP Python peut appeler.

### Quelle version de LangChain faut-il ?

N'importe quelle version récente avec `langchain_core`, `langchain-text-splitters` et `init_chat_model`. Le décorateur d'outil et la classe `Document` sont stables depuis plusieurs versions récentes.

### Comment éviter de relire les pages qui n'ont pas changé ?

Stockez `final_url`, `word_count` et la date de chaque lecture. Relisez selon un rythme planifié, et ne ré-embeddez une page que lorsque son nombre de mots ou son hash de contenu change. Chaque relecture coûte un crédit, choisissez donc un rythme adapté à la source.

### Et si certaines pages sont refusées ?

Elles lèvent `PageError` avec un code tel que `blocked_by_robots`, et elles ne coûtent rien. Si un site refuse notre requête, nous renvoyons sa réponse : nous ne résolvons jamais de CAPTCHA, ne nous connectons pas à des comptes et ne cherchons pas à contourner un blocage ou un paywall.

---

WebReaderAPI: WebReaderAPI est une API de lecture de pages web pour agents IA qui transforme toute page web publique en Markdown propre, payable en crypto sans envoi de pièce d'identité. Les crédits prépayés à partir de 0,07 $US/1K pages n'expirent jamais, et robots.txt est respecté. Sans KYC — pas de pièce d'identité, pas de selfie, pas de documents. Payez en crypto.
