AutoPodAutoPod

Dirbtinio intelekto citavimo poveikio matavimas: analizė, žurnalai ir skydelių nuskaitymas

•22 min. skaitymo
Audio straipsnis
Dirbtinio intelekto citavimo poveikio matavimas: analizė, žurnalai ir skydelių nuskaitymas
0:00—:—
Dirbtinio intelekto citavimo poveikio matavimas: analizė, žurnalai ir skydelių nuskaitymas

Dirbtinio intelekto citavimo poveikio matavimas: analizė, žurnalai ir skydelių nuskaitymas

Įvadas

Dirbtinio intelekto (DI) valdomi atradimai dabar skirstomi į kelis matavimo lygmenis. Žmogus gali pamatyti įmonę, cituojamą DI atsakyme, spustelėti nuorodą be jokio naudingo nukreipimo, apsilankyti per naršyklę, kuri pašalina kampanijos duomenis, arba perskaityti atsakymą visai nespaudžiant. Tuo tarpu DI tikrintuvas (crawler) gali paprašyti įmonės puslapio neatstovaudamas žmogaus lankytojo.

Naudinga matavimo sistema turi atskirti keturis klausimus:

  1. Ar svetainė buvo nuskaityta ar gauta?
  2. Ar svetainė buvo cituojama DI atsakyme?
  3. Ar asmuo apsilankė svetainėje iš to atsakymo?
  4. Ar tas apsilankymas sukėlė prasmingą įsitraukimą ar pajamas?

2026 m. rugsėjo 22 d. duomenimis, pirminių šaltinių ataskaitos gerėja. „Google Search Console“ pateikia generatyvinio DI našumo ataskaitą apie DI apžvalgas (AI Overviews) ir DI režimą (AI Mode), įskaitant parodymus, puslapius, šalis, įrenginius ir datas. Ataskaita tapo prieinama visame pasaulyje 2026 m. rugpjūčio 31 d.. „Google“ generatyvinio DI našumo ataskaita (support.google.com)

„Microsoft“ taip pat teikia DI našumo (AI Performance) ataskaitą „Bing Webmaster Tools“ įrankiuose. Jo vieša peržiūra pateikia bendras citatas, cituotus puslapius, pagrindinius klausimus (grounding queries) ir citavimo tendencijas visose palaikomose „Microsoft AI“ patirtyse. „Bing Webmaster Tools“ DI našumas (blogs.bing.com)

Šios priemonės turėtų būti laikomos autoritetingomis savo sritims, tačiau jos nesukuria visapusiškos tarpasistentinės matavimo sistemos. Rekomenduojamas metodas yra derinti pirminių šaltinių ataskaitas, serverio žurnalus, kontroliuojamas stebėjimo nuorodas, analizės eksportus ir suplanuotus atsakymų skydelių stebėjimus.


1. Kodėl DI nukreipimo matavimas yra sudėtingas

Tradicinė žiniatinklio analizė labai priklauso nuo Referer antraštės ir kampanijos parametrų. Abu gali trūkti arba būti nepilni.

HTTP Referer antraštėje gali būti ankstesnis puslapis, tik ankstesnio tinklalapio šaltinis, arba visai jokios vertės. Naršyklės nukreipimo politikos (referrer policies) gali tyčia apriboti arba pašalinti šią informaciją. MDN Referrer-Policy dokumentacija (developer.mozilla.org)

DI sistemos sukuria papildomų komplikacijų:

  • Asistentas gali susieti su puslapiu, nesiųsdamas atpažįstamo nukreipėjo.
  • Asistentas gali naudoti nukreipiantįjį arba perrašytą URL.
  • Asistentas gali gauti turinį serverio pusėje, prieš parodydamas atsakymą.
  • DI tikrintuvas gali apsilankyti puslapyje, kurio nematys joks žmogus.
  • Atsakyme gali būti nurodytas kanoninis URL, o ne originaliai gautas URL.
  • Atsakymas gali skirtis priklausomai nuo vietos, įrenginio, kalbos, laiko ar personalizavimo.

