AutoPodAutoPod

Messung des AI-Zitierungseffekts: Analytik, Protokolle und Panel-Scrapes

•22 Min. Lesezeit
Audio-Artikel
Messung des AI-Zitierungseffekts: Analytik, Protokolle und Panel-Scrapes
0:00—:—
Messung des AI-Zitierungseffekts: Analytik, Protokolle und Panel-Scrapes

Messung des AI-Zitierungseffekts: Analytik, Protokolle und Panel-Scrapes

Einführung

KI-gesteuerte Entdeckungen verteilen sich mittlerweile auf mehrere Messschichten. Eine Person sieht möglicherweise ein in einer KI-Antwort zitiertes Unternehmen, klickt auf einen Link ohne nützlichen Referrer, besucht die Seite über einen Browser, der Kampagnendaten entfernt, oder liest eine Antwort, ohne überhaupt zu klicken. Gleichzeitig kann ein KI-Crawler die Seite des Unternehmens anfordern, ohne einen menschlichen Besucher darzustellen.

Ein nützliches Messsystem muss daher vier Fragen trennen:

  1. Wurde die Seite gecrawlt oder abgerufen?
  2. Wurde die Seite in einer KI-Antwort zitiert?
  3. Hat eine Person die Seite über diese Antwort besucht?
  4. Hat dieser Besuch ein sinnvolles Engagement oder Umsatz generiert?

Stand 22. September 2026 verbessert sich die Erstanbieter-Berichterstattung. Die Google Search Console bietet einen Performance-Bericht für generative KI für AI Overviews und AI-Modus, einschließlich Impressionen, Seiten, Ländern, Geräten und Daten. Der Bericht wurde am 31. August 2026 weltweit verfügbar. Googles Performance-Bericht für generative KI (support.google.com)

Microsoft bietet ebenfalls einen AI-Leistungsbericht in den Bing Webmaster Tools an. Die öffentliche Vorschau berichtet über Gesamtzitate, zitierte Seiten, unterstützende Abfragen und Zitationstrends über unterstützte Microsoft KI-Erlebnisse hinweg. Bing Webmaster Tools AI-Leistung (blogs.bing.com)

Diese Tools sollten für ihre eigenen Oberflächen als maßgeblich angesehen werden, sie bilden jedoch kein vollständiges, assistentenübergreifendes Messsystem. Der empfohlene Ansatz ist die Kombination von Erstanbieter-Berichten, Server-Logs, kontrollierten Tracking-Links, Analyseexporten und geplanten Beobachtungen von Antwortpanels.


1. Warum die Messung von KI-Verweisen schwierig ist

Traditionelle Webanalyse hängt stark vom Referer-Header und Kampagnenparametern ab. Beides kann fehlen oder unvollständig sein.

Der HTTP-Referer-Header kann die vorherige Seite, nur den Ursprung der vorherigen Website oder überhaupt keinen Wert enthalten. Browser-Referrer-Richtlinien können diese Informationen absichtlich einschränken oder entfernen. MDN Referrer-Policy Dokumentation (developer.mozilla.org)

KI-Systeme schaffen zusätzliche Komplikationen:

  • Ein Assistent kann auf eine Seite verlinken, ohne einen erkennbaren Referrer zu senden.
  • Ein Assistent kann eine umgeleitete oder umgeschriebene URL verwenden.
  • Ein Assistent kann Inhalte serverseitig abrufen, bevor er eine Antwort anzeigt.
  • Ein KI-Crawler kann eine Seite besuchen, ohne dass ein Mensch sie sieht.
  • Die Antwort kann eine kanonische URL zitieren, anstatt der ursprünglich abgerufenen URL.
  • Die Antwort kann je nach Standort, Gerät, Sprache, Zeit oder Personalisierung variieren.

Beispielsweise unterscheidet OpenAI zwischen OAI-SearchBot für die Sucherkennung, GPTBot für mögliche Trainingszwecke und ChatGPT-User für vom Benutzer initiierte Aktionen. Perplexity identifiziert separat PerplexityBot und Perplexity-User. Anthropic identifiziert ClaudeBot, Claude-SearchBot und Claude-User. Diese Anfragen sollten nicht zu einer generischen „AI-Traffic“-Zahl zusammengefasst werden. OpenAI Crawler-Dokumentation (openai.com) Perplexity Crawler-Dokumentation (docs.perplexity.ai) Anthropic Crawler-Dokumentation (support.anthropic.com)

Die zentrale Regel lautet:

Crawler-Aktivität ist kein Verweis-Traffic, die Sichtbarkeit von Zitaten ist kein Traffic, und Traffic ist kein Geschäftserfolg.


2. Das Mess-Framework

Verwenden Sie fünf Messschichten.

EbeneHauptfrageEmpfohlene Daten
ZugriffHat ein KI-System die Seite angefordert?Webserver-Logs, Firewall-Logs
SichtbarkeitWurde die Seite angezeigt oder zitiert?Google Search Console, Bing Webmaster Tools, Beobachtungen von Antwortpanels
AkquisitionIst eine Person von einer KI-Oberfläche gekommen?Referrer, Kampagnenparameter, Kurzlink-Klicks, Analysen
EngagementHat der Besucher sinnvoll interagiert?Analyseereignisse, Seitenaufrufe, Engagement-Zeit
WertHat der Besuch ein gewünschtes Ergebnis erzielt?Anmeldungen, Leads, Käufe, Abonnements

Empfohlene Hierarchie der Wahrheitsquellen

Verwenden Sie für jede Frage das stärkste verfügbare Signal:

  1. Erstanbieter-Plattformberichte für Impressionen und Zitationen.
  2. Antwortpanel-Beobachtungen für Sichtbarkeit auf Prompt-Ebene und Wettbewerbsvergleiche.
  3. Weiterleitungs-Logs für kontrollierte Link-Klicks.
  4. Webanalyse für Sitzungen, Engagement und Konversionen.
  5. Server-Logs für Crawler- und Abrufaktivität.
  6. Modellierte Schätzungen nur, wenn keine direkten Beweise vorliegen.

