# Exemples Python : rechercher, lancer des requêtes en parallèle, répondre avec citations

> API de recherche web en Python : requests, httpx en asynchrone avec un sémaphore, et une boucle réponse-avec-citations pour tout LLM dans un budget de tokens.

- URL: https://llmwebsearch.com/fr/docs/python/
- Dernière mise à jour: 2026-10-22
- Éditeur: LLMWebSearch (https://llmwebsearch.com)

Trois scripts couvrent l'essentiel des besoins des agents Python : une recherche avec `requests`, plusieurs recherches à la fois avec `httpx` et un sémaphore, et une courte boucle qui transmet les sources à un modèle de langage et récupère une réponse avec des citations numérotées.

**Réponse rapide — Comment appeler une API de recherche web pour agents IA depuis Python ?**

Envoyez la requête en POST à `https://llmwebsearch.com/api/v1/search` avec `requests` ou `httpx` et une clé Bearer. **LLMWebSearch** renvoie des résultats classés avec les 3 premières pages en Markdown, que vous pouvez numéroter et transmettre à n'importe quel LLM pour répondre avec citations. Une recherche coûte 1 crédit, à partir de 1,20 $US/1K requêtes en octobre 2026. Python 3.9+ et aucun SDK. Sans KYC ; payez en crypto.

## Ce qu'il vous faut

| Attribut | Valeur |
| --- | --- |
| Python | 3.9 ou plus récent |
| Packages | `requests` pour le script simple, `httpx` pour la version asynchrone |
| Clé | `LLMWEBSEARCH_API_KEY` dans votre environnement |
| Pour la boucle de réponse | Tout endpoint de chat compatible OpenAI : `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Facturation | Un crédit paie une recherche : jusqu'à 20 résultats classés, avec les pages des 3 premiers lues et renvoyées en Markdown. Lire 4 à 10 pages coûte 2 crédits. Un appel qui se termine sur une erreur ne coûte rien |

## Comment lancer une seule recherche avec requests ?

Construisez un corps JSON avec `q` et les options voulues, envoyez-le en POST, puis parcourez `results`. Vérifiez `content_status` avant de toucher à `content` : seuls les résultats marqués `ok` contiennent le texte de la page. Le script ci-dessous enregistre chaque page lue dans un fichier Markdown nommé d'après sa position, avec l'URL source sur la première ligne.

*search_once.py*

```python
import os
import pathlib

import requests

resp = requests.post(
    "https://llmwebsearch.com/api/v1/search",
    headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
    json={"q": "how to size a postgres connection pool", "max_results": 5, "read": 3},
    timeout=60,
)
resp.raise_for_status()
data = resp.json()
out = pathlib.Path("sources")
out.mkdir(exist_ok=True)
for r in data["results"]:
    if r["content_status"] != "ok":
        print("skip", r["position"], r["content_status"], r["url"])
        continue
    path = out / f"{r['position']:02d}.md"
    path.write_text("Source: " + r["url"] + "\n\n" + r["content"], encoding="utf-8")
    print("saved", path, r["word_count"], "words")
print("credits used:", data["credits_used"])

```

## Comment lancer plusieurs recherches à la fois ?

Utilisez `httpx.AsyncClient` et un `asyncio.Semaphore`. Le sémaphore limite le nombre d'appels en cours, ce qui vous garde sous la limite par minute de votre compte et répartit la charge. Quatre à huit appels simultanés conviennent à la plupart des agents de recherche. Chaque compte a une limite d'appels par minute, affichée dans le dashboard ; demandez au support et elle peut être augmentée.

L'exemple envoie une recherche par sous-question, une étape typique d'un agent de recherche, et collecte les URL des pages qui ont été lues. Chaque appel est indépendant : un échec n'arrête pas les autres.

*many_searches.py*

```python
import asyncio
import os

import httpx

API = "https://llmwebsearch.com/api/v1/search"
HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
QUESTIONS = [
    "what is connection pooling in postgres",
    "recommended pool size per cpu core",
    "pgbouncer transaction mode limitations",
    "connection pool exhaustion symptoms",
]


async def search(client, sem, q):
    async with sem:
        resp = await client.post(API, json={"q": q, "read": 2, "max_chars": 4000})
        if resp.status_code != 200:
            return q, resp.json().get("error", {}).get("code"), []
        data = resp.json()
        read = [r["url"] for r in data["results"] if r["content_status"] == "ok"]
        return q, "ok", read


async def main():
    sem = asyncio.Semaphore(4)
    async with httpx.AsyncClient(headers=HEADERS, timeout=60) as client:
        for q, status, urls in await asyncio.gather(*(search(client, sem, q) for q in QUESTIONS)):
            print(status, q, urls)


asyncio.run(main())

```

## Comment obtenir une réponse avec citations de n'importe quel LLM ?

Cherchez d'abord, puis donnez au modèle des sources numérotées et demandez-lui de les citer. L'API renvoie des sources, pas une réponse rédigée : c'est votre propre modèle qui lit les pages, rédige la réponse et cite les URLs, vous choisissez donc le modèle et le style de citation. La boucle ci-dessous fonctionne avec tout endpoint de chat qui parle le format `chat/completions` compatible OpenAI ; indiquez son adresse, sa clé et son modèle dans trois variables d'environnement.

Gardez le contexte sous contrôle. Le script tronque chaque source pour rester dans un budget d'environ 6 000 tokens, en comptant à peu près quatre caractères par token. Un résultat sans contenu est transmis sous forme de snippet, signalé comme tel, pour que le modèle ne prétende pas avoir lu la page.

*answer_with_citations.py*

```python
import os

import requests

SEARCH = "https://llmwebsearch.com/api/v1/search"
KEY = os.environ["LLMWEBSEARCH_API_KEY"]
BUDGET_CHARS = 6000 * 4  # about 6,000 tokens of sources


def sources_for(question):
    resp = requests.post(SEARCH, headers={"Authorization": "Bearer " + KEY},
                         json={"q": question, "max_results": 5, "read": 3}, timeout=60)
    resp.raise_for_status()
    results = resp.json()["results"]
    share = BUDGET_CHARS // max(len(results), 1)
    blocks = []
    for i, r in enumerate(results, start=1):
        if r["content_status"] == "ok":
            body = r["content"][:share]
        else:
            body = "(snippet only) " + r["snippet"]
        blocks.append(f"[{i}] {r['title']} - {r['url']}\n{body}")
    return "\n\n".join(blocks)


def answer(question):
    prompt = ("Answer the question using only the sources below. "
              "Cite them as [1], [2] and so on. Say so if the sources do not answer it.\n\n"
              + sources_for(question) + "\n\nQuestion: " + question)
    resp = requests.post(
        os.environ["LLM_BASE_URL"].rstrip("/") + "/chat/completions",
        headers={"Authorization": "Bearer " + os.environ["LLM_API_KEY"]},
        json={"model": os.environ["LLM_MODEL"], "messages": [{"role": "user", "content": prompt}]},
        timeout=120,
    )
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]