Pavyzdžiui, „OpenAI“ skiria OAI-SearchBot paieškos atradimams, GPTBot galimam apmokymui ir ChatGPT-User vartotojo inicijuotiems veiksmams. „Perplexity“ atskirai identifikuoja PerplexityBot ir Perplexity-User. „Anthropic“ identifikuoja ClaudeBot, Claude-SearchBot ir Claude-User. Šių užklausų nereikėtų jungti į vieną bendrą „DI srauto“ skaičių. „OpenAI“ tikrintuvo dokumentacija (openai.com) „Perplexity“ tikrintuvo dokumentacija (docs.perplexity.ai) „Anthropic“ tikrintuvo dokumentacija (support.anthropic.com)

Pagrindinė taisyklė yra:

Tikrintuvo veikla nėra nukreipiamasis srautas, citavimo matomumas nėra srautas, o srautas nėra verslo poveikis.


2. Matavimo sistema

Naudokite penkis matavimo lygmenis.

LygmuoPagrindinis klausimasRekomenduojami duomenys
PrieigaAr DI sistema paprašė puslapio?Žiniatinklio serverio žurnalai, ugniasienės žurnalai
MatomumasAr puslapis buvo parodytas arba cituojamas?„Google Search Console“, „Bing Webmaster Tools“, atsakymų skydelių stebėjimai
PritraukimasAr asmuo atvyko iš DI paviršiaus?Nukreipėjas, kampanijos parametrai, trumpos nuorodos paspaudimai, analizė
ĮsitraukimasAr lankytojas prasmingai sąveikavo?Analitikos įvykiai, puslapio peržiūros, įsitraukimo laikas
VertėAr apsilankymas davė norimą rezultatą?Registracijos, užklausos, pirkimai, prenumeratos

Rekomenduojama tiesos šaltinio hierarchija

Kiekvienam klausimui naudokite stipriausią prieinamą signalą:

  1. Pirminių platformų ataskaitos parodymams ir citatoms.
  2. Atsakymų skydelių stebėjimai raginimo lygio matomumui ir konkurenciniams palyginimams.
  3. Nukreipimo žurnalai kontroliuojamiems nuorodų paspaudimams.
  4. Žiniatinklio analizė seansams, įsitraukimui ir konversijoms.
  5. Serverio žurnalai tikrintuvo ir duomenų gautuvų veiklai.
  6. Modeliuojami įverčiai tik tada, kai nėra tiesioginių įrodymų.

Nejunkite visų signalų į vieną skaičių neatsižvelgiant į šaltinį ir patikimumo lygį.


3. Pagrindinis duomenų modelis

Sukurkite duomenų saugyklą su atskiromis lentelėmis neapdorotiems stebėjimams ir išvestinėms metrikoms.

panel_runs

Viena eilutė kiekvienam raginimui ir paviršiaus patikrinimui.

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

Viena eilutė kiekvienai nuorodai, pastebėtai atsakymo skydelyje.

text run_id surface topic prompt_id cited_url cited_domain citation_position link_scope target_domain target_cited

ai_crawler_requests

Agreguoti serverio žurnalų duomenys.

text date bot_name bot_role path requests successful_requests error_requests unique_ip_count

shortener_clicks

Viena eilutė kiekvienai kontroliuojamos nuorodos užklausai.

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

ai_sessions

Normalizuota analizės lentelė.

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

Svarbus yra confidence laukas. Rekomenduojamos vertės yra:

  • A – Tiesioginis: kontroliuojamas trumpos nuorodos paspaudimas arba patikimas kampanijos parametras.
  • B – Stiprus: žinomas asistento nukreipėjas arba patvirtinta vartotojo atsiuntimo užklausa.
  • C – Tikėtinas: kampanijos ar nukreipimo puslapio įrodymai be išsamaus nukreipėjo.
  • D – Modeliuotas: išvestas iš laiko, skydelių stebėjimų ar bendrų pokyčių.

Ataskaitose A ir B atskirkite nuo C ir D.


4. Metrikos apibrėžimai

Citavimo dalis pagal temą

Citavimo dalis pagal temą matuoja, kaip dažnai tikslinis domenas pasirodo tarp šaltinių, pastebėtų konkrečiai temai.

[ \text{Citation Share}_{topic}

\frac{\text{Tikslinio domeno citatų pasikartojimai}} {\text{Visų citatų pasikartojimai}} ]

Pavyzdys:

  • 100 visų citatų, pastebėtų temai „projektų valdymo programinė įranga“
  • 12 citatų rodo į jūsų domeną
  • Citavimo dalis pagal temą = 12%

Ši metrika matuoja šaltinio dalį, o ne tai, ar atsakyme jūsų svetainė buvo bent kartą.