Fassen Sie nicht alle Signale zu einer einzigen Zahl zusammen, ohne die Quelle und das Konfidenzniveau beizubehalten.


3. Kern-Datenmodell

Erstellen Sie ein Data Warehouse mit separaten Tabellen für Rohbeobachtungen und abgeleitete Metriken.

panel_runs

Eine Zeile für jeden Prompt- und Oberflächen-Check.

text run_id observed_at surface topic prompt_id query_text locale country device panel_present panel_type answer_hash raw_html_path screenshot_path collector_version status

panel_citations

Eine Zeile für jeden in einem Antwortpanel beobachteten Link.

text run_id surface topic prompt_id cited_url cited_domain citation_position link_scope target_domain target_cited

ai_crawler_requests

Aggregierte Server-Log-Daten.

text date bot_name bot_role path requests successful_requests error_requests unique_ip_count

shortener_clicks

Eine Zeile für jede Anfrage über einen kontrollierten Link.

text click_id occurred_at slug surface topic prompt_id asset_id referrer_host user_agent_family ip_hmac destination_path

ai_sessions

Eine normalisierte Analysetabelle.

text session_id date surface topic prompt_id landing_page attribution_method confidence engaged engagement_time_seconds key_event revenue

Das Feld confidence ist wichtig. Empfohlene Werte sind:

  • A — Direkt: Kontrollierter Kurzlink-Klick oder vertrauenswürdiger Kampagnenparameter.
  • B — Stark: Bekannter Assistenten-Referrer oder verifizierte Benutzer-Abrufanfrage.
  • C — Wahrscheinlich: Kampagnen- oder Landingpage-Nachweise ohne vollständigen Referrer.
  • D — Modelliert: Abgeleitet aus Timing, Panel-Beobachtungen oder aggregierten Änderungen.

Berichten Sie A und B getrennt von C und D.


4. Metrikdefinitionen

Zitationsanteil nach Thema

Der Zitationsanteil nach Thema misst, wie oft eine Zieldomäne unter den für ein Thema beobachteten Quellen erscheint.

[ \text{Zitationsanteil}_{Thema}

\frac{\text{Vorkommen von Zitationen der Zieldomäne}} {\text{Alle Zitationsvorkommen}} ]

Beispiel:

  • 100 gesamte Zitationen für „Projektmanagement-Software“ beobachtet
  • 12 Zitationen verweisen auf Ihre Domain
  • Zitationsanteil nach Thema = 12%

Diese Metrik misst den Quellenanteil, nicht, ob eine Antwort Ihre Website mindestens einmal enthielt.

Zitationsabdeckung

Verwenden Sie eine zweite Metrik für die Präsenz auf Antwortebene:

[ \text{Zitationsabdeckung}_{Thema}

\frac{\text{Antwort-Durchläufe mit einer Zielzitation}} {\text{Antwort-Durchläufe, bei denen ein Panel vorhanden war}} ]

Die Zitationsabdeckung ist für nicht-technisches Publikum oft leichter verständlich:

„Unsere Website erschien in 34% der beobachteten KI-Antworten zu diesem Thema.“

Zitationsposition

Erfassen Sie die Position einer Zitation innerhalb der Antwort:

  • 1 = erste beobachtete Quelle
  • 2 = zweite beobachtete Quelle
  • 3 = dritte beobachtete Quelle

Da KI-Oberflächen Quellen oft gruppieren oder neu anordnen, behandeln Sie die Position als deskriptive Beobachtung, nicht als universelles Ranking.


Assistenten-Verweisrate

Die Assistenten-Verweisrate misst den Anteil der menschlichen Website-Sitzungen, die einer KI-Oberfläche zugeordnet werden können.

[ \text{Assistenten-Verweisrate}

\frac{\text{KI-zugeordnete menschliche Sitzungen}} {\text{Alle menschlichen Sitzungen}} ]

Berichten Sie mindestens zwei Versionen:

text Bestätigte Assistenten-Verweisrate = KI-Sitzungen mit A-Konfidenz / alle menschlichen Sitzungen

Erweiterte Assistenten-Verweisrate = KI-Sitzungen mit A + B + C-Konfidenz / alle menschlichen Sitzungen

Die bestätigte Version ist konservativer. Die erweiterte Version ist nützlich für Trendanalysen, sollte aber nicht als vollständig beobachteter Traffic dargestellt werden.

Panel-Durchläufe nicht als Impressionen verwenden

Wenn Sie 500 Prompts scrapen, bedeutet das nicht, dass 500 Personen diese Antworten gesehen haben. Ihr Scraper misst eine kontrollierte Stichprobe von Antwortzuständen. Verwenden Sie Erstanbieter-Plattform-Impressionsberichte, wenn diese verfügbar sind.


Engagement nach Zitation

Das Engagement nach Zitation misst, ob Personen, die über eine KI-Zitation kamen, eine sinnvolle Aktion durchgeführt haben.

[ \text{Engagement nach Zitation}

\frac{\text{Engagierte KI-Sitzungen}} {\text{KI-zugeordnete Sitzungen}} ]

Für Google Analytics 4 dauert eine engagierte Sitzung länger als 10 Sekunden, beinhaltet ein Schlüsselereignis oder mindestens zwei Seiten- oder Bildschirmansichten. Google Analytics Dokumentation zur Engagement-Rate (support.google.com)

Berichten Sie auch:

text Engagement-Zeit nach Zitation = mittlere engagierte Sekunden pro KI-Sitzung

Konversionsrate nach Zitation = KI-Sitzungen mit einem Schlüsselereignis / KI-zugeordnete Sitzungen

Umsatz pro Sitzung nach Zitation = Umsatz aus KI-Sitzungen / KI-zugeordnete Sitzungen