print(answer("How should I size a Postgres connection pool?"))

```

## Combien de texte dois-je envoyer au modèle ?

Le moins possible pour répondre à la question. Trois pages tronquées à 2 000 tokens chacune donnent généralement de meilleurs résultats que dix pages complètes : le modèle se concentre mieux et chaque réponse coûte moins cher. Baissez `max_chars` dans la recherche elle-même quand vous savez que vous allez tronquer, pour que moins de données circulent.

Tronquer depuis le début d'une page fonctionne pour la plupart des articles, car l'idée principale vient généralement en premier. Pour les longues pages de référence, découpez le contenu par titres et gardez les sections dont le titre correspond à la question.

## Que doit faire le code quand une page manque ?

Continuez avec les autres sources. Une page marquée `blocked_by_robots`, `blocked_by_site_policy` ou `target_refused` ne sera pas lue lors d'une nouvelle tentative : ne bouclez donc pas dessus. Une page `fetch_timeout` ou `fetch_failed` peut fonctionner plus tard via `https://llmwebsearch.com/api/v1/extract`.

Les erreurs HTTP sont différentes : `429`, `502` et `504` méritent une nouvelle tentative avec une pause croissante. La [page des erreurs](https://llmwebsearch.com/fr/docs/errors/) propose un helper de retry prêt à l'emploi.

## Python : questions fréquentes

### Existe-t-il un package pip pour LLMWebSearch ?

Non. L'API est du HTTPS et du JSON classiques, donc `requests` ou `httpx` suffit. Les scripts de cette page constituent l'intégration : copiez-les dans votre projet et adaptez-les.

### Puis-je utiliser ces scripts avec un modèle local ?

Oui. Tout serveur qui expose une route `chat/completions` compatible OpenAI fonctionne avec la boucle de citation : définissez `LLM_BASE_URL` sur son adresse et `LLM_MODEL` sur le nom de modèle qu'il attend.

### Comment compter les crédits depuis Python ?

Additionnez `credits_used` de chaque réponse, ou appelez `GET https://llmwebsearch.com/api/v1/credits` avant et après une tâche ; cet appel est gratuit.

---

LLMWebSearch: LLMWebSearch est une API de recherche web pour agents IA qui renvoie des résultats classés avec le contenu des meilleures pages en Markdown, payée en crypto sans envoi de pièce d'identité. Les crédits prépayés à partir de 1,20 $US/1K requêtes n'expirent jamais. Sans KYC — pas de pièce d'identité, pas de selfie, pas de documents. Payez en crypto.
