AutoPodAutoPod

MÀta AI-citeringspÄverkan: Analysdata, loggar och skrapning av svarspaneler

‱21 min lĂ€sning
Ljudartikel
MÀta AI-citeringspÄverkan: Analysdata, loggar och skrapning av svarspaneler
0:00—:—
MÀta AI-citeringspÄverkan: Analysdata, loggar och skrapning av svarspaneler

MÀta AI-citeringspÄverkan: Analysdata, loggar och skrapning av svarspaneler

Introduktion

AI-driven upptÀckt Àr nu uppdelad i flera mÀtlager. En person kan se ett företag citerat i ett AI-svar, klicka pÄ en lÀnk utan anvÀndbar hÀnvisare, besöka via en webblÀsare som tar bort kampanjdata, eller lÀsa ett svar utan att klicka alls. Samtidigt kan en AI-bot begÀra företagets sida utan att representera en mÀnsklig besökare.

Ett anvÀndbart mÀtsystem mÄste dÀrför separera fyra frÄgor:

  1. HÀmtades eller hÀmtades webbplatsen?
  2. Citerades webbplatsen i ett AI-svar?
  3. Besökte en person webbplatsen frÄn det svaret?
  4. Ledde det besöket till meningsfullt engagemang eller intÀkter?

FrÄn och med den 22 september 2026 förbÀttras förstapartsrapporteringen. Google Search Console tillhandahÄller en prestandarapport för generativ AI för AI Overviews och AI Mode, inklusive exponeringar, sidor, lÀnder, enheter och datum. Rapporten blev tillgÀnglig globalt den 31 augusti 2026. Googles rapport om generativ AI-prestanda (support.google.com)

Microsoft tillhandahÄller ocksÄ en AI Performance-rapport i Bing Webmaster Tools. Dess offentliga förhandsvisning rapporterar totala citeringar, citerade sidor, grundlÀggande frÄgor och citeringstrender över stödda Microsoft AI-upplevelser. Bing Webmaster Tools AI Performance (blogs.bing.com)

Dessa verktyg bör behandlas som auktoritativa för sina egna ytor, men de skapar inte ett komplett mÀtsystem över olika assistenter. Den rekommenderade metoden Àr att kombinera förstapartrapporter, serverloggar, kontrollerade spÄrningslÀnkar, analysdataexporter och schemalagda observationer av svarspaneler.


1. Varför AI-hÀnvisningsmÀtning Àr svÄrt

Traditionell webbanalys Àr starkt beroende av Referer-huvudet och kampanjparametrar. BÄda kan saknas eller vara ofullstÀndiga.

HTTP Referer-huvudet kan innehÄlla föregÄende sida, endast den föregÄende webbplatsens ursprung, eller inget vÀrde alls. WebblÀsarpolicyer för hÀnvisare kan avsiktligt begrÀnsa eller ta bort den informationen. MDN Referrer-Policy dokumentation (developer.mozilla.org)

AI-system skapar ytterligare komplikationer:

  • En assistent kan lĂ€nka till en sida utan att skicka en igenkĂ€nnbar hĂ€nvisare.
  • En assistent kan anvĂ€nda en omdirigerande eller omskriven URL.
  • En assistent kan hĂ€mta innehĂ„ll serversidan innan ett svar visas.
  • En AI-bot kan besöka en sida utan att nĂ„gon mĂ€nniska ser den.
  • Svaret kan citera en kanonisk URL istĂ€llet för den URL som ursprungligen hĂ€mtades.
  • Svaret kan Ă€ndras beroende pĂ„ plats, enhet, sprĂ„k, tid eller personalisering.

Till exempel skiljer OpenAI mellan OAI-SearchBot för sökupptÀckt, GPTBot för möjlig trÀningsanvÀndning och ChatGPT-User för anvÀndarinitierade ÄtgÀrder. Perplexity identifierar separat PerplexityBot och Perplexity-User. Anthropic identifierar ClaudeBot, Claude-SearchBot och Claude-User. Dessa förfrÄgningar bör inte kombineras till ett generiskt "AI-trafik"-nummer. OpenAI bot-dokumentation (openai.com) Perplexity bot-dokumentation (docs.perplexity.ai) Anthropic bot-dokumentation (support.anthropic.com)