Die mittlere Engagement-Zeit ist in der Regel stabiler als die durchschnittliche Engagement-Zeit, da eine kleine Anzahl sehr langer Sitzungen den Durchschnitt verzerren kann.


Zusätzliche nützliche Metriken

Erfolgsrate des KI-Crawlers

[ \text{Crawler-Erfolgsrate}

\frac{\text{KI-Crawler-Anfragen, die 2xx oder 3xx zurückgeben}} {\text{KI-Crawler-Anfragen}} ]

Schlüsseln Sie dies nach Bot und Pfad auf. Ein starker Rückgang der Zitationen in Kombination mit einer steigenden Crawler-Fehlerrate kann auf ein Hosting-, Firewall- oder Robots-Konfigurationsproblem hinweisen.

Zitations-Klickrate

Für kontrollierte Beobachtungen von Antwortpanels:

[ \text{Beobachtete Zitations-Klickrate}

\frac{\text{Bestätigte Kurzlink-Klicks}} {\text{Beobachtete Zielzitationen}} ]

Dies ist keine echte Benutzer-Klickrate, es sei denn, der Nenner repräsentiert tatsächliche Benutzerimpressionen. Kennzeichnen Sie es deutlich als beobachtete operative Metrik.

Zitationsaktualität

Messen Sie, wie oft die zitierte URL die aktuelle Seitenversion widerspiegelt.

text Zitationsaktualität = zitierte Seiten mit aktuellem Content-Hash / zitierte Seiten

Dies ist nützlich für Dokumentations-, Preis-, Produkt- und Richtlinienseiten, die sich häufig ändern.


5. Server-Log-Analyse

Strukturierte Zugriffsprotokolle konfigurieren

Strukturierte Protokolle sind einfacher zu analysieren als herkömmliche, durch Leerzeichen getrennte Protokolle.

Beispiel einer Nginx-Konfiguration:

nginx log_format ai_json escape=json '{' '"ts":"$time_iso8601",' '"remote_addr":"$remote_addr",' '"request":"$request",' '"status":$status,' '"bytes":$body_bytes_sent,' '"referer":"$http_referer",' '"user_agent":"$http_user_agent",' '"host":"$host"' '}';

access_log /var/log/nginx/ai-access.jsonl ai_json;

Speichern Sie die Protokolldateien an einem eingeschränkten Ort. Senden Sie keine rohen IP-Adressen an eine allgemeine Analysetabelle, es sei denn, Sie haben einen klaren operativen Grund.

AI-Anfragetypen identifizieren

Trennen Sie mindestens:

text OpenAI:

  • OAI-SearchBot
  • ChatGPT-User
  • GPTBot

Anthropic:

  • Claude-SearchBot
  • Claude-User
  • ClaudeBot

Perplexity:

  • PerplexityBot
  • Perplexity-User

Suchmaschinen:

  • Googlebot
  • Bingbot

Googles KI-Suchfunktionen verwenden im Allgemeinen bestehende Google Search Crawling-Steuerungen anstelle eines separaten universellen „AI Overview Bot“. Google gibt an, dass Googlebot den Zugriff für die Suche, einschließlich KI-Funktionen, steuert. Google AI-Funktionen und Websites (developers.google.com)

User-Agent-Strings können gefälscht werden. Für operative Entscheidungen kombinieren Sie die User-Agent-Übereinstimmung mit vom Anbieter veröffentlichten IP-Bereichen oder verifizierten Reverse-DNS, wo verfügbar. OpenAI und Perplexity veröffentlichen IP-Informationen für ihre relevanten Crawler und Fetcher. (openai.com)

Reproduzierbarer Log-Summarizer

Speichern Sie Folgendes als ai_log_summary.py.

python #!/usr/bin/env python3

import csv import hashlib import hmac import json import os import re import sys from collections import Counter, defaultdict from datetime import datetime from urllib.parse import urlsplit

BOT_PATTERNS = { "OAI-SearchBot": ("openai", re.compile(r"\bOAI-SearchBot\b", re.I)), "ChatGPT-User": ("openai_user", re.compile(r"\bChatGPT-User\b", re.I)), "GPTBot": ("openai_training", re.compile(r"\bGPTBot\b", re.I)), "Claude-SearchBot": ("anthropic", re.compile(r"\bClaude-SearchBot\b", re.I)), "Claude-User": ("anthropic_user", re.compile(r"\bClaude-User\b", re.I)), "ClaudeBot": ("anthropic_training", re.compile(r"\bClaudeBot\b", re.I)), "PerplexityBot": ("perplexity", re.compile(r"\bPerplexityBot\b", re.I)), "Perplexity-User": ("perplexity_user", re.compile(r"\bPerplexity-User\b", re.I)), "Googlebot": ("google_search", re.compile(r"\bGooglebot\b", re.I)), "Bingbot": ("bing_search", re.compile(r"\bbingbot\b", re.I)), }

SECRET = os.environ.get("HMAC_SECRET", "replace-this-in-production").encode()

def hmac_ip(ip): return hmac.new(SECRET, ip.encode(), hashlib.sha256).hexdigest()[:16]

def classify_bot(user_agent): for bot_name, (role, pattern) in BOT_PATTERNS.items(): if pattern.search(user_agent or ""): return bot_name, role return None, None

def parse_path(request_line): try: parts = request_line.split() return urlsplit(parts[1]).path except Exception: return "/"

def main(input_path): totals = defaultdict(lambda: { "requests": 0, "success": 0, "errors": 0, "ips": set(), "paths": Counter(), })

with open(input_path, "r", encoding="utf-8") as source:
    for line in source:
        try:
            record = json.loads(line)
        except json.JSONDecodeError:
            continue

        bot_name, bot_role = classify_bot(record.get("user_agent", ""))
        if not bot_name:
            continue

        timestamp = record.get("ts", "")
        date = datetime.fromisoformat(
            timestamp.replace("Z", "+00:00")
        ).date().isoformat()

        status = int(record.get("status", 0))
        path = parse_path(record.get("request", ""))
        key = (date, bot_name, bot_role)

        totals[key]["requests"] += 1
        totals[key]["ips"].add(hmac_ip(record.get("remote_addr", "")))
        totals[key]["paths"][path] += 1

        if 200 <= status < 400:
            totals[key]["success"] += 1
        else:
            totals[key]["errors"] += 1

