Zum Hauptinhalt springen
Piloterr
Zurück zum Blog
18. August 2026

Strukturierte Daten mit ChatGPT extrahieren, nachdem die Seite geladen ist

Fügen Sie eine Produkt-URL in ChatGPT ein und Sie erhalten oft einen Preis. Öffnen Sie die Live-Seite: die Zahl ist falsch, veraltet oder erfunden. Es handelt sich nicht um einen unzuverlässigen Scraper. Das Modell hat die URL nie geöffnet. Es hat den Wert aus Trainingsdaten abgeleitet und selbstsicher formuliert.

Behandeln Sie ChatGPT als Parser, nicht als Browser. Piloterr lädt die Seite. Versuchen Sie zuerst JSON-LD oder einen CSS-Selektor. Nur der verbleibende Text geht an ein OpenAI-Modell. Pydantic verwirft jedes Objekt, das nicht zum Schema passt.

Die Demonstration nutzt books.toscrape.com, einen öffentlichen Katalog zu Übungszwecken. Bleiben Sie auf öffentlichen Seiten, respektieren Sie robots.txt und lesen Sie Ist Web Scraping legal?, bevor Sie eine kommerzielle Website ansteuern.

In diesem Tutorial:

  1. Eine Produktseite mit Website Crawler laden (1 Credit).
  2. Titel, Preis, Bestand und UPC per CSS lesen, wenn das Markup stabil ist.
  3. Auf Structured Outputs von gpt-5.6-luna wechseln, wenn nicht.
  4. Eine Kategorie auf article.product_pod zerlegen, nicht mitten im Wort.

Zwei Aufgaben, zwei Werkzeuge

Ein Sprachmodell liest Text. Es rendert kein JavaScript, rotiert keine IP-Adressen und löst keinen Cloudflare-Challenge. Bitten Sie es, eine URL abzurufen, und Sie erhalten eine blockierte Seite, eine leere SPA oder ein erfundenes Payload.

Halten Sie die Trennung explizit:

  1. Eine vollständige Seite laden (Piloterr, oder ein Library-Endpoint, wenn es einen gibt).
  2. Felder extrahieren (CSS, JSON-LD oder ein Modell).

ChatGPT eignet sich, wenn das Markup unregelmäßig ist oder sich so oft ändert, dass die Pflege einer großen Selektor-Menge teurer wird als ein Schema. Amazon, LinkedIn und ähnliche Ziele verfügen bereits über gepflegte APIs. Nutzen Sie diese.

Das DOM parsen, bevor Sie das Modell aufrufen

Beginnen Sie mit einem CSS-Extractor. Auf diesem Katalog ist der Titel h1, der Preis p.price_color, die UPC eine Tabellenzeile. Dieser Weg verbraucht weniger Tokens als gpt-5.6-luna und liefert am nächsten Tag dieselbe Zeichenkette.

Python
from bs4 import BeautifulSoup
from pydantic import BaseModel


class Book(BaseModel):
    title: str
    price: str
    availability: str
    upc: str | None = None
    isbn: str | None = None


def extract_book_css(html: str) -> Book | None:
    soup = BeautifulSoup(html, "html.parser")
    title = soup.select_one("article.product_page h1")
    price = soup.select_one("article.product_page p.price_color")
    stock = soup.select_one("article.product_page p.instock.availability")
    if not (title and price and stock):
        return None

    upc = None
    for row in soup.select("article.product_page table tr"):
        label = row.select_one("th")
        value = row.select_one("td")
        if label and value and label.get_text(strip=True) == "UPC":
            upc = value.get_text(strip=True)
            break

    return Book(
        title=title.get_text(strip=True),
        price=price.get_text(strip=True),
        availability=" ".join(stock.get_text().split()),
        upc=upc,
        isbn=None,
    )

isbn bleibt None: diese Seite hat keine ISBN. Bitten Sie das Modell nicht, eine zu erfinden.

Suchen Sie in dieser Reihenfolge:

  • Eine Library-API (Amazon Product und so weiter)
  • application/ld+json auf der Seite
  • Einen stabilen CSS- oder XPath-Knoten
  • Ein Modell für den Rest: unstrukturierte Spezifikationen, FAQ-Blöcke, Texte, die Einheiten und Hinweise mischen