Den centrala regeln Àr:

Botaktivitet Àr inte hÀnvisningstrafik, citeringssynlighet Àr inte trafik, och trafik Àr inte affÀrspÄverkan.


2. Ramverket för mÀtning

AnvÀnd fem mÀtlager.

LagerHuvudfrÄgaRekommenderad data
ÅtkomstBegĂ€rde ett AI-system sidan?Webbserverloggar, brandvĂ€ggsloggar
SynlighetVisades eller citerades sidan?Google Search Console, Bing Webmaster Tools, observationer av svarspaneler
FörvÀrvKom en person frÄn en AI-yta?HÀnvisare, kampanjparametrar, klick pÄ korta lÀnkar, analysdata
EngagemangInteragerade besökaren meningsfullt?AnalyshÀndelser, sidvisningar, engagemangstid
VÀrdeLedde besöket till ett önskat resultat?Registreringar, leads, köp, prenumerationer

Rekommenderad kÀllhierarki för sanningen

AnvÀnd den starkaste tillgÀngliga signalen för varje frÄga:

  1. Förstapartplattformsrapporter för exponeringar och citeringar.
  2. Observationer av svarspaneler för synlighet pÄ prompt-nivÄ och jÀmförelser med konkurrenter.
  3. Omdirigeringsloggar för kontrollerade lÀnkklick.
  4. Webbanalys för sessioner, engagemang och konverteringar.
  5. Serverloggar för bot- och hÀmtningsaktivitet.
  6. Modellerade uppskattningar endast nÀr direkta bevis saknas.

Kombinera inte alla signaler till ett enda nummer utan att bevara kÀlla och konfidensnivÄ.


3. GrundlÀggande datamodell

Skapa ett datalager med separata tabeller för rÄa observationer och hÀrledda mÄtt.

panel_runs

En rad för varje prompt- och ytkontroll.

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

En rad för varje lÀnk som observerats i en svarspanel.

text run_id surface topic prompt_id cited_url cited_domain citation_position link_scope target_domain target_cited

ai_crawler_requests

Aggregerad serverloggdata.

text date bot_name bot_role path requests successful_requests error_requests unique_ip_count

shortener_clicks

En rad för varje kontrollerad lÀnkbegÀran.

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

ai_sessions

En normaliserad analysdata-tabell.

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

confidence-fÀltet Àr viktigt. Rekommenderade vÀrden Àr:

  • A — Direkt: kontrollerat klick pĂ„ kortlĂ€nk eller betrodd kampanjparameter.
  • B — Stark: kĂ€nd assistent-hĂ€nvisare eller verifierad anvĂ€ndar-hĂ€mtningsförfrĂ„gan.
  • C — Sannolik: bevis frĂ„n kampanj eller mĂ„lsida utan en komplett hĂ€nvisare.
  • D — Modellerad: hĂ€rledd frĂ„n tidpunkt, panelobservationer eller aggregerade förĂ€ndringar.

Rapportera A och B separat frÄn C och D.


4. Metrikdefinitioner

Citeringsandel per Àmne

Citeringsandel per Àmne mÀter hur ofta en mÄldomÀn förekommer bland de kÀllor som observerats för ett Àmne.

\[ \text{Citeringsandel}_{Ă€mne}

\frac{\text{Antal citeringar av mÄldomÀnen}} {\text{Alla citeringsförekomster}} \]

Exempel:

  • 100 totala citeringar observerade för "programvara för projekthantering"
  • 12 citeringar pekar pĂ„ din domĂ€n
  • Citeringsandel per Ă€mne = 12%

Detta mÄtt mÀter kÀllandel, inte om ett svar innehöll din webbplats minst en gÄng.

Citeringsomfattning

AnvÀnd ett andra mÄtt för nÀrvaro pÄ svarsnivÄ:

