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:
- Ar svetainė buvo nuskaityta ar gauta?
- Ar svetainė buvo cituojama DI atsakyme?
- Ar asmuo apsilankė svetainėje iš to atsakymo?
- 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.
| Lygmuo | Pagrindinis klausimas | Rekomenduojami duomenys |
|---|---|---|
| Prieiga | Ar DI sistema paprašė puslapio? | Žiniatinklio serverio žurnalai, ugniasienės žurnalai |
| Matomumas | Ar puslapis buvo parodytas arba cituojamas? | „Google Search Console“, „Bing Webmaster Tools“, atsakymų skydelių stebėjimai |
| Pritraukimas | Ar asmuo atvyko iš DI paviršiaus? | Nukreipėjas, kampanijos parametrai, trumpos nuorodos paspaudimai, analizė |
| Įsitraukimas | Ar 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ą:
- Pirminių platformų ataskaitos parodymams ir citatoms.
- Atsakymų skydelių stebėjimai raginimo lygio matomumui ir konkurenciniams palyginimams.
- Nukreipimo žurnalai kontroliuojamiems nuorodų paspaudimams.
- Žiniatinklio analizė seansams, įsitraukimui ir konversijoms.
- Serverio žurnalai tikrintuvo ir duomenų gautuvų veiklai.
- 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 šaltinis2= antras pastebėtas šaltinis3= 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
- Teikite pirmenybę oficialiam API, licencijuotam paieškos duomenų teikėjui ar pirminės šalies ataskaitai.
- Prieš automatizuojant užklausas, peržiūrėkite teikėjo sąlygas.
- Gerbkite robotų instrukcijas, kai taikoma.
- Neaplenkite CAPTCHA, prisijungimo kliūčių, užklausų limitų ar botų apsaugos.
- Naudokite nedidelį, fiksuotą raginimų rinkinį.
- Vykdykite žemu dažniu.
- Išsaugokite atsakymą ir laiko žymę, kad rezultatus būtų galima patikrinti.
- 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"arbalink_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:
- Citavimo dalis pagal temą — kiek pastebėtos šaltinių erdvės priklauso jūsų domenui.
- Asistento nukreipimo rodiklis — kiek žmogaus srauto galima priskirti DI paviršiams.
- Į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.
Auto