Citavimo aprėptis

Naudokite antrą metriką atsakymo lygio buvimui:

[ \text{Citation Coverage}_{topic}

\frac{\text{Atsakymų vykdymas, kuriame yra tikslinė citata}} {\text{Atsakymų vykdymas, kuriame buvo skydelis}} ]

Citavimo aprėptį dažnai lengviau suprasti netechninei auditorijai:

„Mūsų svetainė pasirodė 34% pastebėtų DI atsakymų šia tema.“

Citavimo pozicija

Įrašykite citatos poziciją atsakyme:

  • 1 = pirmas pastebėtas šaltinis
  • 2 = antras pastebėtas šaltinis
  • 3 = trečias pastebėtas šaltinis

Kadangi DI sąsajos dažnai grupuoja ar perrikiuoja šaltinius, poziciją laikykite aprašomuoju stebėjimu, o ne universaliu reitingavimu.


Asistento nukreipimo rodiklis

Asistento nukreipimo rodiklis matuoja žmogaus svetainės seansų dalį, kurią galima priskirti DI paviršiui.

[ \text{Assistant Referral Rate}

\frac{\text{DI priskirti žmogaus seansai}} {\text{Visi žmogaus seansai}} ]

Praneškite bent dvi versijas:

text Patvirtintas asistento nukreipimo rodiklis = A-patikimumo DI seansai / visi žmogaus seansai

Išplėstinis asistento nukreipimo rodiklis = A + B + C-patikimumo DI seansai / visi žmogaus seansai

Patvirtinta versija yra konservatyvesnė. Išplėstinė versija naudinga tendencijų analizei, bet neturėtų būti pristatoma kaip visiškai stebimas srautas.

Nenaudokite skydelių vykdymų kaip parodymų

Jei nuskaitote 500 raginimų, tai nereiškia, kad 500 žmonių pamatė tuos atsakymus. Jūsų skenuoklis matuoja kontroliuojamą atsakymų būsenų pavyzdį. Naudokite pirminių platformų parodymų ataskaitas, kai jos prieinamos.


Įsitraukimas po citavimo

Įsitraukimas po citavimo matuoja, ar žmonės, atvykę per DI citatą, atliko prasmingą veiksmą.

[ \text{Post-Citation Engagement}

\frac{\text{Įsitraukę DI seansai}} {\text{DI priskirti seansai}} ]

Pagal „Google Analytics 4“ įsitraukęs seansas trunka ilgiau nei 10 sekundžių, apima pagrindinį įvykį arba apima bent dvi puslapio ar ekrano peržiūras. „Google Analytics“ įsitraukimo rodiklio dokumentacija (support.google.com)

Taip pat praneškite:

text Įsitraukimo po citavimo laikas = vidutinis įsitraukimo sekundžių skaičius vienam DI seansui

Konversijų rodiklis po citavimo = DI seansai su pagrindiniu įvykiu / DI priskirti seansai

Pajamos po citavimo per seansą = pajamos iš DI seansų / DI priskirti seansai

Vidutinis įsitraukimo laikas paprastai yra stabilesnis nei vidutinis įsitraukimo laikas, nes nedidelis labai ilgų seansų skaičius gali iškraipyti vidurkį.


Papildomos naudingos metrikos

DI tikrintuvo sėkmės rodiklis

[ \text{Crawler Success Rate}

\frac{\text{DI tikrintuvo užklausos, grąžinančios 2xx arba 3xx}} {\text{DI tikrintuvo užklausos}} ]

Suskaidykite tai pagal botą ir kelią. Didelis citavimo sumažėjimas, kartu su didėjančiu tikrintuvo klaidų rodikliu, gali rodyti prieglobos, ugniasienės ar robotų konfigūracijos problemą.

Citavimų ir paspaudimų rodiklis

Kontroliuojamiems atsakymų skydelių stebėjimams:

[ \text{Observed Citation-to-Click Rate}

\frac{\text{Patvirtinti trumpos nuorodos paspaudimai}} {\text{Pastebėtos tikslinės citatos}} ]

Tai nėra tikras vartotojo paspaudimų rodiklis, nebent vardiklis atspindi faktinius vartotojų parodymus. Aiškiai pažymėkite tai kaip stebimą operacinę metriką.

Citavimo šviežumas