\[ \text{Citeringsomfattning}_{Ă€mne}

\frac{\text{Antal svar som innehÄller en mÄlciteraring}} {\text{Antal svar dÀr en panel fanns}} \]

Citeringsomfattning Àr ofta lÀttare för icke-tekniska mÄlgrupper att förstÄ:

"VÄr webbplats syntes i 34% av observerade AI-svar för detta Àmne."

Citeringsposition

Registrera positionen för en citering inom svaret:

  • 1 = första observerade kĂ€lla
  • 2 = andra observerade kĂ€lla
  • 3 = tredje observerade kĂ€lla

Eftersom AI-grÀnssnitt ofta grupperar eller omordnar kÀllor, behandla position som en beskrivande observation, inte en universell ranking.


AssistenthÀnvisningsfrekvens

AssistenthÀnvisningsfrekvens mÀter andelen mÀnskliga webbplatssessioner som kan tillskrivas en AI-yta.

\[ \text{AssistenthÀnvisningsfrekvens}

\frac{\text{AI-tillskrivna mÀnskliga sessioner}} {\text{Alla mÀnskliga sessioner}} \]

Rapportera minst tvÄ versioner:

text BekrÀftad AssistenthÀnvisningsfrekvens = A-konfidens AI-sessioner / alla mÀnskliga sessioner

Utökad AssistenthÀnvisningsfrekvens = A + B + C-konfidens AI-sessioner / alla mÀnskliga sessioner

Den bekrÀftade versionen Àr mer konservativ. Den utökade versionen Àr anvÀndbar för trendanalys men bör inte presenteras som fullt observerad trafik.

AnvÀnd inte panelkörningar som exponeringar

Om du skrapar 500 prompter betyder det inte att 500 personer sÄg dessa svar. Din skrapare mÀter ett kontrollerat urval av svarstillstÄnd. AnvÀnd förstapartplattforms exponeringrapporter nÀr de Àr tillgÀngliga.


Engagemang efter citering

Engagemang efter citering mÀter om personer som kom via en AI-citering utförde en meningsfull ÄtgÀrd.

\[ \text{Engagemang efter citering}

\frac{\text{Engagerade AI-sessioner}} {\text{AI-tillskrivna sessioner}} \]

För Google Analytics 4 varar en engagerad session lÀngre Àn 10 sekunder, inkluderar en nyckelhÀndelse, eller inkluderar minst tvÄ sid- eller skÀrmvisningar. Google Analytics dokumentation om engagemangsfrekvens (support.google.com)

Rapportera ocksÄ:

text Engagemangstid efter citering = median engagerade sekunder per AI-session

Konverteringsfrekvens efter citering = AI-sessioner med en nyckelhÀndelse / AI-tillskrivna sessioner

IntÀkt per session efter citering = intÀkt frÄn AI-sessioner / AI-tillskrivna sessioner

Median engagemangstid Àr vanligtvis stabilare Àn genomsnittlig engagemangstid eftersom ett litet antal mycket lÄnga sessioner kan förvrÀnga genomsnittet.


Ytterligare anvÀndbara mÀtvÀrden

AI-botars framgÄngsfrekvens

\[ \text{Botars framgÄngsfrekvens}

\frac{\text{AI-botbegÀranden som returnerar 2xx eller 3xx}} {\text{AI-botbegÀranden}} \]

Bryt ner detta per bot och sökvÀg. En hög citeringsminskning i kombination med en ökande bot-felprocent kan indikera ett problem med hosting, brandvÀgg eller robots-konfiguration.

Citerings-till-klick-frekvens

För kontrollerade svarspanelobservationer:

\[ \text{Observerad Citerings-till-klick-frekvens}

\frac{\text{BekrÀftade klick pÄ kortlÀnk}} {\text{Observerade mÄlcitationer}} \]

Detta Àr inte en sann anvÀndares klickfrekvens om inte nÀmnaren representerar faktiska anvÀndarexponeringar. MÀrk det tydligt som ett observerat operationellt mÄtt.

