Warum das Fetch-Tool deines KI-Agenten mit einer leeren Seite zurückkommt
Leere Hülle, Cookie-Wall, Login-Redirect oder abgeschnittene Seite: wie du sie in 30 Zeilen Python auseinanderhältst, mit echten Messungen vom 19. Oktober 2026.

„Ich konnte die Seite nicht lesen” ist beim Modell ein einziger Satz, und darunter stecken mindestens fünf verschiedene Probleme. Die Lösung hängt komplett davon ab, welches du erwischt hast, also stell zuerst die Diagnose.
Warum sieht das Fetch-Tool eines Agenten nichts Brauchbares?
Die meisten eingebauten Fetch-Tools machen dasselbe: ein einfacher HTTP-GET, dann HTML in Text umgewandelt, dann ein Längenlimit. Kein JavaScript läuft, keine Cookies werden behalten, niemand klickt auf „Akzeptieren”. Anthropics eigene Dokumentation zum Web-Fetch-Tool sagt, Stand Oktober 2026, dass es keine Websites unterstützt, die dynamisch mit JavaScript gerendert werden, und dass Inhalte über max_content_tokens abgeschnitten werden.
Fünf Situationen fallen für das Modell also in dasselbe „nichts Brauchbares” zusammen:
| Fall | Was der Server geschickt hat | Signal, das du prüfen kannst |
|---|---|---|
| Clientseitig gerenderte App | Eine HTML-Hülle, Inhalt wird später per Skript aufgebaut | Unter 500 Zeichen sichtbarer Text, ein <noscript>, das nach JavaScript fragt, oder HTML-Bytes weit größer als der Text |
| Consent-Wall | Eine Cookie- oder Datenschutz-Zwischenseite | Redirect auf einen consent.-Host, kurze Seite voller „cookie” und „consent” |
| Login-Wall | Ein Anmeldeformular | Redirect auf /login oder /signin, ein Passwortfeld |
| Blockiert | Ein Fehler oder eine Challenge | 401, 403, 429 oder 503 |
| Zu lang | Die vollständige Seite | Sichtbarer Text größer als das Budget des Tools |
Warum Rendering mehr kostet und wie du es von Hand erkennst, steht in JavaScript-Rendering beim Web-Scraping. In diesem Beitrag geht es darum, die Prüfung im Code zu machen, innerhalb des Agenten.
Eine Diagnosefunktion zum Ausführen
Einfaches requests und BeautifulSoup, kein API-Key, keine Kosten:
import re
import requests
from bs4 import BeautifulSoup
HEADERS = {"User-Agent": "page-diagnostic/1.0", "Accept": "text/html"}
CONSENT = re.compile(r"cookie|consent|gdpr|privacy choices", re.I)
LOGIN = re.compile(r"/(login|log-in|signin|sign-in|auth)\b", re.I)
def diagnose(url, budget=12_000):
r = requests.get(url, headers=HEADERS, timeout=30)
soup = BeautifulSoup(r.text, "html.parser")
noscript = " ".join(n.get_text(" ", strip=True) for n in soup.find_all("noscript"))
for tag in soup(["script", "style", "noscript", "template"]):
tag.decompose()
text = soup.get_text(" ", strip=True)
if r.status_code in (401, 403, 429, 503):
verdict = "blocked"
elif LOGIN.search(r.url) or soup.find("input", {"type": "password"}):
verdict = "login_wall"
elif "consent" in r.url or (len(text) < 3_000 and CONSENT.search(text)):
verdict = "consent_wall"
elif len(text) < 500 and ("javascript" in noscript.lower() or len(r.content) > 50 * len(text)):
verdict = "client_rendered"
elif len(text) > budget:
verdict = "too_long"
else:
verdict = "ok"
return {"status": r.status_code, "final_url": r.url, "html_bytes": len(r.content),
"text_chars": len(text), "verdict": verdict, "text": text}
Die Schwellenwerte sind Heuristiken. Stimm sie auf deinen eigenen Traffic ab, aber die Reihenfolge zählt: Prüf erst die billigen, eindeutigen Signale (Status, URL), dann die unscharfen (Textlänge).
Was sie auf vier öffentlichen Seiten gemeldet hat
Ausgeführt am 19. Oktober 2026 von einem Rechner in Italien:
| URL | Status | HTML-Bytes | Sichtbarer Text | Verdict |
|---|---|---|---|---|
| python.org/about/ | 200 | 41.239 | 4.598 Zeichen | ok |
| crates.io/crates/serde | 200 | 5.406 | 32 Zeichen | client_rendered |
| it.yahoo.com | 200, weitergeleitet auf consent.yahoo.com | 112.226 | 2.471 Zeichen | consent_wall |
| en.wikipedia.org/wiki/World_War_II | 200 | 2.074.721 | 196.628 Zeichen | too_long |
Jede lieferte 200. Eine reine Statuscode-Prüfung hätte alle vier als Erfolg gewertet. crates.io schickt eine 5-KB-Hülle, deren einziger Text der Titel und eine <noscript>-Zeile ist, die nach JavaScript fragt. Die italienische Yahoo-Startseite leitet Besucher ohne Cookies auf eine Consent-Seite weiter; uk.yahoo.com, auf dieselbe Weise abgerufen, tat das nicht.
Was machst du mit welchem Verdict?
ok: Nimm den einfachen Fetch. Python.org hat nichts gekostet und nichts weiter gebraucht.
client_rendered: Render die Seite. Die Web-Scraping-API von FastAF macht das mit dynamic=true für 5 Credits statt 1, oder mit render_js: true im scrape_page-Tool des MCP. Auf crates.io lieferte ein statischer Aufruf 0 Zeichen Markdown, ein gerenderter 4.939. Sei aber ehrlich zu dir, was die Schwankung angeht: Wir haben am 19. Oktober sechs gerenderte Aufrufe auf diese Seite gemacht, vier kamen mit 2.313 bis 4.939 Zeichen zurück und zwei leer, weil die App noch nicht fertig geladen hatte. Diese zwei wurden damals abgerechnet; nachdem wir das für diesen Beitrag gemessen hatten, haben wir die Regel geändert, und ein leeres Ergebnis liefert jetzt 422 target_unavailable und kostet nichts. Ein Render kann trotzdem eine dünne Hülle zurückgeben, also prüf die Textlänge und setz wait (0 bis 35.000 ms) für langsame Apps.
consent_wall: Rendering hilft nicht. Auch der Fetch von FastAF auf it.yahoo.com bekam die Consent-Seite (3.697 Zeichen, alles Datenschutzhinweis). Niemand sollte in einem automatisierten Lauf im Namen einer Website Bedingungen akzeptieren. Melde es, oder hol die Information aus einer Quelle, die dafür gemacht ist, etwa einer News-Suche.
login_wall und blocked: Stopp und sag es. Inhalte hinter einem Login sind nicht öffentlich, und Bot-Schutz ist die Entscheidung des Seitenbetreibers. FastAF liefert nur öffentliche Seiten aus: Ein Request auf https://example.com/login liefert 400 blocked_target in 64 ms und kostet nichts. Die Regeln stehen in der Acceptable Use Policy.
too_long: Fordere Markdown mit einem Budget an. Das MCP-Tool scrape_page lieferte auf der Wikipedia-Seite die ersten 12.000 von 597.765 Markdown-Zeichen mit truncated: true, für 1 Credit in 1,3 Sekunden. Genau darum geht es bei dem Flag: Das Modell weiß, dass es einen Ausschnitt gelesen hat, nicht die ganze Seite.
Eine Fetch-Strategie für Agenten
Erst der billige Weg, Rendering nur, wenn die Diagnose danach verlangt, und ein hartes Limit pro Lauf:
import os, time, requests
def read_page(url, state, max_chars=12_000):
d = diagnose(url, budget=max_chars)
if d["verdict"] in ("blocked", "login_wall", "consent_wall"):
return {"error": f"{d['verdict']}: page not readable, tell the user"}
if d["verdict"] in ("ok", "too_long"):
t = d["text"]
return {"text": t[:max_chars], "truncated": len(t) > max_chars}
if state["renders_left"] <= 0:
return {"error": "page needs rendering, render budget for this run is spent"}
for _ in range(3):
r = requests.get("https://api.fastaf.co/v1/scrape",
params={"url": url, "formats": "markdown", "dynamic": "true", "wait": 3000},
headers={"Authorization": f"Bearer {os.environ['FASTAF_API_KEY']}"}, timeout=90)
if r.status_code != 429:
break
time.sleep(int(r.headers.get("Retry-After", 3)))
if r.status_code != 200:
return {"error": r.json().get("error", {}).get("code", "scrape_failed")}
state["renders_left"] -= 1
if len(r.text) < 500:
return {"error": "rendered page still empty, tell the user"}
return {"text": r.text[:max_chars], "truncated": len(r.text) > max_chars}
Unser letzter Lauf am 19. Oktober, mit renders_left auf 1, kostete insgesamt 5 Credits. Python.org und Wikipedia kamen aus dem kostenlosen einfachen Fetch (Wikipedia auf 12.000 von 196.628 Zeichen gekürzt und markiert), crates.io/crates/serde wurde in 6,3 Sekunden zu 2.508 Zeichen gerendert, crates.io/crates/tokio bekam „budget spent” und Yahoo consent_wall. Fehler kommen als Tool-Ergebnisse zurück, damit das Modell dem Nutzer sagen kann, was passiert ist, statt zu raten. Alle Parameter stehen in den Docs.
Kurzversion
Ein leerer Fetch ist ein Symptom, keine Diagnose. Prüf Status, Redirect-Ziel, Länge des sichtbaren Texts und <noscript>, bevor du irgendetwas ausgibst. Einfacher Fetch für statische Seiten, Rendering für 5 Credits nur bei clientseitig gerenderten öffentlichen Seiten, ein Render-Limit pro Lauf und ein ehrliches „Das kann ich nicht lesen” bei Consent-Walls, Logins und Blockaden. Abgeschnittene Seiten brauchen ein Budget und ein Flag, keinen größeren Kontext. Hol dir 500 kostenlose Credits und probier die Diagnose an den Seiten aus, an denen dein Agent immer wieder scheitert.