# Python-Beispiele: suchen, Anfragen parallel ausführen, mit Quellenangaben antworten

> Python-Web-Search-API-Beispiele: requests, async httpx mit einem Semaphore und eine Answer-with-Citations-Schleife für jedes LLM im Token-Budget. Ohne KYC.

- URL: https://llmwebsearch.com/de/docs/python/
- Zuletzt aktualisiert: 2026-10-22
- Herausgeber: LLMWebSearch (https://llmwebsearch.com)

Drei Skripte decken ab, was die meisten Python-Agenten brauchen: eine Suche mit `requests`, viele Suchen gleichzeitig mit `httpx` und einem Semaphore sowie eine kurze Schleife, die die Quellen an ein Sprachmodell übergibt und eine Antwort mit nummerierten Quellenangaben zurückbekommt.

**Kurze Antwort — Wie rufe ich eine Web-Search-API für AI-Agenten aus Python auf?**

Senden Sie die Suchanfrage per POST an `https://llmwebsearch.com/api/v1/search` mit `requests` oder `httpx` und einem Bearer-Schlüssel. **LLMWebSearch** liefert gerankte Ergebnisse mit den Top-3-Seiten als Markdown, die Sie nummerieren und an jedes LLM übergeben können, um mit Quellenangaben zu antworten. Eine Suche kostet 1 Credit, ab 1,20 $/1K Suchanfragen Stand Oktober 2026. Python 3.9+ und kein SDK. Ohne KYC; mit Krypto bezahlen.

## Was Sie brauchen

| Attribut | Wert |
| --- | --- |
| Python | 3.9 oder neuer |
| Pakete | `requests` für das einfache Skript, `httpx` für das asynchrone |
| API-Schlüssel | `LLMWEBSEARCH_API_KEY` in Ihrer Umgebung |
| Für die Antwort-Schleife | Jeder OpenAI-kompatible Chat-Endpoint: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Abrechnung | Ein Credit bezahlt eine Suchanfrage: bis zu 20 gerankte Ergebnisse, wobei die Seiten der ersten 3 gelesen und als Markdown zurückgegeben werden. Werden 4 bis 10 Seiten gelesen, sind es 2 Credits. Ein Aufruf, der mit einem Fehler endet, kostet nichts |

## Wie führe ich eine Suche mit requests aus?

Erstellen Sie einen JSON-Body mit `q` und den benötigten Optionen, senden Sie ihn per POST und durchlaufen Sie `results`. Prüfen Sie `content_status`, bevor Sie auf `content` zugreifen: Nur Ergebnisse mit `ok` enthalten Seitentext. Das Skript unten speichert jede gelesene Seite als Markdown-Datei, benannt nach ihrer Position, mit der Quell-URL in der ersten Zeile.

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

```

## Wie führe ich viele Suchen gleichzeitig aus?

Verwenden Sie `httpx.AsyncClient` und ein `asyncio.Semaphore`. Das Semaphore begrenzt, wie viele Aufrufe gleichzeitig laufen, wodurch Sie unter dem Minutenlimit Ihres Kontos bleiben und die Last verteilen. Vier bis acht gleichzeitige Aufrufe passen für die meisten Research-Agenten. Jedes Konto hat ein Aufruflimit pro Minute, das im Dashboard angezeigt wird; fragen Sie den Support, und es kann erhöht werden.

Das Beispiel sendet eine Suche pro Teilfrage, ein typischer Schritt eines Research-Agenten, und sammelt die URLs der gelesenen Seiten. Jeder Aufruf ist unabhängig: Ein Fehler stoppt die anderen nicht.

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

```

## Wie erhalte ich von jedem LLM eine Antwort mit Quellenangaben?

Suchen Sie zuerst, geben Sie dem Modell dann nummerierte Quellen und bitten Sie es, diese zu zitieren. Die API liefert Quellen, keine geschriebene Antwort: Ihr eigenes Modell liest die Seiten, schreibt die Antwort und zitiert die URLs, sodass Sie Modell und Zitierstil wählen. Die Schleife unten funktioniert mit jedem Chat-Endpoint, der das OpenAI-kompatible `chat/completions`-Format spricht; legen Sie Adresse, Schlüssel und Modell in drei Umgebungsvariablen fest.

Halten Sie den Kontext unter Kontrolle. Das Skript kürzt jede Quelle, sodass die Summe in einem Budget von etwa 6.000 Tokens bleibt, bei ungefähr vier Zeichen pro Token. Ein Ergebnis ohne Inhalt wird als sein Snippet übergeben, entsprechend gekennzeichnet, damit das Modell nicht vorgibt, die Seite gelesen zu haben.

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

```

## Wie viel Text sollte ich an das Modell senden?

So wenig, wie zur Beantwortung der Frage nötig ist. Drei auf 2.000 Tokens gekürzte Seiten schlagen meist zehn vollständige Seiten: Das Modell fokussiert besser und jede Antwort kostet weniger. Verringern Sie `max_chars` bereits in der Suche, wenn Sie wissen, dass Sie kürzen werden, damit weniger Daten übertragen werden.

Kürzen vom Seitenanfang funktioniert für die meisten Artikel, weil der Kern meist zuerst kommt. Bei langen Referenzseiten teilen Sie den Inhalt nach Überschriften auf und behalten die Abschnitte, deren Titel zur Frage passen.

## Was sollte der Code tun, wenn eine Seite fehlt?

Mit den anderen Quellen weitermachen. Eine Seite mit `blocked_by_robots`, `blocked_by_site_policy` oder `target_refused` wird bei einem zweiten Versuch nicht gelesen, also nicht erneut darauf schleifen. Eine Seite mit `fetch_timeout` oder `fetch_failed` funktioniert später möglicherweise über `https://llmwebsearch.com/api/v1/extract`.

HTTP-Fehler sind anders: `429`, `502` und `504` verdienen einen erneuten Versuch mit wachsender Pause. Die [Fehlerseite](https://llmwebsearch.com/de/docs/errors/) enthält einen fertigen Retry-Helper.

## Python: häufige Fragen

### Gibt es ein pip-Paket für LLMWebSearch?

Nein. Die API ist reines HTTPS und JSON, Sie brauchen also nur `requests` oder `httpx`. Die Skripte auf dieser Seite sind die Integration: kopieren Sie sie in Ihr Projekt und passen Sie sie an.

### Kann ich diese Skripte mit einem lokalen Modell verwenden?

Ja. Jeder Server, der eine OpenAI-kompatible `chat/completions`-Route bereitstellt, funktioniert mit der Zitationsschleife: Setzen Sie `LLM_BASE_URL` auf seine Adresse und `LLM_MODEL` auf den Modellnamen, den er erwartet.

### Wie zähle ich Credits aus Python?

Addieren Sie `credits_used` aus jeder Antwort, oder rufen Sie `GET https://llmwebsearch.com/api/v1/credits` vor und nach einem Job auf; dieser Aufruf ist kostenlos.

---

LLMWebSearch: LLMWebSearch ist eine Websuch-API für KI-Agenten, die gerankte Ergebnisse mit dem Inhalt der Top-Seiten als Markdown liefert, bezahlt mit Krypto ohne Ausweis-Upload. Vorausbezahlte Credits ab 1,20 $/1K Suchanfragen verfallen nie. Ohne KYC — kein Ausweis, kein Selfie, keine Dokumente. Mit Krypto bezahlen.