Citeringsaktualitet

MĂ€t hur ofta den citerade URL:en speglar den aktuella sidversionen.

text Citeringsaktualitet = citerade sidor med nuvarande innehÄllshash / citerade sidor

Detta Àr anvÀndbart för dokumentation, prissÀttning, produkter och policysidor som ofta Àndras.


5. Serverlogganalys

Konfigurera strukturerade Ätkomstloggar

Strukturerade loggar Àr lÀttare att analysera Àn traditionella mellanslagseparerade loggar.

Exempel pÄ 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;

Lagr loggfilerna pÄ en begrÀnsad plats. Skicka inte rÄa IP-adresser till en generell analysdata-tabell om du inte har en tydlig operativ anledning.

Identifiera AI-förfrÄgningstyper

Separera minst:

text OpenAI:

  • OAI-SearchBot
  • ChatGPT-User
  • GPTBot

Anthropic:

  • Claude-SearchBot
  • Claude-User
  • ClaudeBot

Perplexity:

  • PerplexityBot
  • Perplexity-User

Sökmotorer:

  • Googlebot
  • Bingbot

Googles AI Search-funktioner anvÀnder generellt befintliga Google Search-genomsökningskontroller snarare Àn en separat universell "AI Overview bot." Google anger att Googlebot kontrollerar Ätkomst för Sök, inklusive AI-funktioner. Googles AI-funktioner och webbplatser (developers.google.com)

User-agent-strÀngar kan förfalskas. För operationella beslut, kombinera user-agent-matchning med leverantörspublicerade IP-intervall eller verifierad omvÀnd DNS dÀr tillgÀngligt. OpenAI och Perplexity publicerar IP-information för sina relevanta botar och hÀmtare. (openai.com)

Reproducerbar loggsammanfattare

Spara följande som 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])

Kör den med:

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

data/ai_crawler_daily.csv

Detta ger operationella bevis om hÀmtningsaktivitet, inte mÀnsklig trafik.


6. Anpassade lÀnkkortare för AI-riktade sammanfattningar

En förstaparts-lÀnkkortare tillhandahÄller en kontrollerad mÀtpunkt nÀr du publicerar sammanfattningar eller kÀllmaterial som kan refereras av AI-system.

AnvÀnd en domÀn du kontrollerar, till exempel:

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

Destinationen bör inkludera kampanjparametrar som:

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

Google rekommenderar kampanjparametrar för att identifiera trafikkÀllor och kampanjer. Google Analytics kampanj-URL-dokumentation (support.google.com)

Rekommenderade lÀnkfÀlt

text surface topic prompt_id asset_id content_version destination_path

LÀgg inte in personlig information, e-postadresser eller anvÀndares sökfrÄgor i lÀnken. Kampanjidentifierare bör beskriva den publicerade tillgÄngen, inte identifiera en person.

Viktiga begrÀnsningar

En kort lÀnk Àr ett kalibreringsverktyg, inte en universell lösning:

  • Assistenten kan skriva om destinations-URL:en.
  • Assistenten kan istĂ€llet citera den kanoniska URL:en.
  • LĂ€nken kan visas men aldrig klickas.
  • En webblĂ€sare eller integritetsverktyg kan ta bort hĂ€nvisaren.
  • Vissa system kan hĂ€mta destinationen serversidan.
  • En kort lĂ€nk mĂ€ter inte anvĂ€ndare som minns varumĂ€rket och Ă„tervĂ€nder senare.

AnvÀnd inte en kortare för att dölja destinationen, kringgÄ sÀkerhetssystem eller manipulera citeringar. AnvÀnd en tydlig omdirigering till en relevant, kanonisk sida.

Minimal förstaparts-lÀnkkortare

Följande exempel anvÀnder FastAPI och SQLite. Skydda lÀnkskapande endpoint med autentisering innan den anvÀnds i produktion.

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)

Kör den med:

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

AnvÀnd en 302-omdirigering för mÀtlÀnkar sÄ att webblÀsare och mellansystem Àr mindre benÀgna att cachelagra omdirigeringen permanent.