Išmatuokite, kaip dažnai cituojamas URL atspindi dabartinę puslapio versiją.

text Citavimo šviežumas = cituojami puslapiai su dabartine turinio maišos verte / cituojami puslapiai

Tai naudinga dažnai besikeičiantiems dokumentacijos, kainodaros, produktų ir politikos puslapiams.


5. Serverio žurnalų analizė

Sukonfigūruokite struktūrizuotus prieigos žurnalus

Struktūrizuotus žurnalus lengviau analizuoti nei tradicinius, tarpu atskirtus žurnalus.

Pavyzdys „Nginx“ konfigūracijai:

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;

Logų failus saugokite apribotoje vietoje. Nesiųskite neapdorotų IP adresų į bendrą analizės lentelę, nebent turite aiškią veiklos priežastį.

Nustatykite DI užklausų tipus

Bent jau atskirkite:

text OpenAI:

  • OAI-SearchBot
  • ChatGPT-User
  • GPTBot

Anthropic:

  • Claude-SearchBot
  • Claude-User
  • ClaudeBot

Perplexity:

  • PerplexityBot
  • Perplexity-User

Paieškos sistemos:

  • Googlebot
  • Bingbot

„Google“ DI paieškos funkcijos paprastai naudoja esamus „Google“ paieškos nuskaitymo valdiklius, o ne atskirą universalų „AI Overview bot“. „Google“ teigia, kad „Googlebot“ kontroliuoja prieigą paieškai, įskaitant DI funkcijas. „Google“ DI funkcijos ir svetainės (developers.google.com)

Vartotojo agento eilutės (User-agent strings) gali būti suklastotos. Priimant veiklos sprendimus, derinkite vartotojo agento atitikimą su paslaugos teikėjo paskelbtais IP diapazonais arba patikrintu atvirkštiniu DNS, jei toks yra. „OpenAI“ ir „Perplexity“ skelbia IP informaciją apie savo atitinkamus tikrintuvus ir duomenų gautuvus. (openai.com)

Atkūriamasis žurnalų suvestinės kūrėjas

Išsaugokite šį failą kaip 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])

Paleiskite tai su:

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

data/ai_crawler_daily.csv

Tai suteikia operatyvinių įrodymų apie gavimo veiklą, o ne žmogaus srautą.


6. Individualūs nuorodų trumpintojai DI skirtoms suvestinėms

Pirminės šalies nuorodų trumpintojas suteikia kontroliuojamą matavimo tašką, kai skelbiate suvestines ar šaltinio medžiagą, į kurią gali būti nukreipiama DI sistemų.

Naudokite jums priklausantį domeną, pavyzdžiui:

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

Paskirties vietoje turėtų būti kampanijos parametrai, tokie kaip:

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

„Google“ rekomenduoja kampanijos parametrus, kad būtų galima nustatyti srauto šaltinius ir kampanijas. „Google Analytics“ kampanijos URL dokumentacija (support.google.com)

Rekomenduojami nuorodos laukai

text surface topic prompt_id asset_id content_version destination_path

Nededėkite asmeninės informacijos, el. pašto adresų ar vartotojo paieškos užklausų į nuorodą. Kampanijos identifikatoriai turėtų aprašyti paskelbtą turtą, o ne identifikuoti asmenį.

Svarbūs apribojimai

Trumpa nuoroda yra kalibravimo priemonė, o ne universalus sprendimas:

  • Asistentas gali perrašyti paskirties URL.
  • Asistentas gali cituoti kanoninį URL vietoj to.
  • Nuoroda gali būti rodoma, bet niekada nepaspausta.
  • Naršyklė arba privatumo įrankis gali pašalinti nukreipėją.
  • Kai kurios sistemos gali atsiųsti paskirties vietą serverio pusėje.
  • Trumpa nuoroda nematuoja vartotojų, kurie prisimena prekės ženklą ir grįžta vėliau.

Nenaudokite trumpintojo, kad paslėptumėte paskirties vietą, apeitumėte saugumo sistemas ar manipuliuotumėte citatomis. Naudokite aiškų nukreipimą į atitinkamą, kanoninį puslapį.

Minimalus pirminės šalies trumpintojas

Šiame pavyzdyje naudojamas FastAPI ir SQLite. Prieš naudojant gamyboje, apsaugokite nuorodų kūrimo galinį tašką autentifikavimu.

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)