Klare Preisstrings gehen durch Price-Parser ohne LLM.

Python
import json


def json_ld_blocks(html: str) -> list[dict]:
    soup = BeautifulSoup(html, "html.parser")
    blocks = []
    for node in soup.select('script[type="application/ld+json"]'):
        if not node.string:
            continue
        try:
            parsed = json.loads(node.string)
        except json.JSONDecodeError:
            continue
        blocks.extend(parsed if isinstance(parsed, list) else [parsed])
    return blocks

Fetch-Modus wählen, bevor Sie den Prompt schreiben

Ein ungeeigneter Modus liefert leeres HTML. Das Modell «extrahiert» dann von einer Anmeldeseite.

ModusCreditsWann
Website Crawler1Statisches HTML oder JSON-APIs. Am schnellsten. Kein JS.
Website Rendering2Inhalt nach JavaScript. Braucht wait_in_seconds oder wait_for.
Website WebUnlocker3Allowlist-Domains hinter Cloudflare, DataDome, PerimeterX, Akamai. Kein JS.

Vergleich: Crawler vs Rendering vs WebUnlocker. Dieses Tutorial verwendet Crawler, weil books.toscrape.com serverseitig gerendert ist.

HTML laden

Python 3.10+, ein Piloterr-API-Key und ein OpenAI-Key. Beide in der Umgebung. Niemals im Script.

Bash
python --version
mkdir fetch-then-extract && cd fetch-then-extract
pip install requests beautifulsoup4 "openai>=2" pydantic
export PILOTERR_API_KEY="your-x-api-key"
export OPENAI_API_KEY="your-openai-key"

Crawler ist ein GET. query ist die Ziel-URL. Der JSON-Body ist der HTML-String.

Python
import os
import requests

API = "https://api.piloterr.com/v2/website/crawler"
KEY = os.environ["PILOTERR_API_KEY"]
PAGE = "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
CATALOG = "https://books.toscrape.com/"


def fetch_html(url: str, endpoint: str = API, **extra: object) -> str:
    response = requests.get(
        endpoint,
        headers={"x-api-key": KEY},
        params={"query": url, "allow_redirects": "true", **extra},
        timeout=60,
    )
    response.raise_for_status()
    html = response.json()
    if not isinstance(html, str) or len(html) < 200:
        raise RuntimeError("Fetch returned too little HTML. Try Rendering or WebUnlocker.")
    return html

Einige hundert Kilobyte sind erwartet. Einige hundert Zeichen sind oft eine Challenge-Seite. Wechseln Sie den Modus. Bitten Sie das Modell nicht, unvollständiges HTML zu kompensieren.

Für eine stark JavaScript-lastige Produktseite gilt dasselbe Anfragemuster für Rendering (2 Credits):

Python
RENDER = "https://api.piloterr.com/v2/website/rendering"

def fetch_rendered(url: str) -> str:
    return fetch_html(url, endpoint=RENDER, wait_in_seconds=4)

Dokumentation: Website Crawler und Website Rendering. Ein erfolgreicher Aufruf kostet 1 oder 2 Credits. Client-4xx und die meisten Plattform-5xx nicht. Siehe Pricing und die Markdown-Version /pricing.md.

HTML reduzieren, bevor Sie das Modell aufrufen

Scripts, SVG-Pfade und Cookie-Banner verbrauchen Tokens und verdecken die benötigten Felder.

Python
def readable_text(html: str) -> str:
    soup = BeautifulSoup(html, "html.parser")
    for tag in soup(["script", "style", "noscript", "svg", "iframe"]):
        tag.decompose()
    main = soup.select_one("article.product_page") or soup.body or soup
    return main.get_text("\n", strip=True)

Structured Outputs, dann Validierung

Wenn CSS fehlschlägt, wird der Prompt zum Extractor. Benennen Sie jedes Feld. Verwenden Sie null, wenn der Text den Wert nicht enthält. client.responses.parse bindet die Antwort an die Pydantic-Klasse. Ein fehlerhaftes Objekt wird nicht als vertrauenswürdiges Dict behandelt.

Python
from openai import OpenAI

