AutoPodAutoPod

Pomiar wpływu cytowań AI: Analityka, logi i skrobanie paneli

•23 min czytania
Artykuł audio
Pomiar wpływu cytowań AI: Analityka, logi i skrobanie paneli
0:00—:—
Pomiar wpływu cytowań AI: Analityka, logi i skrobanie paneli

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:

  1. Czy strona została zeskanowana lub pobrana?
  2. Czy strona została zacytowana w odpowiedzi AI?
  3. Czy osoba odwiedziła stronę z tej odpowiedzi?
  4. 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.

WarstwaGłówne pytanieZalecane dane
DostępCzy 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
PozyskanieCzy osoba dotarła z powierzchni AI?Referer, parametry kampanii, kliknięcia krótkich linków, analityka
ZaangażowanieCzy 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:

  1. Raporty platform własnych dla wyświetleń i cytowań.
  2. Obserwacje paneli odpowiedzi dla widoczności na poziomie promptu i porównań konkurencyjnych.
  3. Logi przekierowań dla kontrolowanych kliknięć linków.
  4. Analityka internetowa dla sesji, zaangażowania i konwersji.
  5. Logi serwera dla aktywności crawlera i fetchera.
  6. 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ło
  • 2 = drugie zaobserwowane źródło
  • 3 = 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

  1. Preferuj oficjalne API, licencjonowanego dostawcę danych wyszukiwania lub raport własny.
  2. Przed zautomatyzowaniem żądań przejrzyj warunki dostawcy.
  3. W stosownych przypadkach przestrzegaj instrukcji robots.
  4. Nie omijaj CAPTCHA, barier logowania, limitów żądań ani zabezpieczeń przed botami.
  5. Użyj małego, stałego zestawu promptów.
  6. Uruchamiaj z niską częstotliwością.
  7. Zapisz odpowiedź i znacznik czasu, aby wyniki mogły być audytowane.
  8. 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" lub link_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:

  1. Udział w Cytowaniach według Tematu — jaka część obserwowanej przestrzeni źródeł należy do Twojej domeny.
  2. Współczynnik Odesłań Asystenta — jaka część ruchu ludzkiego może być przypisana do powierzchni AI.
  3. 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.

Powiązane artykuły

Podobają Ci się te treści?

Zapisz się do naszego newslettera, aby otrzymywać najnowsze spostrzeżenia dotyczące content marketingu i przewodniki wzrostu.

Ten artykuł służy wyłącznie celom informacyjnym. Treści i strategie mogą się różnić w zależności od Twoich konkretnych potrzeb.
Pomiar wpływu cytowań AI: Analityka, logi i skrobanie paneli | AutoPod