# Ejemplos en Python: buscar, lanzar consultas en paralelo, responder con citas

> Ejemplos de API de búsqueda web en Python: requests, httpx asíncrono con semáforo y bucle de respuesta con citas para cualquier LLM. Sin KYC.

- URL: https://llmwebsearch.com/es/docs/python/
- Última actualización: 2026-10-22
- Editor: LLMWebSearch (https://llmwebsearch.com)

Tres scripts cubren lo que necesitan la mayoría de los agentes en Python: una búsqueda con `requests`, muchas búsquedas a la vez con `httpx` y un semáforo, y un bucle corto que entrega las fuentes a un modelo de lenguaje y recibe una respuesta con citas numeradas.

**Respuesta rápida — ¿Cómo llamo a una API de búsqueda web para agentes de IA desde Python?**

Haz un POST de la consulta a `https://llmwebsearch.com/api/v1/search` con `requests` o `httpx` y una clave Bearer. **LLMWebSearch** devuelve resultados ordenados con las 3 mejores páginas en Markdown, que puedes numerar y pasar a cualquier LLM para que responda con citas. Una búsqueda cuesta 1 crédito, desde 1,20 US$/1K búsquedas en octubre de 2026. Python 3.9+ y sin SDK. Sin KYC; paga con criptomonedas.

## Lo que necesitas

| Atributo | Valor |
| --- | --- |
| Python | 3.9 o superior |
| Paquetes | `requests` para el script simple, `httpx` para el asíncrono |
| Clave | `LLMWEBSEARCH_API_KEY` en tu entorno |
| Para el bucle de respuestas | Cualquier endpoint de chat compatible con OpenAI: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Facturación | Un crédito paga una búsqueda: hasta 20 resultados clasificados, con las páginas de los 3 primeros leídas y devueltas como Markdown. Leer de 4 a 10 páginas pasa a 2 créditos. Una llamada que termina en error no cuesta nada |

## ¿Cómo hago una búsqueda con requests?

Construye un cuerpo JSON con `q` y las opciones que necesites, haz el POST y recorre `results`. Revisa `content_status` antes de tocar `content`: solo los resultados marcados con `ok` llevan el texto de la página. El script de abajo guarda cada página leída como un archivo Markdown con el nombre de su posición, y la URL de origen en la primera línea.

*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"])

```

## ¿Cómo hago muchas búsquedas a la vez?

Usa `httpx.AsyncClient` y un `asyncio.Semaphore`. El semáforo limita cuántas llamadas hay en curso, lo que te mantiene por debajo del límite por minuto de tu cuenta y reparte la carga. De cuatro a ocho llamadas simultáneas van bien para la mayoría de los agentes de investigación. Cada cuenta tiene un límite de llamadas por minuto, que se muestra en el panel; pídelo a soporte y se puede aumentar.

El ejemplo envía una búsqueda por subpregunta, un paso típico de un agente de investigación, y recoge las URLs de las páginas que se leyeron. Cada llamada es independiente: un fallo no detiene a las demás.

*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())

```

## ¿Cómo obtengo una respuesta con citas de cualquier LLM?

Primero busca y después da al modelo fuentes numeradas y pídele que las cite. La API devuelve fuentes, no una respuesta redactada: tu propio modelo lee las páginas, escribe la respuesta y cita las URL, así eliges tú el modelo y el estilo de cita. El bucle de abajo funciona con cualquier endpoint de chat que hable el formato `chat/completions` compatible con OpenAI; configura su dirección, su clave y su modelo en tres variables de entorno.

Mantén el contexto bajo control. El script recorta cada fuente para que el total quede dentro de un presupuesto de unos 6,000 tokens, contando aproximadamente cuatro caracteres por token. Un resultado sin contenido se pasa como su snippet, etiquetado como tal, para que el modelo no finja que leyó la página.

*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?"))

```

## ¿Cuánto texto debería enviar al modelo?

El mínimo que responda a la pregunta. Tres páginas recortadas a 2,000 tokens cada una suelen superar a diez páginas completas: el modelo se centra mejor y cada respuesta cuesta menos. Baja `max_chars` en la propia búsqueda cuando sepas que vas a recortar, para que viajen menos datos.

Recortar desde el principio de la página funciona para la mayoría de los artículos, porque lo principal suele ir primero. Para páginas de referencia largas, divide el contenido por encabezados y quédate con las secciones cuyos títulos coincidan con la pregunta.

## ¿Qué debe hacer el código cuando falta una página?

Sigue adelante con las otras fuentes. Una página marcada con `blocked_by_robots`, `blocked_by_site_policy` o `target_refused` no se leerá en un segundo intento, así que no insistas en bucle. Una página `fetch_timeout` o `fetch_failed` puede funcionar más tarde con `https://llmwebsearch.com/api/v1/extract`.

Los errores HTTP son distintos: `429`, `502` y `504` merecen un reintento con una pausa creciente. La [página de errores](https://llmwebsearch.com/es/docs/errors/) tiene un helper de reintento listo.

## Python: preguntas frecuentes

### ¿Hay un paquete pip para LLMWebSearch?

No. La API es HTTPS y JSON sin más, así que con `requests` o `httpx` tienes suficiente. Los scripts de esta página son la integración: cópialos en tu proyecto y adáptalos.

### ¿Puedo usar estos scripts con un modelo local?

Sí. Cualquier servidor que exponga una ruta `chat/completions` compatible con OpenAI funciona con el bucle de citas: configura `LLM_BASE_URL` con su dirección y `LLM_MODEL` con el nombre del modelo que espera.

### ¿Cómo cuento los créditos desde Python?

Suma `credits_used` de cada respuesta, o llama a `GET https://llmwebsearch.com/api/v1/credits` antes y después de un trabajo; esa llamada es gratuita.

---

LLMWebSearch: LLMWebSearch es una API de búsqueda web para agentes de IA que devuelve resultados clasificados con el contenido de las mejores páginas en Markdown, se paga con criptomonedas y sin subir documentos de identidad. Los créditos prepago desde 1,20 US$/1K búsquedas nunca caducan. Sin KYC: sin ID, sin selfie, sin documentos. Paga con criptomonedas.