writer = csv.writer(sys.stdout)
writer.writerow([
    "date",
    "bot_name",
    "bot_role",
    "requests",
    "successful_requests",
    "error_requests",
    "unique_ip_count",
    "top_paths",
])

for (date, bot_name, bot_role), values in sorted(totals.items()):
    top_paths = values["paths"].most_common(10)
    writer.writerow([
        date,
        bot_name,
        bot_role,
        values["requests"],
        values["success"],
        values["errors"],
        len(values["ips"]),
        json.dumps(top_paths),
    ])

if name == "main": if len(sys.argv) != 2: print("Usage: python ai_log_summary.py /path/to/access.jsonl") sys.exit(1)

main(sys.argv[1])

Führen Sie es aus mit:

bash HMAC_SECRET='a-long-random-secret'
python ai_log_summary.py /var/log/nginx/ai-access.jsonl \

data/ai_crawler_daily.csv

Dies liefert operative Nachweise über die Abrufaktivität, nicht über menschlichen Traffic.


6. Benutzerdefinierte Kurzlinks für AI-bezogene Zusammenfassungen

Ein Erstanbieter-Link-Shortener bietet einen kontrollierten Messpunkt, wenn Sie Zusammenfassungen oder Quellmaterialien veröffentlichen, die von KI-Systemen referenziert werden könnten.

Verwenden Sie eine Domain, die Sie kontrollieren, wie z.B.:

text https://go.example.com/ai-docs

Das Ziel sollte Kampagnenparameter enthalten, wie z.B.:

text utm_source=perplexity utm_medium=ai_answer utm_campaign=product_documentation utm_content=pricing_summary_v3

Google empfiehlt Kampagnenparameter zur Identifizierung von Traffic-Quellen und Kampagnen. Google Analytics Dokumentation zu Kampagnen-URLs (support.google.com)

Empfohlene Link-Felder

text surface topic prompt_id asset_id content_version destination_path

Fügen Sie keine persönlichen Informationen, E-Mail-Adressen oder Benutzersuchanfragen in den Link ein. Kampagnen-Identifikatoren sollten das veröffentlichte Asset beschreiben, nicht eine Person identifizieren.

Wichtige Einschränkungen

Ein Kurzlink ist ein Kalibrierungsgerät, keine Universallösung:

  • Der Assistent kann die Ziel-URL umschreiben.
  • Der Assistent kann stattdessen die kanonische URL zitieren.
  • Der Link kann angezeigt, aber nie angeklickt werden.
  • Ein Browser oder Datenschutztool kann den Referrer entfernen.
  • Einige Systeme können das Ziel serverseitig abrufen.
  • Ein Kurzlink misst keine Benutzer, die sich an die Marke erinnern und später zurückkehren.

Verwenden Sie keinen Shortener, um das Ziel zu verbergen, Sicherheitssysteme zu umgehen oder Zitationen zu manipulieren. Verwenden Sie eine klare Weiterleitung zu einer relevanten, kanonischen Seite.

Minimaler Erstanbieter-Shortener

Das folgende Beispiel verwendet FastAPI und SQLite. Schützen Sie den Link-Erstellungsendpunkt mit Authentifizierung, bevor Sie ihn in der Produktion verwenden.

python

shortener.py

import hashlib import hmac import os import secrets import sqlite3 from datetime import datetime, timezone from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit

from fastapi import FastAPI, HTTPException, Request from fastapi.responses import RedirectResponse from pydantic import BaseModel

DB_PATH = os.environ.get("SHORTENER_DB", "shortener.db") HMAC_SECRET = os.environ.get( "HMAC_SECRET", "replace-this-in-production" ).encode()

ALLOWED_HOST = "www.example.com"

app = FastAPI()

def db(): connection = sqlite3.connect(DB_PATH) connection.row_factory = sqlite3.Row return connection

def init_db(): connection = db() connection.execute(""" CREATE TABLE IF NOT EXISTS links ( slug TEXT PRIMARY KEY, destination TEXT NOT NULL, surface TEXT NOT NULL, topic TEXT, prompt_id TEXT, asset_id TEXT, created_at TEXT NOT NULL ) """) connection.execute(""" CREATE TABLE IF NOT EXISTS clicks ( id INTEGER PRIMARY KEY AUTOINCREMENT, occurred_at TEXT NOT NULL, slug TEXT NOT NULL, surface TEXT, topic TEXT, prompt_id TEXT, referrer_host TEXT, user_agent TEXT, ip_hmac TEXT ) """) connection.commit() connection.close()

def append_query(url, params): parsed = urlsplit(url) query = dict(parse_qsl(parsed.query, keep_blank_values=True)) query.update(params) return urlunsplit(( parsed.scheme, parsed.netloc, parsed.path, urlencode(query), parsed.fragment, ))

def hash_ip(ip): return hmac.new( HMAC_SECRET, (ip or "").encode(), hashlib.sha256 ).hexdigest()[:16]

class LinkRequest(BaseModel): destination_path: str surface: str topic: str | None = None prompt_id: str | None = None asset_id: str | None = None

@app.on_event("startup") def startup(): init_db()

@app.post("/links") def create_link(payload: LinkRequest): destination = f"https://{ALLOWED_HOST}{payload.destination_path}"

if not payload.destination_path.startswith("/"):
    raise HTTPException(400, "destination_path must be a site-relative path")

slug = secrets.token_urlsafe(7).replace("_", "-").replace("/", "-")