7. Schemalagd skrapning av AI-svarspaneler

Panel-skrapning Àr anvÀndbart för att besvara frÄgor som förstapartanalyser kanske inte exponerar:

  • Vilka konkurrerande kĂ€llor citeras?
  • Vilken sida citeras för en specifik prompt?
  • Ändras svaret beroende pĂ„ land eller enhet?
  • Förekommer citeringen i svaret, kĂ€lllistan eller bĂ„da?
  • Hur stabilt Ă€r svaret över tid?
  • Citerar assistenten en kort lĂ€nk eller den kanoniska URL:en?

Skrapning mÄste dock göras försiktigt.

Regler för ansvarsfull insamling

  1. Föredra ett officiellt API, licensierad sökdataleverantör eller förstapartsrapport.
  2. Granska leverantörens villkor innan du automatiserar förfrÄgningar.
  3. Respektera robots-instruktioner dÀr det Àr tillÀmpligt.
  4. KringgÄ inte CAPTCHA:s, inloggningshinder, hastighetsbegrÀnsningar eller bot-skydd.
  5. AnvÀnd en liten, fast promptuppsÀttning.
  6. Kör med lÄg frekvens.
  7. Spara svaret och tidsstÀmpeln sÄ att resultaten kan granskas.
  8. Stoppa nÀr en leverantör returnerar en utmaning eller ett nekad Ätkomst-svar.

Protokoll för exkludering av robotar Àr en preferensmekanism för botar, inte ett sÀkerhetssystem. Det auktoriserar inte Ätkomst till skyddat innehÄll. RFC 9309: Protokoll för exkludering av robotar (rfc-editor.org)

Bygg ett prompt-manifest

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

HÄll prompterna stabila. Om prompterna Àndras blir trendjÀmförelser mindre tillförlitliga.

Lagra Àven:

  • land
  • sprĂ„k
  • enhetstyp
  • webblĂ€sarversion
  • promptversion
  • innehĂ„llsversion
  • insamlingstid i UTC

Google noterar att AI Overview och AI Mode-svar kan variera eftersom de anvÀnder olika system och kan anvÀnda relaterade sökningar eller "query fan-out." Googles AI-funktioner och webbplatsdokumentation (developers.google.com)

Playwright insamlingsskript

Följande skript Àr ett allmÀnt forskningsverktyg. Det registrerar den renderade sidan, skÀrmbilden, sidlÀnkar och svarstext. Det kringgÄr avsiktligt inte utmaningar eller försöker besegra Ätkomstkontroller.

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) + "\

") output.flush()

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

    await browser.close()

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

Installera och kör:

bash pip install playwright playwright install chromium python scrape_panels.py

SchemalÀgg det med ett lÄs sÄ att tvÄ körningar inte kan överlappa varandra:

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

FörbÀttra noggrannheten med yt-adaptrar

Fallbacks-skriptet registrerar lÀnkar pÄ sidnivÄ. Detta Àr anvÀndbart för upptÀckt men kan inkludera vanliga sökresultatlÀnkar. För produktionsanvÀndning:

  • AnvĂ€nd ett licensierat svarspanel-API nĂ€r det Ă€r tillgĂ€ngligt.
  • Skapa en separat vĂ€ljar-adapter för varje yta.
  • Lagra vĂ€ljarversionen.
  • Lagra rĂ„ HTML och en skĂ€rmbild.
  • Markera varje post med link_scope = "panel_selector" eller link_scope = "page_fallback".

BerÀkna endast citeringsandel frÄn panel_selector-data nÀr du pÄ ett tillförlitligt sÀtt kan identifiera panelen. HÄll fallback-observationer i en separat utforskande rapport.


8. Google Analytics och BigQuery-integration

Google Analytics 4 kan exportera rÄa hÀndelsedata till BigQuery. Dagliga exporter ger stabila tabeller som events_YYYYMMDD; streamingexporter Àr anvÀndbara för nÀra realtidsövervakning men kan vara ofullstÀndiga tills den dagliga tabellen Àr fÀrdigstÀlld. Google Analytics BigQuery Export (support.google.com)