client = OpenAI()

SYSTEM = (
    "Read the page text and fill the schema. "
    "Copy strings as they appear. "
    "Use null when a key is absent. "
    "Do not invent a title, price, UPC, or ISBN."
)


def extract_book_llm(text: str) -> Book:
    response = client.responses.parse(
        model="gpt-5.6-luna",
        input=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": f"Fill the Book schema from this page text.\n\n{text}"},
        ],
        text_format=Book,
        reasoning={"effort": "none"},
    )
    book = response.output_parsed
    if book is None:
        raise ValueError("Model returned no parsed object")
    return book

gpt-5.6-luna reicht zum Kopieren von Feldern. Wechseln Sie zu gpt-5.6 nur, wenn zwei Preise widersprechen oder eine Tabelle falsch gelesen wird. Führen Sie eine Stichprobe von 20 Seiten aus, messen Sie die Fehler, dann wählen Sie das Modell. client.chat.completions.parse funktioniert weiterhin, wenn Sie bei Chat Completions bleiben. Bevorzugen Sie responses.parse mit openai 2.x.

Ein Script

Python
if __name__ == "__main__":
    html = fetch_html(PAGE)
    book = extract_book_css(html)
    if book is None:
        book = extract_book_llm(readable_text(html))
    print(book.model_dump_json(indent=2))

Erwartete Form auf der Produktseite „A Light in the Attic“ (UPC steht auf der Seite, ISBN nicht):

JSON
{
  "title": "A Light in the Attic",
  "price": "£51.77",
  "availability": "In stock (22 available)",
  "upc": "a897fe39b1053632",
  "isbn": null
}

Eine halluzinierte ISBN kann in einer Tabelle stehen, ohne Alarm auszulösen, bis eine Bestellung darauf aufsetzt.

Lange Seiten: an Produktkarten schneiden

Eine gesamte Kategorie in einen Prompt zu senden überschreitet das Kontextfenster. Schneiden Sie nicht bei 12 000 Zeichen mitten im Wort. Parsen Sie die Liste, dann eine Karte.

Python
def product_cards(html: str) -> list[str]:
    soup = BeautifulSoup(html, "html.parser")
    return [str(card) for card in soup.select("article.product_pod")]


def extract_catalog(html: str) -> list[Book]:
    books: list[Book] = []
    seen: set[str] = set()
    for card_html in product_cards(html):
        book = extract_book_css(card_html) or extract_book_llm(readable_text(card_html))
        key = book.title.casefold()
        if key in seen:
            continue
        seen.add(key)
        books.append(book)
    return books

Listing-Karten haben keine UPC-Tabelle. CSS liefert None, das Modell füllt Titel und Preis aus dem Kartentext. Führen Sie über den Titel zusammen, oder behalten Sie den href, wenn Sie ihn gespeichert haben.

Python
print(extract_catalog(fetch_html(CATALOG)))

Den Agenten laden lassen, dieselbe Trennung beibehalten

HTML in die ChatGPT-Oberfläche einzufügen eignet sich für eine Demonstration. Tägliche Ausführungen erfolgen in Python, oder in einem Tool, das der Agent aufruft.

Piloterr MCP stellt Crawler und Rendering unter https://mcp.piloterr.com/ mit demselben x-api-key bereit. Claude und Cursor können eine Seite laden. Sie validieren das JSON weiterhin in Ihrem Code. Spezifikationshinweise: Piloterr MCP am 2026-07-28.

Praktiken, die Sie vermeiden sollten

  • ChatGPT nach einer Live-URL fragen und der Antwort vertrauen.
  • Rohes HTML mit Werbung und Scripts mitsenden.
  • Modell-Output ohne Pydantic-Prüfung (oder gleichwertig) speichern.
  • Rendering auf einem statischen Katalog nutzen und 2 Credits ohne Bedarf abrechnen.
  • Hinter einer Anmeldung scrapen oder personenbezogene Daten sammeln, mit der Begründung, das Modell «brauche Kontext».

FAQ

Kann ChatGPT eine Website allein scrapen?