target = append_query(destination, {
    "utm_source": payload.surface,
    "utm_medium": "ai_answer",
    "utm_campaign": "citation_measurement",
    "utm_content": payload.asset_id or "unspecified",
})

connection = db()
connection.execute("""
    INSERT INTO links
    (slug, destination, surface, topic, prompt_id, asset_id, created_at)
    VALUES (?, ?, ?, ?, ?, ?, ?)
""", (
    slug,
    target,
    payload.surface,
    payload.topic,
    payload.prompt_id,
    payload.asset_id,
    datetime.now(timezone.utc).isoformat(),
))
connection.commit()
connection.close()

return {"slug": slug, "url": f"https://go.example.com/{slug}"}

@app.get("/{slug}") def redirect(slug: str, request: Request): connection = db() row = connection.execute( "SELECT * FROM links WHERE slug = ?", (slug,) ).fetchone()

if not row:
    connection.close()
    raise HTTPException(404, "Unknown link")

referrer = request.headers.get("referer", "")
referrer_host = urlsplit(referrer).hostname or ""

connection.execute("""
    INSERT INTO clicks
    (occurred_at, slug, surface, topic, prompt_id,
     referrer_host, user_agent, ip_hmac)
    VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", (
    datetime.now(timezone.utc).isoformat(),
    slug,
    row["surface"],
    row["topic"],
    row["prompt_id"],
    referrer_host,
    request.headers.get("user-agent", "")[:300],
    hash_ip(request.client.host if request.client else ""),
))
connection.commit()
connection.close()

return RedirectResponse(row["destination"], status_code=302)

Führen Sie es aus mit:

bash pip install fastapi uvicorn uvicorn shortener:app --host 0.0.0.0 --port 8080

Verwenden Sie eine 302-Weiterleitung für Messlinks, damit Browser und Zwischensysteme die Weiterleitung weniger wahrscheinlich dauerhaft cachen.


7. Geplantes Scraping von AI-Antwortpanels

Das Scraping von Panels ist nützlich, um Fragen zu beantworten, die Erstanbieter-Analysen möglicherweise nicht offenlegen:

  • Welche konkurrierenden Quellen werden zitiert?
  • Welche Seite wird für einen bestimmten Prompt zitiert?
  • Ändert sich die Antwort nach Land oder Gerät?
  • Erscheint die Zitation in der Antwort, in der Quellenliste oder in beiden?
  • Wie stabil ist die Antwort über die Zeit?
  • Zitiert der Assistent einen Kurzlink oder die kanonische URL?

Das Scraping muss jedoch sorgfältig erfolgen.

Regeln für verantwortungsvolle Datenerfassung

  1. Bevorzugen Sie eine offizielle API, einen lizenzierten Suchdatenanbieter oder einen Erstanbieter-Bericht.
  2. Überprüfen Sie die Bedingungen des Anbieters, bevor Sie Anfragen automatisieren.
  3. Beachten Sie gegebenenfalls Robots-Anweisungen.
  4. Umgehen Sie keine CAPTCHAs, Anmeldesperren, Ratenbegrenzungen oder Bot-Schutzmaßnahmen.
  5. Verwenden Sie einen kleinen, festen Prompt-Satz.
  6. Führen Sie mit geringer Frequenz aus.
  7. Speichern Sie die Antwort und den Zeitstempel, damit die Ergebnisse überprüft werden können.
  8. Stoppen Sie, wenn ein Anbieter eine Herausforderung oder eine Zugriffsverweigerung zurückgibt.

Das Robots Exclusion Protocol ist ein Präferenzmechanismus für Crawler, kein Sicherheitssystem. Es autorisiert keinen Zugriff auf geschützte Inhalte. RFC 9309: Robots Exclusion Protocol (rfc-editor.org)

Prompt-Manifest erstellen

csv prompt_id,topic,query,locale,surface,target_domain pm_001,project-management,"best project management software for remote teams",en-US,google,example.com pm_001,project-management,"best project management software for remote teams",en-US,bing,example.com pm_002,analytics,"how to measure AI referral traffic",en-US,google,example.com

Halten Sie Prompts stabil. Wenn sich Prompts ändern, werden Trendvergleiche weniger zuverlässig.

Speichern Sie auch:

  • Land
  • Sprache
  • Gerätetyp
  • Browserversion
  • Prompt-Version
  • Inhaltsversion
  • Erfassungszeit in UTC

Google weist darauf hin, dass die Antworten von AI Overview und AI-Modus variieren können, da sie unterschiedliche Systeme verwenden und verwandte Suchanfragen oder „Query Fan-out“ nutzen können. Google AI-Funktionen und Website-Dokumentation (developers.google.com)

Playwright Sammlungsskript

Das folgende Skript ist ein allgemeines Forschungstool. Es zeichnet die gerenderte Seite, einen Screenshot, Seitenlinks und den Antworttext auf. Es umgeht absichtlich keine Herausforderungen oder versucht, Zugriffskontrollen zu überwinden.

python

scrape_panels.py

import asyncio import csv import hashlib import json import os import re from datetime import datetime, timezone from pathlib import Path from urllib.parse import quote_plus, urljoin, urlsplit

from playwright.async_api import async_playwright

DATA_DIR = Path("data/panels") DATA_DIR.mkdir(parents=True, exist_ok=True)

SURFACE_URLS = { "google": "https://www.google.com/search?q={query}&hl=en&gl=us", "bing": "https://www.bing.com/search?q={query}&setlang=en-us", }

PANEL_MARKERS = { "google": ["AI Overview", "AI Mode"], "bing": ["Copilot", "Generative search"], }

BLOCK_MARKERS = [ "captcha", "unusual traffic", "verify you are human", "access denied", ]

def clean_text(value): return re.sub(r"\s+", " ", value or "").strip()

def host_matches(url, target_domain): host = (urlsplit(url).hostname or "").lower() target = target_domain.lower().lstrip("www.") return host == target or host.endswith("." + target)

def page_hash(text): normalized = clean_text(text).encode("utf-8") return hashlib.sha256(normalized).hexdigest()

async def collect_one(page, row): surface = row["surface"] query = row["query"] target_domain = row["target_domain"]

search_url = SURFACE_URLS[surface].format(
    query=quote_plus(query)
)

await page.goto(
    search_url,
    wait_until="domcontentloaded",
    timeout=45_000,
)
await page.wait_for_timeout(2500)

text = await page.locator("body").inner_text()
lowered = text.lower()

if any(marker in lowered for marker in BLOCK_MARKERS):
    return {
        **row,
        "observed_at": datetime.now(timezone.utc).isoformat(),
        "status": "blocked_or_challenged",
        "panel_present": False,
        "links": [],
    }

links = await page.locator("a").evaluate_all("""
    anchors => anchors.map(a => ({
        href: a.href || "",
        text: (a.innerText || "").trim()
    })).filter(x => x.href)
""")

panel_present = any(
    marker.lower() in lowered
    for marker in PANEL_MARKERS.get(surface, [])
)

observed_at = datetime.now(timezone.utc)
run_id = hashlib.sha256(
    f"{surface}|{row['prompt_id']}|{observed_at.isoformat()}".encode()
).hexdigest()[:20]

html_path = DATA_DIR / f"{run_id}.html"
screenshot_path = DATA_DIR / f"{run_id}.png"

await page.screenshot(path=str(screenshot_path), full_page=True)
html_path.write_text(await page.content(), encoding="utf-8")

output_links = []
for position, link in enumerate(links, start=1):
    output_links.append({
        "href": urljoin(search_url, link["href"]),
        "text": clean_text(link["text"])[:300],
        "position": position,
        "target_cited": host_matches(
            link["href"],
            target_domain
        ),
    })

return {
    **row,
    "run_id": run_id,
    "observed_at": observed_at.isoformat(),
    "status": "ok",
    "panel_present": panel_present,
    "answer_hash": page_hash(text),
    "answer_text": clean_text(text)[:50_000],
    "html_path": str(html_path),
    "screenshot_path": str(screenshot_path),
    "link_scope": "page_fallback",
    "links": output_links,
}

async def main(manifest_path): rows = [] with open(manifest_path, newline="", encoding="utf-8") as source: rows = list(csv.DictReader(source))

output_path = DATA_DIR / (
    datetime.now(timezone.utc).strftime("%Y-%m-%d") + ".jsonl"
)

async with async_playwright() as playwright:
    browser = await playwright.chromium.launch(headless=True)
    context = await browser.new_context(
        locale="en-US",
        timezone_id="UTC",
    )
    page = await context.new_page()

    with open(output_path, "w", encoding="utf-8") as output:
        for row in rows:
            try:
                result = await collect_one(page, row)
            except Exception as exc:
                result = {
                    **row,
                    "observed_at": datetime.now(timezone.utc).isoformat(),
                    "status": f"error:{type(exc).__name__}",
                    "panel_present": False,
                    "links": [],
                }

            output.write(json.dumps(result) + "\n")
            output.flush()

            # Keep request volume low.
            await asyncio.sleep(8)

    await browser.close()

if name == "main": asyncio.run(main("prompts.csv"))

Installieren und ausführen:

bash pip install playwright playwright install chromium python scrape_panels.py

Planen Sie es mit einer Sperre, damit sich zwei Ausführungen nicht überschneiden können:

cron 15 3 * * * flock -n /tmp/ai-panel.lock
/usr/bin/python3 /opt/ai-measure/scrape_panels.py

Genauigkeit mit Oberflächenadaptern verbessern

Das Fallback-Skript zeichnet Links auf Seitenebene auf. Das ist nützlich für die Entdeckung, kann aber auch gewöhnliche Suchergebnis-Links enthalten. Für den Produktionseinsatz:

  • Verwenden Sie eine lizenzierte Antwortpanel-API, wenn verfügbar.
  • Erstellen Sie einen separaten Selektor-Adapter für jede Oberfläche.
  • Speichern Sie die Selektor-Version.
  • Speichern Sie rohes HTML und einen Screenshot.
  • Markieren Sie jeden Datensatz mit link_scope = "panel_selector" oder link_scope = "page_fallback".

Berechnen Sie den Zitationsanteil nur aus panel_selector-Daten, wenn Sie das Panel zuverlässig identifizieren können. Halten Sie Fallback-Beobachtungen in einem separaten explorativen Bericht.


8. Google Analytics und BigQuery Integration

Google Analytics 4 kann Rohereignisdaten nach BigQuery exportieren. Tägliche Exporte bieten stabile Tabellen wie events_JJJJMMTT; Streaming-Exporte sind nützlich für die nahezu Echtzeit-Überwachung, können aber unvollständig sein, bis die tägliche Tabelle finalisiert ist. Google Analytics BigQuery-Export (support.google.com)

Eine vereinfachte Sitzungsabfrage könnte so aussehen:

sql WITH page_events AS ( SELECT event_date, user_pseudo_id,

(
  SELECT value.int_value
  FROM UNNEST(event_params)
  WHERE key = 'ga_session_id'
) AS session_id,

(
  SELECT value.string_value
  FROM UNNEST(event_params)
  WHERE key = 'page_location'
) AS page_location,

(
  SELECT value.string_value
  FROM UNNEST(event_params)
  WHERE key = 'session_engaged'
) AS session_engaged,

event_name

FROM PROJECT.analytics_PROPERTY.events_* WHERE _TABLE_SUFFIX BETWEEN '20260901' AND '20260921' ),

session_rollup AS ( SELECT event_date, user_pseudo_id, session_id,

MAX(
  CASE
    WHEN REGEXP_CONTAINS(
      LOWER(page_location),
      r'[?&]utm_medium=ai_answer'
    )
    THEN 1 ELSE 0
  END
) AS ai_session,

MAX(
  CASE
    WHEN session_engaged = '1'
    THEN 1 ELSE 0
  END
) AS engaged_session,

MAX(
  CASE
    WHEN event_name IN ('purchase', 'generate_lead', 'sign_up')
    THEN 1 ELSE 0
  END
) AS key_event

FROM page_events GROUP BY event_date, user_pseudo_id, session_id )

SELECT event_date, COUNTIF(ai_session = 1) AS ai_sessions, COUNTIF(ai_session = 1 AND engaged_session = 1) AS engaged_ai_sessions, COUNTIF(ai_session = 1 AND key_event = 1) AS ai_key_events, SAFE_DIVIDE( COUNTIF(ai_session = 1 AND engaged_session = 1), COUNTIF(ai_session = 1) ) AS post_citation_engagement_rate FROM session_rollup GROUP BY event_date ORDER BY event_date;

Für eine ausgereifte Implementierung fügen Sie surface, topic und prompt_id zur Kampagnenstruktur hinzu oder verknüpfen Sie sie aus der Kurzlink-Klick-Tabelle.

Erstellen Sie keine hochkardinalen Analysedimensionen für jeden einzelnen Benutzer oder jede Sitzung. Google warnt, dass übermäßige hochkardinale benutzerdefinierte Dimensionen die Berichtsqualität beeinträchtigen können. Google Analytics benutzerdefinierte Dimensionen und Metriken (support.google.com)


9. Dashboard-Design

Ein praktisches Dashboard sollte fünf Ansichten enthalten.

Management Summary

Anzeige:

  • Zitationsanteil nach Thema
  • Zitationsabdeckung nach Thema
  • Bestätigte Assistenten-Verweisrate
  • Erweiterte Assistenten-Verweisrate
  • Engagement nach Zitation
  • Erfolgsrate des KI-Crawlers
  • KI-zugeordnete Schlüsselereignisse oder Einnahmen

Trend des Zitationsanteils

Liniendiagramm:

text Datum × Thema × Oberfläche

Zeigen Sie beides:

  • Vorkommen von Zitationen der Zieldomäne
  • Alle beobachteten Zitationsvorkommen

Traffic-Trichter der Assistenten

text Beobachtete Zitationen ↓ Kontrollierte Link-Klicks ↓ KI-zugeordnete Sitzungen ↓ Engagierte KI-Sitzungen ↓ Schlüsselereignisse

Jede Stufe sollte ihr Attributionskonfidenz anzeigen.

Crawler-Zustand

Tabelle nach Bot:

text bot_name requests successful_requests error_rate unique_ip_count top_paths

Alarmieren Sie, wenn:

  • die Fehlerrate 2 Prozent überschreitet
  • ein Bot plötzlich verschwindet
  • ein zuvor zugänglicher Inhaltsbereich 403 oder 429 zurückgibt
  • Crawler-Anfragen sich auf einen unbeabsichtigten privaten Pfad konzentrieren

Content-Chancen-Tabelle

Seiten und Themen nach Rang ordnen durch:

text hoher Zitationsanteil + niedrige Verweisrate hohe Impressionen + geringe Zitationsabdeckung hohes Engagement + geringe Zitationssichtbarkeit hohe Crawler-Fehler + hohe geschäftliche Bedeutung

Diese Kombinationen führen in der Regel zu besseren Entscheidungen, als nur das Traffic-Volumen zu betrachten.

Minimales Streamlit-Dashboard

Speichern Sie als dashboard.py.

python import glob import json from pathlib import Path

import pandas as pd import streamlit as st

st.set_page_config( page_title="AI Citation Impact", layout="wide", )

@st.cache_data(ttl=300) def load_panel_rows(): rows = []

for filename in glob.glob("data/panels/*.jsonl"):
    with open(filename, encoding="utf-8") as source:
        for line in source:
            record = json.loads(line)

            run_id = record.get("run_id")
            for link in record.get("links", []):
                hostname = link.get("href", "").split("/")[2]
                rows.append({
                    "run_id": run_id,
                    "observed_at": record.get("observed_at"),
                    "surface": record.get("surface"),
                    "topic": record.get("topic"),
                    "prompt_id": record.get("prompt_id"),
                    "panel_present": record.get("panel_present"),
                    "target_domain": record.get("target_domain"),
                    "cited_domain": hostname,
                    "target_cited": link.get("target_cited", False),
                    "position": link.get("position"),
                    "link_scope": record.get("link_scope"),
                })

return pd.DataFrame(rows)

panels = load_panel_rows()

st.title("Dashboard für KI-Zitationsauswirkungen")

if panels.empty: st.warning("Keine Panel-Beobachtungen gefunden.") st.stop()

panels["date"] = pd.to_datetime( panels["observed_at"], errors="coerce" ).dt.date

citation_share = ( panels.groupby("topic") .apply( lambda group: group["target_cited"].sum() / max(len(group), 1) ) .reset_index(name="citation_share") )

coverage = ( panels.groupby(["topic", "run_id"], dropna=False) .agg( panel_present=("panel_present", "max"), target_cited=("target_cited", "max"), ) .reset_index() )

coverage = ( coverage[coverage["panel_present"] == True] .groupby("topic") .apply(lambda group: group["target_cited"].mean()) .reset_index(name="citation_coverage") )

col1, col2 = st.columns(2)

with col1: st.subheader("Zitationsanteil nach Thema") st.dataframe(citation_share, use_container_width=True) st.bar_chart( citation_share.set_index("topic")["citation_share"] )

with col2: st.subheader("Zitationsabdeckung nach Thema") st.dataframe(coverage, use_container_width=True) st.bar_chart( coverage.set_index("topic")["citation_coverage"] )

st.subheader("Zitationsposition") st.dataframe( panels.groupby(["topic", "surface"], dropna=False) .agg( citations=("target_cited", "sum"), median_position=("position", "median"), observations=("run_id", "nunique"), ) .reset_index(), use_container_width=True, )

st.subheader("Tägliche Beobachtungen") daily = ( panels.groupby(["date", "surface"], dropna=False) .agg( citations=("target_cited", "sum"), observed_links=("cited_domain", "count"), ) .reset_index() )

st.line_chart( daily.pivot( index="date", columns="surface", values="citations" ).fillna(0) )

Führen Sie es aus mit:

bash pip install streamlit pandas streamlit run dashboard.py

Für die Produktion laden Sie normalisierte Tabellen in DuckDB, BigQuery, Snowflake oder PostgreSQL, anstatt Rohdateien direkt zu lesen.


10. Qualitätskontrollen

Ein Messsystem kann präzise aussehende, aber irreführende Zahlen produzieren. Fügen Sie diese Kontrollen hinzu.

Bots von Menschen trennen

Zählen Sie niemals OAI-SearchBot, GPTBot, ClaudeBot oder PerplexityBot als menschliche Sitzungen. Dies sind Crawler- oder Abrufsignale.

Rohdaten aufbewahren

Bewahren Sie für jeden Panel-Durchlauf auf:

  • rohes HTML
  • Screenshot
  • Antworttext
  • Zeitstempel
  • Browser- und Collector-Version
  • Prompt und Locale
  • Antwort-Hash

Dies ermöglicht es, zu erklären, warum sich eine Metrik geändert hat.

Panel-Abwesenheit verfolgen

Ein fehlendes KI-Panel ist nicht dasselbe wie eine Antwort ohne Ihre Zitation.

Verwenden Sie:

text panel_nicht_vorhanden panel_vorhanden_ohne_ziel panel_vorhanden_ziel_zitiert blockiert_oder_herausgefordert sammlungsfehler

Stichprobenverzerrung kontrollieren

Ein geplanter Scraping-Vorgang wird beeinflusst durch:

  • Standort
  • Gerät
  • Sprache
  • Zeit
  • Personalisierung
  • Browser-Zustand
  • Prompt-Formulierung

Verwenden Sie einen festen Kernsatz von Prompts und einen rotierenden Satz von explorativen Prompts. Halten Sie die beiden Gruppen in Berichten getrennt.

Falsche Präzision vermeiden

Berichten Sie Konfidenzintervalle oder minimale Stichprobengrößen für kleine Themengruppen. Ein Thema mit drei Beobachtungen sollte nicht direkt mit einem Thema mit 300 Beobachtungen verglichen werden.

Gleiches mit Gleichem vergleichen

Beim Vergleich von Oberflächen halten Sie konstant:

  • Prompt
  • Locale
  • Land
  • Gerät
  • Sammelstunde
  • Zieldomänen-Regeln
  • Zitations-Parser-Version

11. Empfohlener Rollout-Plan

Woche 1: Instrumentierung

  • Strukturierte Server-Logs konfigurieren.
  • Das Bot-Register erstellen.
  • Die Shortener-Domain erstellen.
  • Kampagnen-Benennungsregeln hinzufügen.
  • Die Themataxonomie definieren.
  • Google Analytics 4 BigQuery-Export aktivieren.

Woche 2: Kontrollierte Links

  • Fünf bis zehn getrackte Zusammenfassungen veröffentlichen.
  • Links für verschiedene Themen und Inhaltsversionen erstellen.
  • Weiterleitungs-Logs mit Analytics-Sitzungen validieren.
  • Bestätigen, dass keine persönlichen Informationen in URLs oder Analyseparameter gelangen.

Woche 3: Panel-Beobachtungen

  • Ein Prompt-Manifest erstellen.
  • Beginnen Sie mit zwei Oberflächen und 20 bis 50 stabilen Prompts.
  • Einmal täglich ausführen.
  • Screenshots und HTML speichern.
  • Sammelfehler getrennt von fehlenden Zitationen messen.

Woche 4: Dashboard und Governance

  • Themen- und Oberflächen-Dashboards erstellen.
  • Crawler-Zustandsalarme hinzufügen.
  • Konfidenzregeln definieren.
  • Erstanbieter-Berichte mit Panel-Beobachtungen vergleichen.
  • Richtlinien für Datenaufbewahrung, -zugriff und -löschung dokumentieren.

Fazit

Die Messung von KI-Zitationen sollte als Multi-Signal-Attributionssystem aufgebaut werden, nicht als einzelner Verweisbericht.

Das zuverlässigste Framework kombiniert:

  • Google- und Bing-Erstanbieter-Sichtbarkeitsberichte
  • Server-Logs für Crawler- und Abrufaktivität
  • Erstanbieter-Kurzlinks für die kontrollierte Klickmessung
  • geplante, konforme Beobachtungen von Antwortpanels
  • Analyse- und Konversionsdaten für den nachgelagerten Wert

Die drei wichtigsten Metriken sind:

  1. Zitationsanteil nach Thema — wie viel des beobachteten Quellenraums zu Ihrer Domain gehört.
  2. Assistenten-Verweisrate — wie viel menschlicher Traffic KI-Oberflächen zugeschrieben werden kann.
  3. Engagement nach Zitation — ob von KI verwiesene Besucher sinnvolle Aktionen durchführen.

Das System wird nützlich, wenn jede Zahl ihre Oberfläche, den Zeitraum, den Nenner, die Attributionsmethode und das Konfidenzniveau enthält. Diese Disziplin verhindert, dass Crawler-Zugriffe mit Nutzern, gescrapte Antworten mit Impressionen und Zitationen mit Geschäftsergebnissen verwechselt werden.

Ähnliche Artikel

Gefallen Ihnen diese Inhalte?

Abonnieren Sie unseren Newsletter für die neuesten Content-Marketing-Insights und Wachstumsleitfäden.

Dieser Artikel dient nur zu Informationszwecken. Inhalte und Strategien können je nach Ihren spezifischen Bedürfnissen variieren.
Messung des AI-Zitierungseffekts: Analytik, Protokolle und Panel-Scrapes | AutoPod