En förenklad sessionsfrÄga kan se ut sÄ hÀr:

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 en mogen implementering, lÀgg till surface, topic och prompt_id till kampanjstrukturen eller koppla dem frÄn klick-tabellen för lÀnkkortaren.

Skapa inte en dimension med hög kardinalitet för varje enskild anvÀndare eller session. Google varnar för att alltför mÄnga anpassade dimensioner med hög kardinalitet kan minska rapporteringskvaliteten. Google Analytics anpassade dimensioner och mÀtvÀrden (support.google.com)


9. Dashboard-design

En praktisk instrumentpanel bör innehÄlla fem vyer.

Sammanfattning för ledningen

Visa:

  • Citeringsandel per Ă€mne
  • Citeringsomfattning per Ă€mne
  • BekrĂ€ftad assistenthĂ€nvisningsfrekvens
  • Utökad assistenthĂ€nvisningsfrekvens
  • Engagemang efter citering
  • AI-botars framgĂ„ngsfrekvens
  • AI-tillskrivna nyckelhĂ€ndelser eller intĂ€kter

Trend för citeringsandel

Linjediagram:

text Datum × Ämne × Yta

Visa bÄda:

  • förekomster av citeringar av mĂ„ldomĂ€nen
  • alla observerade citeringsförekomster

Assistenttrafiktratt

text Observerade citeringar ↓ Klick pĂ„ kontrollerade lĂ€nkar ↓ AI-tillskrivna sessioner ↓ Engagerade AI-sessioner ↓ NyckelhĂ€ndelser

Varje steg bör visa sin attribution-konfidens.

Bot-status

Tabell per bot:

text bot_name requests successful_requests error_rate unique_ip_count top_paths

Varna nÀr:

  • felprocenten överstiger 2 procent
  • en bot plötsligt försvinner
  • en tidigare tillgĂ€nglig innehĂ„llssektion returnerar 403 eller 429
  • bot-förfrĂ„gningar koncentreras till en oavsiktlig privat sökvĂ€g

Tabell för innehÄllsmöjligheter

Rankningssidor och Àmnen efter:

text hög citeringsandel + lÄg hÀnvisningsfrekvens höga exponeringar + lÄg citeringsomfattning högt engagemang + lÄg citeringssynlighet höga botfel + hög affÀrsmÀssiga betydelse

Dessa kombinationer ger vanligtvis bÀttre beslut Àn att bara titta pÄ trafikvolym.

Minimal Streamlit-instrumentpanel

Spara som 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) )

Kör den med:

bash pip install streamlit pandas streamlit run dashboard.py

För produktion, ladda normaliserade tabeller till DuckDB, BigQuery, Snowflake eller PostgreSQL istÀllet för att lÀsa rÄa filer direkt.


10. Kvalitetskontroller

Ett mÀtsystem kan producera exakt utseende men missvisande siffror. LÀgg till dessa kontroller.

Separera botar frÄn mÀnniskor

RÀkna aldrig OAI-SearchBot, GPTBot, ClaudeBot eller PerplexityBot som mÀnskliga sessioner. Dessa Àr signaler frÄn botar eller hÀmtningar.

Bevara rÄa bevis

För varje panelkörning, behÄll:

  • rĂ„ HTML
  • skĂ€rmbild
  • svarstext
  • tidsstĂ€mpel
  • webblĂ€sar- och samlarversion
  • prompt och lokalisering
  • svarshash

Detta gör det möjligt att förklara varför ett mÄtt Àndrades.

SpÄra panelens frÄnvaro

En saknad AI-panel Àr inte samma sak som ett svar utan din citering.

AnvÀnd:

text panel_not_present panel_present_no_target panel_present_target_cited blocked_or_challenged collection_error

Kontrollera samplingsbias

En schemalagd skrapning pÄverkas av:

  • plats
  • enhet
  • sprĂ„k
  • tid
  • personalisering
  • webblĂ€sarstatus
  • promptens formulering