Paleiskite tai su:

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

Naudokite 302 nukreipimą matavimo nuorodoms, kad naršyklės ir tarpinės sistemos mažiau tikėtina nuolat talpintų nukreipimą į talpyklą.


7. Suplanuotas DI atsakymų skydelių nuskaitymas

Skydelių nuskaitymas yra naudingas atsakant į klausimus, kurių pirminės šalies analizė gali neatskleisti:

  • Kokie konkuruojantys šaltiniai cituojami?
  • Kuris puslapis cituojamas konkrečiam raginimui?
  • Ar atsakymas keičiasi priklausomai nuo šalies ar įrenginio?
  • Ar citata pasirodo atsakyme, šaltinių sąraše, ar abiejuose?
  • Kiek stabilus atsakymas laikui bėgant?
  • Ar asistentas cituoja trumpą nuorodą, ar kanoninį URL?

Tačiau nuskaitymas turi būti atliekamas atsargiai.

Atsakingo duomenų rinkimo taisyklės

  1. Teikite pirmenybę oficialiam API, licencijuotam paieškos duomenų teikėjui ar pirminės šalies ataskaitai.
  2. Prieš automatizuojant užklausas, peržiūrėkite teikėjo sąlygas.
  3. Gerbkite robotų instrukcijas, kai taikoma.
  4. Neaplenkite CAPTCHA, prisijungimo kliūčių, užklausų limitų ar botų apsaugos.
  5. Naudokite nedidelį, fiksuotą raginimų rinkinį.
  6. Vykdykite žemu dažniu.
  7. Išsaugokite atsakymą ir laiko žymę, kad rezultatus būtų galima patikrinti.
  8. Sustokite, kai teikėjas grąžina iššūkį arba prieigos uždraudimo atsakymą.

Robotų išskyrimo protokolas (Robots Exclusion Protocol) yra tikrintuvo pirmenybės mechanizmas, o ne saugumo sistema. Jis neleidžia prieigos prie apsaugoto turinio. RFC 9309: Robotų išskyrimo protokolas (rfc-editor.org)

Sukurkite raginimų 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

Išlaikykite raginimus stabilius. Jei raginimai keičiasi, tendencijų palyginimai tampa mažiau patikimi.

Taip pat saugokite:

  • šalį
  • kalbą
  • įrenginio tipą
  • naršyklės versiją
  • raginimo versiją
  • turinio versiją
  • rinkimo laiką UTC formatu

„Google“ pažymi, kad „AI Overview“ ir „AI Mode“ atsakymai gali skirtis, nes jie naudoja skirtingas sistemas ir gali naudoti susijusias paieškas arba „query fan-out“. „Google“ DI funkcijos ir svetainių dokumentacija (developers.google.com)

„Playwright“ rinkimo scenarijus

Šis scenarijus yra bendrosios paskirties tyrimo įrankis. Jis įrašo atvaizduotą puslapį, ekrano kopiją, puslapio nuorodas ir atsakymo tekstą. Jis tyčia neaplenkia iššūkių ir nebanda įveikti prieigos kontrolės.

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

Įdiekite ir paleiskite:

bash pip install playwright playwright install chromium python scrape_panels.py

Suplanuokite tai su užraktu, kad du vykdymo seansai nepersidengtų:

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

Pagerinkite tikslumą naudodami paviršiaus adapterius

Atsarginis scenarijus įrašo puslapio lygio nuorodas. Tai naudinga atradimams, bet gali apimti įprastas paieškos rezultatų nuorodas. Gamybai naudokite:

  • Naudokite licencijuotą atsakymo skydelių API, kai ji prieinama.
  • Sukurkite atskirą selektoriaus adapterį kiekvienam paviršiui.
  • Išsaugokite selektoriaus versiją.
  • Išsaugokite neapdorotą HTML ir ekrano kopiją.
  • Kiekvieną įrašą pažymėkite link_scope = "panel_selector" arba link_scope = "page_fallback".

Citavimo dalį skaičiuokite tik iš panel_selector duomenų, kai galite patikimai identifikuoti skydelį. Atsarginius stebėjimus laikykite atskiroje bandomojoje ataskaitoje.


8. „Google Analytics“ ir „BigQuery“ integravimas

