Pomiar wpływu cytowań AI: Analityka, logi i skrobanie paneli
Wprowadzenie
Odkrywanie treści napędzane przez AI jest obecnie rozłożone na kilka warstw pomiarowych. Osoba może zobaczyć firmę cytowaną w odpowiedzi AI, kliknąć link bez użytecznego referera, odwiedzić stronę przez przeglądarkę, która usuwa dane kampanii, lub przeczytać odpowiedź, nie klikając wcale. Tymczasem crawler AI może zażądać strony firmy, nie reprezentując ludzkiego użytkownika.
Użyteczny system pomiarowy musi zatem rozróżniać cztery pytania:
- Czy strona została zeskanowana lub pobrana?
- Czy strona została zacytowana w odpowiedzi AI?
- Czy osoba odwiedziła stronę z tej odpowiedzi?
- Czy ta wizyta przyniosła znaczące zaangażowanie lub przychody?
Od 22 września 2026 roku raportowanie własne ulega poprawie. Google Search Console udostępnia raport wydajności generatywnej AI dla Przeglądów AI i Trybu AI, zawierający wyświetlenia, strony, kraje, urządzenia i daty. Raport stał się dostępny na całym świecie 31 sierpnia 2026 roku. Raport wydajności generatywnej AI Google (support.google.com)
Microsoft również udostępnia raport Wydajności AI w Narzędziach dla Webmasterów Bing. Jego publiczna wersja zapoznawcza raportuje całkowitą liczbę cytowań, cytowane strony, zapytania kontekstowe oraz trendy cytowań w obsługiwanych doświadczeniach AI firmy Microsoft. Wydajność AI w Narzędziach dla Webmasterów Bing (blogs.bing.com)
Narzędzia te powinny być traktowane jako autorytatywne dla własnych powierzchni, ale nie tworzą kompletnego systemu pomiarowego obejmującego wszystkie asystenty. Zalecane podejście to połączenie raportów własnych, logów serwera, kontrolowanych linków śledzących, eksportów analitycznych oraz zaplanowanych obserwacji paneli odpowiedzi.
1. Dlaczego pomiar odesłań AI jest trudny
Tradycyjna analityka internetowa w dużej mierze opiera się na nagłówku Referer i parametrach kampanii. Oba mogą być brakujące lub niekompletne.
Nagłówek HTTP Referer może zawierać poprzednią stronę, tylko źródło poprzedniej witryny lub wcale nie mieć wartości. Polityki refererów przeglądarki mogą celowo ograniczać lub usuwać te informacje. Dokumentacja Referrer-Policy MDN (developer.mozilla.org)
Systemy AI wprowadzają dodatkowe komplikacje:
- Asystent może linkować do strony bez wysyłania rozpoznawalnego referera.
- Asystent może używać przekierowującego lub przepisującego URL.
- Asystent może pobierać treści po stronie serwera przed wyświetleniem odpowiedzi.
- Crawler AI może odwiedzić stronę, nie będąc widzianym przez żadnego człowieka.
- Odpowiedź może cytować URL kanoniczny zamiast URL, który został pierwotnie pobrany.
- Odpowiedź może się zmieniać w zależności od lokalizacji, urządzenia, języka, czasu lub personalizacji.
Na przykład OpenAI rozróżnia OAI-SearchBot do odkrywania treści w wyszukiwaniu, GPTBot do potencjalnego wykorzystania w szkoleniu oraz ChatGPT-User do akcji inicjowanych przez użytkownika. Perplexity oddzielnie identyfikuje PerplexityBot i Perplexity-User. Anthropic identyfikuje ClaudeBot, Claude-SearchBot i Claude-User. Tych żądań nie należy łączyć w jedną ogólną liczbę „ruchu AI”. Dokumentacja crawlera OpenAI (openai.com) Dokumentacja crawlera Perplexity (docs.perplexity.ai) Dokumentacja crawlera Anthropic (support.anthropic.com)
Centralna zasada brzmi:
Aktywność crawlera to nie ruch referencyjny, widoczność cytowań to nie ruch, a ruch to nie wpływ na biznes.
2. Ramy pomiarowe
Użyj pięciu warstw pomiarowych.
| Warstwa | Główne pytanie | Zalecane dane |
|---|---|---|
| Dostęp | Czy system AI zażądał strony? | Logi serwera www, logi zapory sieciowej |
| Widoczność | Czy strona została wyświetlona lub zacytowana? | Google Search Console, Bing Webmaster Tools, obserwacje paneli odpowiedzi |
| Pozyskanie | Czy osoba dotarła z powierzchni AI? | Referer, parametry kampanii, kliknięcia krótkich linków, analityka |
| Zaangażowanie | Czy odwiedzający wchodził w znaczącą interakcję? | Zdarzenia analityczne, wyświetlenia strony, czas zaangażowania |
| Wartość | Czy wizyta przyniosła pożądany rezultat? | Rejestracje, leady, zakupy, subskrypcje |
Zalecana hierarchia źródeł prawdy
Dla każdego pytania używaj najsilniejszego dostępnego sygnału:
- Raporty platform własnych dla wyświetleń i cytowań.
- Obserwacje paneli odpowiedzi dla widoczności na poziomie promptu i porównań konkurencyjnych.
- Logi przekierowań dla kontrolowanych kliknięć linków.
- Analityka internetowa dla sesji, zaangażowania i konwersji.
- Logi serwera dla aktywności crawlera i fetchera.
- Oszacowania modelowane tylko wtedy, gdy bezpośrednie dowody są niedostępne.
Nie łącz wszystkich sygnałów w jedną liczbę bez zachowania źródła i poziomu ufności.
3. Podstawowy model danych
Utwórz hurtownię z oddzielnymi tabelami dla surowych obserwacji i wskaźników pochodnych.
panel_runs
Jeden wiersz dla każdego promptu i sprawdzenia powierzchni.
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
Jeden wiersz dla każdego linku zaobserwowanego w panelu odpowiedzi.
text run_id surface topic prompt_id cited_url cited_domain citation_position link_scope target_domain target_cited
ai_crawler_requests
Agregowane dane z logów serwera.
text date bot_name bot_role path requests successful_requests error_requests unique_ip_count
shortener_clicks
Jeden wiersz dla każdego żądania kontrolowanego linku.
text click_id occurred_at slug surface topic prompt_id asset_id referrer_host user_agent_family ip_hmac destination_path
ai_sessions
Znormalizowana tabela analityczna.
text session_id date surface topic prompt_id landing_page attribution_method confidence engaged engagement_time_seconds key_event revenue
Pole confidence jest ważne. Zalecane wartości to:
- A — Bezpośrednie: kliknięcie kontrolowanego krótkiego linku lub zaufany parametr kampanii.
- B — Silne: znany referer asystenta lub zweryfikowane żądanie pobrania przez użytkownika.
- C — Prawdopodobne: dowody z kampanii lub strony docelowej bez pełnego referera.
- D — Modelowane: wywnioskowane z czasu, obserwacji paneli lub zagregowanych zmian.
Raportuj A i B oddzielnie od C i D.
4. Definicje metryk
Udział w Cytowaniach według Tematu
Udział w Cytowaniach według Tematu mierzy, jak często domena docelowa pojawia się wśród źródeł zaobserwowanych dla danego tematu.
[ \text{Udział w Cytowaniach}_{temat}
\frac{\text{Wystąpienia cytowań domeny docelowej}} {\text{Wszystkie wystąpienia cytowań}} ]
Przykład:
- 100 wszystkich cytowań zaobserwowanych dla „oprogramowania do zarządzania projektami”
- 12 cytowań wskazuje na Twoją domenę
- Udział w Cytowaniach według Tematu = 12%
Ten wskaźnik mierzy udział źródła, a nie to, czy odpowiedź zawierała Twoją witrynę choć raz.
Zasięg Cytowań
Użyj drugiej metryki dla obecności na poziomie odpowiedzi:
[ \text{Zasięg Cytowań}_{temat}
\frac{\text{Uruchomienia odpowiedzi zawierające cytat docelowy}} {\text{Uruchomienia odpowiedzi, w których panel był obecny}} ]
Zasięg Cytowań jest często łatwiejszy do zrozumienia dla osób nietechnicznych:
„Nasza strona pojawiła się w 34% zaobserwowanych odpowiedzi AI dla tego tematu.”
Pozycja Cytowania
Zarejestruj pozycję cytowania w ramach odpowiedzi:
1= pierwsze zaobserwowane źródło2= drugie zaobserwowane źródło3= trzecie zaobserwowane źródło
Ponieważ interfejsy AI często grupują lub zmieniają kolejność źródeł, traktuj pozycję jako obserwację opisową, a nie uniwersalny ranking.
Współczynnik Odesłań Asystenta
Współczynnik Odesłań Asystenta mierzy proporcję ludzkich sesji na stronie, które można przypisać powierzchni AI.
[ \text{Współczynnik Odesłań Asystenta}
\frac{\text{Sesje ludzkie przypisane AI}} {\text{Wszystkie sesje ludzkie}} ]
Raportuj co najmniej dwie wersje:
text Potwierdzony Współczynnik Odesłań Asystenta = sesje AI z ufnością A / wszystkie sesje ludzkie
Rozszerzony Współczynnik Odesłań Asystenta = sesje AI z ufnością A + B + C / wszystkie sesje ludzkie
Wersja potwierdzona jest bardziej konserwatywna. Wersja rozszerzona jest przydatna do analizy trendów, ale nie powinna być przedstawiana jako w pełni zaobserwowany ruch.
Nie używaj przebiegów paneli jako wyświetleń
Jeśli przeszukujesz 500 promptów, to nie oznacza, że 500 osób widziało te odpowiedzi. Twój skraper mierzy kontrolowaną próbkę stanów odpowiedzi. Korzystaj z raportów wyświetleń platformy własnej, gdy są dostępne.
Zaangażowanie po Cytowaniu
Zaangażowanie po Cytowaniu mierzy, czy osoby, które dotarły przez cytat AI, podjęły znaczące działanie.
[ \text{Zaangażowanie po Cytowaniu}
\frac{\text{Zaangażowane sesje AI}} {\text{Sesje przypisane AI}} ]
Dla Google Analytics 4, zaangażowana sesja trwa dłużej niż 10 sekund, zawiera kluczowe zdarzenie lub obejmuje co najmniej dwa wyświetlenia strony lub ekranu. Dokumentacja wskaźnika zaangażowania Google Analytics (support.google.com)
Raportuj również:
text Czas Zaangażowania po Cytowaniu = mediana zaangażowanych sekund na sesję AI
Współczynnik Konwersji po Cytowaniu = sesje AI z kluczowym zdarzeniem / sesje przypisane AI
Przychód na Sesję po Cytowaniu = przychód z sesji AI / sesje przypisane AI
Mediana czasu zaangażowania jest zazwyczaj bardziej stabilna niż średni czas zaangażowania, ponieważ niewielka liczba bardzo długich sesji może zniekształcić średnią.
Dodatkowe użyteczne metryki
Współczynnik Sukcesu Crawlera AI
[ \text{Współczynnik Sukcesu Crawlera}
\frac{\text{Żądania crawlera AI zwracające 2xx lub 3xx}} {\text{Żądania crawlera AI}} ]
Rozbij to według bota i ścieżki. Wysoki spadek cytowań w połączeniu z rosnącym wskaźnikiem błędów crawlera może wskazywać na problem z hostingiem, zaporą sieciową lub konfiguracją pliku robots.
Współczynnik Cytowania do Kliknięcia
Dla kontrolowanych obserwacji paneli odpowiedzi:
[ \text{Zaobserwowany Współczynnik Cytowania do Kliknięcia}
\frac{\text{Potwierdzone kliknięcia krótkich linków}} {\text{Zaobserwowane cytaty docelowe}} ]
To nie jest prawdziwy współczynnik klikalności użytkownika, chyba że mianownik reprezentuje rzeczywiste wyświetlenia użytkowników. Oznacz to wyraźnie jako obserwowany wskaźnik operacyjny.
Świeżość Cytowań
Mierzy, jak często cytowany URL odzwierciedla aktualną wersję strony.
text Świeżość Cytowań = cytowane strony z aktualnym hashem treści / cytowane strony
Jest to przydatne dla często zmieniających się stron dokumentacji, cen, produktów i polityk.
5. Analiza logów serwera
Skonfiguruj ustrukturyzowane logi dostępu
Ustrukturyzowane logi są łatwiejsze do analizy niż tradycyjne logi oddzielane spacjami.
Przykład konfiguracji Nginx:
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;
Przechowuj pliki logów w ograniczonym miejscu. Nie wysyłaj surowych adresów IP do ogólnej tabeli analitycznej, chyba że masz jasny powód operacyjny.
Identyfikuj typy żądań AI
Minimum, oddziel:
text OpenAI:
- OAI-SearchBot
- ChatGPT-User
- GPTBot
Anthropic:
- Claude-SearchBot
- Claude-User
- ClaudeBot
Perplexity:
- PerplexityBot
- Perplexity-User
Wyszukiwarki:
- Googlebot
- Bingbot
Funkcje wyszukiwania AI Google zazwyczaj używają istniejących mechanizmów kontroli indeksowania Google Search, a nie oddzielnego uniwersalnego „bota Przeglądu AI”. Google stwierdza, że Googlebot kontroluje dostęp do Wyszukiwarki, w tym funkcji AI. Funkcje AI Google i witryny (developers.google.com)
Ciągi user-agent mogą być fałszowane. Dla decyzji operacyjnych połącz dopasowanie user-agenta z zakresami adresów IP opublikowanymi przez dostawców lub zweryfikowanym reverse DNS, jeśli są dostępne. OpenAI i Perplexity publikują informacje o adresach IP dla swoich odpowiednich crawlerów i fetcherów. (openai.com)
Reprodukowalny sumator logów
Zapisz poniższe jako 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])
Uruchom za pomocą:
bash
HMAC_SECRET='a-long-random-secret'
python ai_log_summary.py /var/log/nginx/ai-access.jsonl \
data/ai_crawler_daily.csv
To generuje dowody operacyjne dotyczące aktywności pobierania, a nie ruchu ludzkiego.
6. Niestandardowe skracacze linków dla podsumowań zorientowanych na AI
Własny skracacz linków zapewnia kontrolowany punkt pomiaru, gdy publikujesz streszczenia lub materiały źródłowe, które mogą być odwoływane przez systemy AI.
Użyj kontrolowanej przez siebie domeny, takiej jak:
text https://go.example.com/ai-docs
Miejsce docelowe powinno zawierać parametry kampanii, takie jak:
text utm_source=perplexity utm_medium=ai_answer utm_campaign=product_documentation utm_content=pricing_summary_v3
Google zaleca stosowanie parametrów kampanii do identyfikacji źródeł ruchu i kampanii. Dokumentacja URL kampanii Google Analytics (support.google.com)
Zalecane pola linków
text surface topic prompt_id asset_id content_version destination_path
Nie umieszczaj w linku danych osobowych, adresów e-mail ani zapytań wyszukiwania użytkowników. Identyfikatory kampanii powinny opisywać opublikowany zasób, a nie identyfikować osobę.
Ważne ograniczenia
Krótki link to urządzenie kalibracyjne, a nie uniwersalne rozwiązanie:
- Asystent może przepisać docelowy URL.
- Asystent może cytować URL kanoniczny zamiast krótkiego linku.
- Link może być wyświetlony, ale nigdy nie kliknięty.
- Przeglądarka lub narzędzie prywatności może usunąć referera.
- Niektóre systemy mogą pobierać miejsce docelowe po stronie serwera.
- Krótki link nie mierzy użytkowników, którzy pamiętają markę i wracają później.
Nie używaj skracacza do ukrywania miejsca docelowego, omijania systemów bezpieczeństwa ani manipulowania cytowaniami. Użyj wyraźnego przekierowania do odpowiedniej, kanonicznej strony.
Minimalny własny skracacz linków
Poniższy przykład wykorzystuje FastAPI i SQLite. Przed użyciem w środowisku produkcyjnym zabezpiecz punkt końcowy tworzenia linków za pomocą uwierzytelniania.
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)
Uruchom za pomocą:
bash pip install fastapi uvicorn uvicorn shortener:app --host 0.0.0.0 --port 8080
Użyj przekierowania 302 dla linków pomiarowych, aby przeglądarki i systemy pośredniczące rzadziej buforowały przekierowanie na stałe.
7. Zaplanowane skrobanie paneli odpowiedzi AI
Skrobanie paneli jest przydatne do odpowiadania na pytania, których analityka własna może nie ujawniać:
- Jakie konkurencyjne źródła są cytowane?
- Która strona jest cytowana dla konkretnego promptu?
- Czy odpowiedź zmienia się w zależności od kraju lub urządzenia?
- Czy cytat pojawia się w odpowiedzi, na liście źródeł, czy w obu miejscach?
- Jak stabilna jest odpowiedź w czasie?
- Czy asystent cytuje krótki link, czy URL kanoniczny?
Jednak skrobanie musi być wykonywane ostrożnie.
Zasady odpowiedzialnego gromadzenia danych
- Preferuj oficjalne API, licencjonowanego dostawcę danych wyszukiwania lub raport własny.
- Przed zautomatyzowaniem żądań przejrzyj warunki dostawcy.
- W stosownych przypadkach przestrzegaj instrukcji robots.
- Nie omijaj CAPTCHA, barier logowania, limitów żądań ani zabezpieczeń przed botami.
- Użyj małego, stałego zestawu promptów.
- Uruchamiaj z niską częstotliwością.
- Zapisz odpowiedź i znacznik czasu, aby wyniki mogły być audytowane.
- Zatrzymaj, gdy dostawca zwróci wyzwanie lub odpowiedź „dostęp zabroniony”.
Protokół wykluczenia robotów to mechanizm preferencji crawlera, a nie system bezpieczeństwa. Nie autoryzuje dostępu do chronionych treści. RFC 9309: Protokół wykluczenia robotów (rfc-editor.org)
Zbuduj Manifest Promptów
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
Utrzymuj stabilne prompty. Jeśli prompty się zmieniają, porównania trendów stają się mniej wiarygodne.
Przechowuj również:
- kraj
- język
- typ urządzenia
- wersję przeglądarki
- wersję promptu
- wersję treści
- czas kolekcji w UTC
Google zauważa, że odpowiedzi z Przeglądu AI i Trybu AI mogą się różnić, ponieważ wykorzystują różne systemy i mogą używać powiązanych wyszukiwań lub „rozszerzania zapytań”. Dokumentacja funkcji AI Google i witryny (developers.google.com)
Skrypt do zbierania danych za pomocą Playwright
Poniższy skrypt to uniwersalne narzędzie badawcze. Rejestruje wyrenderowaną stronę, zrzut ekranu, linki do stron i tekst odpowiedzi. Celowo nie omija wyzwań ani nie próbuje pokonać kontroli dostępu.
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"))
Zainstaluj i uruchom:
bash pip install playwright playwright install chromium python scrape_panels.py
Zaplanuj to z blokadą, aby dwa uruchomienia nie mogły się nakładać:
cron
15 3 * * * flock -n /tmp/ai-panel.lock
/usr/bin/python3 /opt/ai-measure/scrape_panels.py
Popraw dokładność za pomocą adapterów powierzchni
Skrypt awaryjny rejestruje linki na poziomie strony. Jest to przydatne do odkrywania, ale może zawierać zwykłe linki z wyników wyszukiwania. Do użytku produkcyjnego:
- Użyj licencjonowanego API panelu odpowiedzi, gdy jest dostępne.
- Utwórz oddzielny adapter selektora dla każdej powierzchni.
- Przechowuj wersję selektora.
- Przechowuj surowy kod HTML i zrzut ekranu.
- Oznacz każdy rekord za pomocą
link_scope = "panel_selector"lublink_scope = "page_fallback".
Obliczaj Udział w Cytowaniach (Citation Share) tylko na podstawie danych panel_selector, gdy możesz wiarygodnie zidentyfikować panel. Zachowaj obserwacje awaryjne w oddzielnym raporcie eksploracyjnym.
8. Integracja Google Analytics i BigQuery
Google Analytics 4 może eksportować surowe dane zdarzeń do BigQuery. Dzienne eksporty dostarczają stabilne tabele, takie jak events_RRRRMMDD; eksporty strumieniowe są przydatne do monitorowania niemal w czasie rzeczywistym, ale mogą być niekompletne, dopóki dzienna tabela nie zostanie sfinalizowana. Eksport Google Analytics do BigQuery (support.google.com)
Uproszczone zapytanie o sesję może wyglądać następująco:
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;
Dla dojrzałej implementacji dodaj surface, topic i prompt_id do struktury kampanii lub dołącz je z tabeli kliknięć skracacza.
Nie twórz wymiaru analitycznego o wysokiej kardynalności dla każdego pojedynczego użytkownika lub sesji. Google ostrzega, że nadmierne niestandardowe wymiary o wysokiej kardynalności mogą obniżyć jakość raportowania. Niestandardowe wymiary i metryki Google Analytics (support.google.com)
9. Projekt pulpitu nawigacyjnego
Praktyczny pulpit nawigacyjny powinien zawierać pięć widoków.
Podsumowanie dla kadry zarządzającej
Wyświetl:
- Udział w Cytowaniach według Tematu
- Zasięg Cytowań według Tematu
- Potwierdzony Współczynnik Odesłań Asystenta
- Rozszerzony Współczynnik Odesłań Asystenta
- Zaangażowanie po Cytowaniu
- Współczynnik sukcesu crawlera AI
- Kluczowe zdarzenia lub przychody przypisane AI
Trend Udziału w Cytowaniach
Wykres liniowy:
text Data × Temat × Powierzchnia
Pokaż zarówno:
- wystąpienia cytowań domeny docelowej
- wszystkie zaobserwowane wystąpienia cytowań
Lejek Ruchu Asystenta
text Zaobserwowane cytaty ↓ Kliknięcia kontrolowanych linków ↓ Sesje przypisane AI ↓ Zaangażowane sesje AI ↓ Kluczowe zdarzenia
Każdy etap powinien wyświetlać pewność atrybucji.
Stan Crawlera
Tabela według bota:
text bot_name requests successful_requests error_rate unique_ip_count top_paths
Alertuj, gdy:
- wskaźnik błędów przekroczy 2 procent
- bot nagle zniknie
- wcześniej dostępna sekcja treści zwraca błąd 403 lub 429
- żądania crawlera koncentrują się na niezamierzonej prywatnej ścieżce
Tabela szans na treści
Klasyfikuj strony i tematy według:
text wysoki udział w cytowaniach + niski współczynnik odesłań wysokie wyświetlenia + niski zasięg cytowań wysokie zaangażowanie + niska widoczność cytowań wysokie błędy crawlera + wysokie znaczenie biznesowe
Te kombinacje zazwyczaj prowadzą do lepszych decyzji niż patrzenie tylko na wolumen ruchu.
Minimalny pulpit nawigacyjny Streamlit
Zapisz jako 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("AI Citation Impact Dashboard")
if panels.empty: st.warning("No panel observations found.") 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("Citation Share by Topic") st.dataframe(citation_share, use_container_width=True) st.bar_chart( citation_share.set_index("topic")["citation_share"] )
with col2: st.subheader("Citation Coverage by Topic") st.dataframe(coverage, use_container_width=True) st.bar_chart( coverage.set_index("topic")["citation_coverage"] )
st.subheader("Citation Position") 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("Daily Observations") 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) )
Uruchom za pomocą:
bash pip install streamlit pandas streamlit run dashboard.py
W środowisku produkcyjnym, zamiast bezpośredniego odczytu surowych plików, wczytuj znormalizowane tabele do DuckDB, BigQuery, Snowflake lub PostgreSQL.
10. Kontrole jakości
System pomiarowy może generować liczby wyglądające na precyzyjne, ale wprowadzające w błąd. Dodaj te kontrole.
Oddziel boty od ludzi
Nigdy nie licz OAI-SearchBot, GPTBot, ClaudeBot ani PerplexityBot jako sesji ludzkich. To są sygnały crawlera lub pobierania.
Zachowaj surowe dowody
Dla każdego uruchomienia panelu zachowaj:
- surowy kod HTML
- zrzut ekranu
- tekst odpowiedzi
- znacznik czasu
- wersję przeglądarki i kolektora
- prompt i lokalizację
- hash odpowiedzi
To umożliwia wyjaśnienie, dlaczego wskaźnik się zmienił.
Śledź brak panelu
Brakujący panel AI to nie to samo, co odpowiedź bez Twojego cytowania.
Użyj:
text panel_not_present panel_present_no_target panel_present_target_cited blocked_or_challenged collection_error
Kontroluj błąd próbkowania
Zaplanowane skrobanie jest متاثرowane przez:
- lokalizację
- urządzenie
- język
- czas
- personalizację
- stan przeglądarki
- sformułowanie promptu
Użyj stałego podstawowego zestawu promptów i rotacyjnego zestawu promptów eksploracyjnych. Zachowaj te dwie grupy oddzielnie w raportach.
Unikaj fałszywej precyzji
Raportuj przedziały ufności lub minimalne rozmiary próbek dla małych grup tematycznych. Temat z trzema obserwacjami nie powinien być bezpośrednio porównywany z tematem zawierającym 300 obserwacji.
Porównuj podobne z podobnymi
Podczas porównywania powierzchni, utrzymuj stałe:
- prompt
- lokalizację
- kraj
- urządzenie
- godzinę zbierania danych
- zasady dotyczące domeny docelowej
- wersję parsera cytowań
11. Zalecany plan wdrożenia
Tydzień 1: Instrumentacja
- Skonfiguruj ustrukturyzowane logi serwera.
- Utwórz rejestr botów.
- Utwórz domenę dla skracacza linków.
- Dodaj zasady nazewnictwa kampanii.
- Zdefiniuj taksonomię tematów.
- Włącz eksport Google Analytics 4 do BigQuery.
Tydzień 2: Kontrolowane linki
- Opublikuj pięć do dziesięciu śledzonych podsumowań.
- Utwórz linki dla różnych tematów i wersji treści.
- Zweryfikuj logi przekierowań z sesjami analitycznymi.
- Potwierdź, że żadne dane osobowe nie trafiają do URL-i ani parametrów analitycznych.
Tydzień 3: Obserwacje paneli
- Utwórz manifest promptów.
- Zacznij od dwóch powierzchni i 20 do 50 stabilnych promptów.
- Uruchamiaj raz dziennie.
- Zapisuj zrzuty ekranu i kod HTML.
- Mierz błędy zbierania danych oddzielnie od braku cytowań.
Tydzień 4: Pulpit nawigacyjny i zarządzanie
- Zbuduj pulpity nawigacyjne dla tematów i powierzchni.
- Dodaj alerty o stanie crawlera.
- Zdefiniuj zasady ufności.
- Porównaj raporty własne z obserwacjami paneli.
- Udokumentuj politykę przechowywania, dostępu i usuwania danych.
Podsumowanie
Pomiar cytowań AI powinien być budowany jako system atrybucji wielosygnałowej, a nie jako pojedynczy raport odesłań.
Najbardziej wiarygodne ramy łączą:
- Raporty widoczności własnych platform Google i Bing
- logi serwera dla aktywności crawlera i fetchera
- własne krótkie linki do kontrolowanego pomiaru kliknięć
- zaplanowane, zgodne z zasadami obserwacje paneli odpowiedzi
- dane analityczne i konwersji dla wartości końcowej
Trzy najważniejsze metryki to:
- Udział w Cytowaniach według Tematu — jaka część obserwowanej przestrzeni źródeł należy do Twojej domeny.
- Współczynnik Odesłań Asystenta — jaka część ruchu ludzkiego może być przypisana do powierzchni AI.
- Zaangażowanie po Cytowaniu — czy użytkownicy odesłani przez AI podejmują znaczące działania.
System staje się użyteczny, gdy każda liczba zawiera swoją powierzchnię, okres czasu, mianownik, metodę atrybucji i poziom ufności. Ta dyscyplina zapobiega myleniu trafień crawlera z użytkownikami, skrobanych odpowiedzi z wyświetleniami oraz cytowań z wynikami biznesowymi.
Auto