Nein. Es gibt keinen Browser, den Sie steuern, und keine zuverlässige Möglichkeit, die URL zu laden, die Sie gerade eingegeben haben. Das Modell kann Text umschreiben, den Sie bereits abgerufen haben. Laden Sie mit Website Crawler oder Website Rendering, dann extrahieren Sie.

Warum nicht direkt requests.get aufrufen?

Auf books.toscrape.com können Sie das. Auf einem JavaScript-Shop oder einer Cloudflare-geschützten Seite erhalten Sie einen leeren Root oder eine Challenge. Das Modell schreibt trotzdem Felder daraus. Piloterr liefert die Seite, die ein Browser anzeigen würde, und Sie zahlen nur bei erfolgreichem Fetch.

Wann ist Rendering der passende Fetch?

Wenn Preis oder Liste in «Quelltext anzeigen» fehlen, in den DevTools nach der Hydration aber vorhanden sind. Warten Sie mit wait_in_seconds oder wait_for. Steht das HTML bereits in der ersten Antwort, bleiben Sie bei Crawler.

Wie verhindern Sie erfundene Preise?

Zuerst CSS oder JSON-LD. Dann wortgetreue Anweisung, reasoning={"effort": "none"}, Structured Outputs und Pydantic. Vergleichen Sie den Preisstring mit Price-Parser. Schlägt die Validierung fehl: einmal erneut versuchen oder ein Selektor. Füllen Sie keinen fehlenden Preis.

ChatGPT oder eine Library-API?

Library-API, wenn die Site bereits abgedeckt ist. ChatGPT bei einer Ad-hoc-Seite oder einem instabilen Template. Beides kombinieren ist sinnvoll: Fetch mit Crawler, CSS parsen, Modell nur auf den Feldern, die fehlgeschlagen sind.

Was, wenn die Liste für einen Modell-Aufruf zu lang ist?

Pro article oder pro Zeile extrahieren. Auf Titel oder URL deduplizieren. Nicht am Zeichenindex schneiden, außer die Seite hat kein Listen-Markup.

Ist dieses Vorgehen zulässig?

Öffentliche Seiten, angemessene Rate, keine Anmeldung, keine personenbezogenen Daten, und die AGB der Site gelten trotzdem. Beginnen Sie mit Ist Web Scraping legal?. Für kommerzielle Weiterverwendung: Lizenz oder offizieller Feed, wenn es einen gibt.

Weitere Artikel

Anleitungen und Neuigkeiten zu Web Scraping, Proxys und Datenextraktion.

News

Piloterr MCP läuft auf der Spec 2026-07-28

Unser gehosteter MCP unter mcp.piloterr.com folgt MCP 2026-07-28: zustandsloser Request/Response-Kern, dieselbe Streamable-HTTP-URL, derselbe x-api-key.

Josselin Liebe
Josselin Liebe
Lesen
Web Scraping

Web Scraping für Automotive: Fahrzeug- und Teilepreise auf AutoScout24, Mobile.de, Leboncoin und Autodoc verfolgen

Wie Automotive-Pricing-Plattformen Scraping-APIs nutzen, um Gebrauchtwagen-Inserate auf AutoScout24, Mobile.de und Leboncoin sowie Teilepreise in über 30 nationalen Autodoc-Shops zu überwachen. Pipeline-Design, API-Beispiele und ein Python-Skript.

Josselin Liebe
Josselin Liebe
Lesen
Web Scraping

Web Scraping für Brand Protection: Fälschungsjagd auf AliExpress, Alibaba und Amazon

Wie Brand-Protection-Plattformen Scraping-APIs nutzen, um gefälschte Angebote zu erkennen, Verkäufer-Beweise zu sichern und Wiedereinstellungen auf AliExpress, Alibaba, Amazon und anderen Marktplätzen zu überwachen. Pipeline-Design, API-Beispiele und ein Python-Skript.

Josselin Liebe
Josselin Liebe
Lesen

Bereit loszulegen?

Ihre Web-Scraping-API ist nur einen Klick entfernt. Starten Sie mit +500 Credits, ohne Infrastruktur einrichten zu müssen, ohne Proxys zu verwalten und ohne Kreditkarte.

  • +500 Credits
  • Keine Kreditkarte erforderlich
  • Alle Endpunkte enthalten