„Google Analytics 4“ gali eksportuoti neapdorotus įvykių duomenis į „BigQuery“. Kasdieniai eksportai teikia stabilias lenteles, pvz., events_YYYYMMDD; srautiniai eksportai yra naudingi beveik realaus laiko stebėsenai, tačiau gali būti neišsamūs, kol nebus baigtos kasdienės lentelės. „Google Analytics BigQuery“ eksportas (support.google.com)

Paprastesnė seanso užklausa galėtų atrodyti taip:

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;

Brandžiam įgyvendinimui, pridėkite surface, topic ir prompt_id prie kampanijos struktūros arba sujunkite juos iš nuorodų trumpintojo paspaudimų lentelės.

Nekurkite didelio kardinalumo analizės matmens kiekvienam atskiram vartotojui ar seansui. „Google“ įspėja, kad pernelyg didelės kardinalumo pasirinktinės dimensijos gali sumažinti ataskaitų kokybę. „Google Analytics“ pasirinktinės dimensijos ir metrikos (support.google.com)


9. Prietaisų skydelio dizainas

Praktiškas prietaisų skydelis turėtų turėti penkis rodinius.

Santrauka vadovybei

Rodyti:

  • Citavimo dalis pagal temą
  • Citavimo aprėptis pagal temą
  • Patvirtintas asistento nukreipimo rodiklis
  • Išplėstinis asistento nukreipimo rodiklis
  • Įsitraukimas po citavimo
  • DI tikrintuvo sėkmės rodiklis
  • DI priskirti pagrindiniai įvykiai ar pajamos

Citavimo dalies tendencija

Linijinė diagrama:

text Data × Tema × Paviršius

Rodyti abu:

  • tikslinio domeno citatų pasikartojimai
  • visi pastebėti citatų pasikartojimai

Asistento srauto piltuvėlis

text Pastebėtos citatos ↓ Kontroliuojami nuorodų paspaudimai ↓ DI priskirti seansai ↓ Įsitraukę DI seansai ↓ Pagrindiniai įvykiai

Kiekviename etape turėtų būti rodomas jo priskyrimo patikimumas.

Tikrintuvo būklė

Lentelė pagal botą:

text bot_name requests successful_requests error_rate unique_ip_count top_paths

Įspėti, kai:

  • klaidų rodiklis viršija 2 procentus
  • botas staiga dingsta
  • anksčiau prieinama turinio dalis grąžina 403 arba 429
  • tikrintuvo užklausos koncentruojasi į netinkamą privatų kelią

Turinio galimybių lentelė

Rikiuokite puslapius ir temas pagal:

text didelė citavimo dalis + mažas nukreipimo rodiklis dideli parodymai + maža citavimo aprėptis didelis įsitraukimas + mažas citavimo matomumas dideli tikrintuvo klaidų skaičiai + didelė verslo svarba

Šios kombinacijos paprastai leidžia priimti geresnius sprendimus nei vien srauto apimties stebėjimas.

Minimalus „Streamlit“ prietaisų skydelis

Išsaugokite kaip 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) )

Paleiskite tai su:

bash pip install streamlit pandas streamlit run dashboard.py

Gamybai, normalizuotas lenteles įkelkite į „DuckDB“, „BigQuery“, „Snowflake“ ar „PostgreSQL“, o ne tiesiogiai skaitykite neapdorotus failus.


10. Kokybės kontrolė

Matavimo sistema gali pateikti tikslius, bet klaidinančius skaičius. Pridėkite šiuos valdiklius.

Atskirkite botus nuo žmonių

Niekada neskaičiuokite OAI-SearchBot, GPTBot, ClaudeBot ar PerplexityBot kaip žmogaus seansų. Tai yra tikrintuvo arba duomenų gavimo signalai.

Išsaugokite neapdorotus įrodymus

Kiekvienam skydelio vykdymui išsaugokite:

  • neapdorotą HTML
  • ekrano kopiją
  • atsakymo tekstą
  • laiko žymę
  • naršyklės ir rinkiklio versiją
  • raginimą ir lokalę
  • atsakymo maišos vertę

Tai leidžia paaiškinti, kodėl metrika pasikeitė.

Sekite skydelio nebuvimą

Trūkstamas DI skydelis nėra tas pats, kas atsakymas be jūsų citatos.

Naudokite:

text panel_not_present panel_present_no_target panel_present_target_cited blocked_or_challenged collection_error

Kontroliuokite atrankos šališkumą