AnvÀnd en fast kÀrnuppsÀttning av prompter och en roterande uppsÀttning utforskande prompter. HÄll de tvÄ grupperna separata i rapporter.

Undvik falsk precision

Rapportera konfidensintervall eller minsta sampelstorlekar för smÄ Àmnesgrupper. Ett Àmne med tre observationer bör inte jÀmföras direkt med ett Àmne som innehÄller 300 observationer.

JÀmför lika med lika

NÀr du jÀmför ytor, hÄll konstant:

  • prompt
  • lokalisering
  • land
  • enhet
  • insamlingstimme
  • regler för mĂ„ldomĂ€n
  • version av citeringsparser

11. Rekommenderad utrullningsplan

Vecka 1: Instrumentering

  • Konfigurera strukturerade serverloggar.
  • Skapa botregistret.
  • Skapa lĂ€nkkortare-domĂ€nen.
  • LĂ€gg till regler för kampanjnamngivning.
  • Definiera Ă€mnestaxonomin.
  • Aktivera Google Analytics 4 BigQuery-export.

Vecka 2: Kontrollerade lÀnkar

  • Publicera fem till tio spĂ„rade sammanfattningar.
  • Skapa lĂ€nkar för olika Ă€mnen och innehĂ„llsversioner.
  • Validera omdirigeringsloggar mot analyssessioner.
  • BekrĂ€fta att ingen personlig information hamnar i URL:er eller analysparametrar.

Vecka 3: Panelobservationer

  • Skapa ett prompt-manifest.
  • Börja med tvĂ„ ytor och 20 till 50 stabila prompter.
  • Kör en gĂ„ng per dag.
  • Spara skĂ€rmbilder och HTML.
  • MĂ€t insamlingsfel separat frĂ„n citeringens frĂ„nvaro.

Vecka 4: Instrumentpanel och styrning

  • Bygg instrumentpaneler för Ă€mnen och ytor.
  • LĂ€gg till bot-statusvarningar.
  • Definiera konfidensregler.
  • JĂ€mför förstapartrapporter med panelobservationer.
  • Dokumentera policyer för lagring, Ă„tkomst och radering.

Slutsats

AI-citeringsmÀtning bör byggas som ett fler-signalstillskrivningssystem, inte som en enkel hÀnvisningsrapport.

Den mest tillförlitliga ramen kombinerar:

  • Googles och Bings förstapartsrapporter för synlighet
  • serverloggar för bot- och hĂ€mtningsaktivitet
  • förstaparts-kortlĂ€nkar för kontrollerad klickmĂ€tning
  • schemalagda, kompatibla observationer av svarspaneler
  • analys- och konverteringsdata för nedströmsvĂ€rde

De tre viktigaste mÀtvÀrdena Àr:

  1. Citeringsandel per Ă€mne — hur stor del av det observerade kĂ€llutrymmet som tillhör din domĂ€n.
  2. AssistenthĂ€nvisningsfrekvens — hur mycket mĂ€nsklig trafik som kan tillskrivas AI-ytor.
  3. Engagemang efter citering — om AI-hĂ€nvisade besökare utför meningsfulla Ă„tgĂ€rder.

Systemet blir anvÀndbart nÀr varje nummer inkluderar dess yta, tidsperiod, nÀmnare, attribueringsmetod och konfidensnivÄ. Denna disciplin förhindrar att bot-trÀffar misstas för anvÀndare, att skrapade svar misstas för exponeringar och att citeringar misstas för affÀrsresultat.

Relaterade artiklar

Gillar du detta innehÄll?

Prenumerera pÄ vÄrt nyhetsbrev för de senaste insikterna om innehÄllsmarknadsföring och tillvÀxtguider.

Denna artikel Àr endast i informationssyfte. InnehÄll och strategier kan variera beroende pÄ dina specifika behov.
MÀta AI-citeringspÄverkan: Analysdata, loggar och skrapning av svarspaneler | AutoPod