Suplanuotam nuskaitymui turi įtakos:

  • vieta
  • įrenginys
  • kalba
  • laikas
  • personalizavimas
  • naršyklės būsena
  • raginimo formuluotė

Naudokite fiksuotą pagrindinių raginimų rinkinį ir besikeičiantį tiriamųjų raginimų rinkinį. Ataskaitose abi grupes laikykite atskirai.

Venkite klaidingo tikslumo

Praneškite patikimumo intervalus arba minimalius imties dydžius mažoms temų grupėms. Tema su trimis stebėjimais neturėtų būti tiesiogiai lyginama su tema, kurioje yra 300 stebėjimų.

Lyginkite panašius dalykus

Lyginant paviršius, išlaikykite pastovius:

  • raginimą
  • lokalę
  • šalį
  • įrenginį
  • rinkimo valandą
  • tikslinio domeno taisykles
  • citavimo analizatoriaus versiją

11. Rekomenduojamas įdiegimo planas

1 savaitė: instrumentavimas

  • Sukonfigūruokite struktūrizuotus serverio žurnalus.
  • Sukurkite botų registrą.
  • Sukurkite trumpintojo domeną.
  • Pridėkite kampanijos pavadinimų taisykles.
  • Apibrėžkite temų taksonomiją.
  • Įgalinkite „Google Analytics 4 BigQuery“ eksportą.

2 savaitė: kontroliuojamos nuorodos

  • Paskelbkite penkias–dešimt stebimų suvestinių.
  • Sukurkite nuorodas skirtingoms temoms ir turinio versijoms.
  • Patvirtinkite nukreipimo žurnalus su analizės seansais.
  • Patvirtinkite, kad asmeninė informacija nepatenka į URL ar analizės parametrus.

3 savaitė: skydelių stebėjimai

  • Sukurkite raginimų manifestą.
  • Pradėkite nuo dviejų paviršių ir 20–50 stabilių raginimų.
  • Vykdykite kartą per dieną.
  • Išsaugokite ekrano kopijas ir HTML.
  • Matuokite rinkimo klaidas atskirai nuo citavimo nebuvimo.

4 savaitė: prietaisų skydelis ir valdymas

  • Sukurkite temų ir paviršių prietaisų skydelius.
  • Pridėkite tikrintuvo būklės įspėjimus.
  • Apibrėžkite patikimumo taisykles.
  • Palyginkite pirminių šaltinių ataskaitas su skydelių stebėjimais.
  • Dokumentuokite duomenų saugojimo, prieigos ir ištrynimo politikas.

Išvada

DI citavimo matavimas turėtų būti kuriamas kaip kelių signalų priskyrimo sistema, o ne kaip viena nukreipimo ataskaita.

Patikimiausia sistema derina:

  • „Google“ ir „Bing“ pirminės šalies matomumo ataskaitos
  • serverio žurnalai tikrintuvo ir duomenų gautuvų veiklai
  • pirminės šalies trumpos nuorodos kontroliuojamam paspaudimų matavimui
  • suplanuoti, atitinkantys atsakymų skydelių stebėjimai
  • analizės ir konversijų duomenys tolesnei vertei

Trys svarbiausios metrikos yra:

  1. Citavimo dalis pagal temą — kiek pastebėtos šaltinių erdvės priklauso jūsų domenui.
  2. Asistento nukreipimo rodiklis — kiek žmogaus srauto galima priskirti DI paviršiams.
  3. Įsitraukimas po citavimo — ar DI nukreipti lankytojai atlieka prasmingus veiksmus.

Sistema tampa naudinga, kai kiekvienas skaičius apima jo paviršių, laikotarpį, vardiklį, priskyrimo metodą ir patikimumo lygį. Ši disciplina neleidžia tikrintuvų paspaudimų supainioti su vartotojais, nuskaitytų atsakymų supainioti su parodymus ir citatų supainioti su verslo rezultatais.

Susiję straipsniai

Patinka šis turinys?

Prenumeruokite mūsų naujienlaiškį, kad gautumėte naujausias turinio rinkodaros įžvalgas ir augimo vadovus.

Šis straipsnis yra tik informacinio pobūdžio. Turinys ir strategijos gali skirtis priklausomai nuo jūsų specifinių poreikių.
Dirbtinio intelekto citavimo poveikio matavimas: analizė, žurnalai ir skydelių nuskaitymas | AutoPod