AutoPodAutoPod

Измерение влияния цитирований ИИ: аналитика, логи и сбор данных с панелей ответов

•23 мин чтения
Аудиостатья
Измерение влияния цитирований ИИ: аналитика, логи и сбор данных с панелей ответов
0:00—:—
Измерение влияния цитирований ИИ: аналитика, логи и сбор данных с панелей ответов

Измерение влияния цитирований ИИ: аналитика, логи и сбор данных с панелей ответов

Введение

Обнаружение, управляемое ИИ, теперь разделено на несколько уровней измерения. Человек может увидеть компанию, процитированную в ответе ИИ, кликнуть по ссылке без полезного реферера, перейти через браузер, который удаляет данные кампании, или прочитать ответ, вообще не нажимая на ссылку. Тем временем, краулер ИИ может запросить страницу компании, не представляя собой человеческого посетителя.

Поэтому полезная система измерения должна разделять четыре вопроса:

  1. Была ли страница проиндексирована или извлечена?
  2. Была ли страница процитирована в ответе ИИ?
  3. Перешел ли человек на сайт по этой ссылке?
  4. Привел ли этот визит к значимому взаимодействию или доходу?

По состоянию на 22 сентября 2026 года отчетность из первых источников улучшается. Google Search Console предоставляет отчет о производительности генеративного ИИ для обзоров ИИ и режима ИИ, включая показы, страницы, страны, устройства и даты. Отчет стал доступен по всему миру 31 августа 2026 года. Отчет Google о производительности генеративного ИИ (support.google.com)

Microsoft также предоставляет отчет AI Performance в Bing Webmaster Tools. Его публичная предварительная версия сообщает общее количество цитирований, цитируемые страницы, запросы для обоснования и тенденции цитирования в поддерживаемых ИИ-системах Microsoft. AI Performance в Bing Webmaster Tools (blogs.bing.com)

Эти инструменты следует рассматривать как авторитетные для их собственных поверхностей, но они не создают полную систему измерения для всех ассистентов. Рекомендуемый подход состоит в сочетании отчетов из первых источников, серверных логов, контролируемых ссылок отслеживания, экспортов аналитики и плановых наблюдений за панелями ответов.


1. Почему измерение рефералов ИИ затруднено

Традиционная веб-аналитика сильно зависит от заголовка Referer и параметров кампании. Оба могут отсутствовать или быть неполными.

HTTP-заголовок Referer может содержать предыдущую страницу, только источник предыдущего веб-сайта или вообще не иметь значения. Политики рефереров браузеров могут намеренно ограничивать или удалять эту информацию. Документация MDN по Referrer-Policy (developer.mozilla.org)

Системы ИИ создают дополнительные сложности:

  • Ассистент может ссылаться на страницу без отправки распознаваемого реферера.
  • Ассистент может использовать перенаправляющий или переписанный URL.
  • Ассистент может извлекать контент на стороне сервера перед показом ответа.
  • Краулер ИИ может посетить страницу, не будучи увиденным человеком.
  • Ответ может цитировать канонический URL вместо URL, который был изначально извлечен.
  • Ответ может меняться в зависимости от местоположения, устройства, языка, времени или персонализации.

Например, OpenAI различает OAI-SearchBot для обнаружения в поиске, GPTBot для возможного использования в обучении и ChatGPT-User для действий, инициированных пользователем. Perplexity отдельно идентифицирует PerplexityBot и Perplexity-User. Anthropic идентифицирует ClaudeBot, Claude-SearchBot и Claude-User. Эти запросы не следует объединять в одно общее число «трафика ИИ». Документация OpenAI по краулерам (openai.com) Документация Perplexity по краулерам (docs.perplexity.ai) Документация Anthropic по краулерам (support.anthropic.com)

Центральное правило таково:

Активность краулеров — это не реферальный трафик, видимость цитирования — это не трафик, а трафик — это не влияние на бизнес.


2. Структура измерения

Используйте пять уровней измерения.

УровеньГлавный вопросРекомендуемые данные
ДоступЗапросила ли система ИИ страницу?Логи веб-сервера, логи брандмауэра
ВидимостьБыла ли страница показана или процитирована?Google Search Console, Bing Webmaster Tools, наблюдения за панелями ответов
ПривлечениеПришел ли человек с поверхности ИИ?Реферер, параметры кампании, клики по коротким ссылкам, аналитика
ВовлеченностьВзаимодействовал ли посетитель значимым образом?События аналитики, просмотры страниц, время вовлеченности
ЦенностьПривел ли визит к желаемому результату?Регистрации, лиды, покупки, подписки

Рекомендуемая иерархия источников достоверности

Используйте самый сильный доступный сигнал для каждого вопроса:

  1. Отчеты платформы из первых источников для показов и цитирований.
  2. Наблюдения за панелями ответов для видимости на уровне промпта и сравнительного анализа с конкурентами.
  3. Логи перенаправлений для контролируемых кликов по ссылкам.
  4. Веб-аналитика для сессий, вовлеченности и конверсий.
  5. Серверные логи для активности краулеров и извлекателей.
  6. Моделированные оценки только при отсутствии прямых доказательств.

Не объединяйте все сигналы в одно число, не сохраняя источник и уровень достоверности.


3. Базовая модель данных

Создайте хранилище с отдельными таблицами для исходных наблюдений и производных метрик.

panel_runs

Одна строка для каждой проверки промпта и поверхности.

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

Одна строка для каждой ссылки, наблюдаемой на панели ответов.

text run_id surface topic prompt_id cited_url cited_domain citation_position link_scope target_domain target_cited

ai_crawler_requests

Агрегированные данные серверных логов.

text date bot_name bot_role path requests successful_requests error_requests unique_ip_count

shortener_clicks

Одна строка для каждого запроса контролируемой ссылки.

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

ai_sessions

Нормализованная таблица аналитики.

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

Поле confidence важно. Рекомендуемые значения:

  • A — Прямое: клик по контролируемой короткой ссылке или доверенный параметр кампании.
  • B — Сильное: известный реферер ассистента или проверенный запрос на получение данных пользователем.
  • C — Вероятное: доказательство кампании или целевой страницы без полного реферера.
  • D — Моделированное: выведено из времени, наблюдений за панелями или агрегированных изменений.

Сообщайте A и B отдельно от C и D.


4. Определения метрик

Доля цитирования по теме

Доля цитирования по теме измеряет, как часто целевой домен появляется среди источников, наблюдаемых для определенной темы.

[ \text{Доля цитирования}_{тема}

\frac{\text{Количество цитирований целевого домена}} {\text{Общее количество цитирований}} ]

Пример:

  • 100 общих цитирований, наблюдаемых для «программного обеспечения для управления проектами»
  • 12 цитирований указывают на ваш домен
  • Доля цитирования по теме = 12%

Эта метрика измеряет долю источника, а не то, содержал ли ответ ваш сайт хотя бы один раз.

Охват цитирования

Используйте вторую метрику для присутствия на уровне ответа:

[ \text{Охват цитирования}_{тема}

\frac{\text{Прогоны ответов, содержащие цитирование цели}} {\text{Прогоны ответов, где присутствовала панель}} ]

Охват цитирования часто легче понять нетехнической аудитории:

«Наш сайт появился в 34% наблюдаемых ответов ИИ по этой теме».

Позиция цитирования

Запишите позицию цитирования в ответе:

  • 1 = первый наблюдаемый источник
  • 2 = второй наблюдаемый источник
  • 3 = третий наблюдаемый источник

Поскольку интерфейсы ИИ часто группируют или переупорядочивают источники, рассматривайте позицию как описательное наблюдение, а не как универсальный рейтинг.


Коэффициент рефералов от ассистентов

Коэффициент рефералов от ассистентов измеряет долю человеческих сессий на сайте, которые могут быть отнесены к поверхности ИИ.

[ \text{Коэффициент рефералов от ассистентов}

\frac{\text{Человеческие сессии, атрибутированные ИИ}} {\text{Все человеческие сессии}} ]

Сообщайте как минимум две версии:

text Подтвержденный коэффициент рефералов от ассистентов = Сессии ИИ с уверенностью A / все человеческие сессии

Расширенный коэффициент рефералов от ассистентов = Сессии ИИ с уверенностью A + B + C / все человеческие сессии

Подтвержденная версия более консервативна. Расширенная версия полезна для анализа тенденций, но не должна представляться как полностью наблюдаемый трафик.

Не используйте прогоны панелей как показы

Если вы собираете данные с 500 промптов, это не означает, что 500 человек видели эти ответы. Ваш парсер измеряет контролируемую выборку состояний ответов. Используйте отчеты о показах из первых источников, когда они доступны.


Вовлеченность после цитирования

Вовлеченность после цитирования измеряет, совершили ли люди, пришедшие через цитирование ИИ, значимое действие.

[ \text{Вовлеченность после цитирования}

\frac{\text{Вовлеченные сессии ИИ}} {\text{Сессии, атрибутированные ИИ}} ]

Для Google Analytics 4 вовлеченная сессия длится более 10 секунд, включает ключевое событие или включает как минимум два просмотра страниц или экранов. Документация Google Analytics по коэффициенту вовлеченности (support.google.com)

Также сообщайте:

text Время вовлеченности после цитирования = медианное время вовлеченности в секундах на сессию ИИ

Коэффициент конверсии после цитирования = Сессии ИИ с ключевым событием / Сессии, атрибутированные ИИ

Доход после цитирования на сессию = доход от сессий ИИ / Сессии, атрибутированные ИИ

Медианное время вовлеченности обычно более стабильно, чем среднее время вовлеченности, поскольку небольшое количество очень долгих сессий может исказить среднее значение.


Дополнительные полезные метрики

Уровень успешности краулера ИИ

[ \text{Уровень успешности краулера}

\frac{\text{Запросы краулера ИИ, возвращающие 2xx или 3xx}} {\text{Запросы краулера ИИ}} ]

Разбивайте это по боту и пути. Высокое снижение цитирования в сочетании с ростом частоты ошибок краулера может указывать на проблему с хостингом, брандмауэром или конфигурацией robots.

Коэффициент цитирования к клику

Для контролируемых наблюдений за панелью ответов:

[ \text{Наблюдаемый коэффициент цитирования к клику}

\frac{\text{Подтвержденные клики по коротким ссылкам}} {\text{Наблюдаемые цитирования цели}} ]

Это не истинный коэффициент кликов пользователей, если знаменатель не представляет фактические показы пользователям. Четко обозначьте это как наблюдаемую операционную метрику.

Свежесть цитирования

Измеряйте, как часто цитируемый URL отражает текущую версию страницы.

text Свежесть цитирования = цитируемые страницы с текущим хешем контента / цитируемые страницы

Это полезно для часто меняющихся страниц документации, ценообразования, продуктов и политик.


5. Анализ серверных логов

Настройка структурированных логов доступа

Структурированные логи легче анализировать, чем традиционные логи с разделителями-пробелами.

Пример конфигурации Nginx:

nginx log_format ai_json escape=json '{' '"ts":"$time_iso8601",' '"remote_addr":"$remote_addr",' '"request":"$request",' '"status":$status,' '"bytes":$body_bytes_sent,' '"referer":"$http_referer",' '"user_agent":"$http_user_agent",' '"host":"$host"' '}';

access_log /var/log/nginx/ai-access.jsonl ai_json;

Храните файлы логов в ограниченном месте. Не отправляйте необработанные IP-адреса в общую таблицу аналитики, если у вас нет четкой операционной причины.

Идентификация типов запросов ИИ

Как минимум, разделите:

text OpenAI:

  • OAI-SearchBot
  • ChatGPT-User
  • GPTBot

Anthropic:

  • Claude-SearchBot
  • Claude-User
  • ClaudeBot

Perplexity:

  • PerplexityBot
  • Perplexity-User

Поисковые системы:

  • Googlebot
  • Bingbot

Функции поиска ИИ Google обычно используют существующие элементы управления сканированием Google Search, а не отдельного универсального «бота AI Overview». Google заявляет, что Googlebot контролирует доступ для поиска, включая функции ИИ. Функции ИИ Google и веб-сайты (developers.google.com)

Строки пользовательского агента могут быть подделаны. Для операционных решений комбинируйте сопоставление пользовательского агента с опубликованными провайдером диапазонами IP-адресов или проверенным обратным DNS, где это возможно. OpenAI и Perplexity публикуют информацию об IP-адресах для своих соответствующих краулеров и извлекателей. (openai.com)

Воспроизводимый сумматор логов

Сохраните следующее как 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])

Запустите это с помощью:

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

data/ai_crawler_daily.csv

Это предоставляет операционные доказательства активности извлечения, а не человеческого трафика.


6. Пользовательские сокращатели ссылок для сводок, ориентированных на ИИ

Собственный сокращатель ссылок предоставляет контролируемую точку измерения, когда вы публикуете сводки или исходные материалы, которые могут быть упомянуты системами ИИ.

Используйте домен, который вы контролируете, например:

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

Назначение должно включать параметры кампании, такие как:

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

Google рекомендует параметры кампании для идентификации источников трафика и кампаний. Документация Google Analytics по URL-адресам кампаний (support.google.com)

Рекомендуемые поля ссылок

text surface topic prompt_id asset_id content_version destination_path

Не включайте личную информацию, адреса электронной почты или пользовательские поисковые запросы в ссылку. Идентификаторы кампании должны описывать опубликованный актив, а не идентифицировать человека.

Важные ограничения

Короткая ссылка — это устройство калибровки, а не универсальное решение:

  • Ассистент может переписать целевой URL.
  • Ассистент может вместо этого цитировать канонический URL.
  • Ссылка может быть отображена, но никогда не кликнута.
  • Браузер или инструмент конфиденциальности могут удалить реферера.
  • Некоторые системы могут извлекать целевое содержимое на стороне сервера.
  • Короткая ссылка не измеряет пользователей, которые запоминают бренд и возвращаются позже.

Не используйте сокращатель для скрытия назначения, обхода систем безопасности или манипулирования цитированиями. Используйте четкое перенаправление на соответствующую, каноническую страницу.

Минимальный собственный сокращатель ссылок

Следующий пример использует FastAPI и SQLite. Защитите конечную точку создания ссылок аутентификацией перед использованием в продакшене.

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)

Запустите это с помощью:

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

Используйте перенаправление 302 для ссылок измерения, чтобы браузеры и промежуточные системы с меньшей вероятностью кэшировали перенаправление навсегда.


7. Плановое сканирование панелей ответов ИИ

Сканирование панелей полезно для ответов на вопросы, которые аналитика из первых источников может не раскрывать:

  • Какие конкурирующие источники цитируются?
  • Какая страница цитируется для конкретного промпта?
  • Изменяется ли ответ в зависимости от страны или устройства?
  • Появляется ли цитирование в ответе, списке источников или в обоих?
  • Насколько стабилен ответ со временем?
  • Цитирует ли ассистент короткую ссылку или канонический URL?

Однако сканирование должно выполняться осторожно.

Правила ответственного сбора данных

  1. Предпочитайте официальный API, лицензированного поставщика поисковых данных или отчет из первых источников.
  2. Ознакомьтесь с условиями поставщика перед автоматизацией запросов.
  3. Соблюдайте инструкции robots, где это применимо.
  4. Не обходите CAPTCHA, барьеры входа в систему, ограничения скорости или защиты от ботов.
  5. Используйте небольшой, фиксированный набор промптов.
  6. Запускайте с низкой частотой.
  7. Сохраняйте ответ и временную метку, чтобы результаты можно было проверить.
  8. Останавливайтесь, когда поставщик возвращает запрос на проверку или сообщение об отказе в доступе.

Протокол исключения роботов — это механизм предпочтений для краулеров, а не система безопасности. Он не разрешает доступ к защищенному контенту. RFC 9309: Robots Exclusion Protocol (rfc-editor.org)

Создание манифеста промптов

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

Сохраняйте промпты стабильными. Если промпты меняются, сравнения тенденций становятся менее надежными.

Также храните:

  • страна
  • язык
  • тип устройства
  • версия браузера
  • версия промпта
  • версия контента
  • время сбора в UTC

Google отмечает, что ответы AI Overview и AI Mode могут различаться, потому что они используют разные системы и могут использовать связанные поисковые запросы или «расширение запроса». Документация Google по функциям ИИ и веб-сайтам (developers.google.com)

Скрипт сбора данных Playwright

Следующий скрипт представляет собой универсальную исследовательскую платформу. Он записывает отрисованную страницу, скриншот, ссылки на странице и текст ответа. Он намеренно не обходит проверки и не пытается преодолеть элементы управления доступом.

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

Установите и запустите:

bash pip install playwright playwright install chromium python scrape_panels.py

Запланируйте его с блокировкой, чтобы два запуска не могли перекрываться:

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

Повышение точности с помощью адаптеров поверхностей

Резервный скрипт записывает ссылки на уровне страницы. Это полезно для обнаружения, но может включать обычные ссылки из результатов поиска. Для использования в продакшене:

  • Используйте лицензированный API панели ответов, когда он доступен.
  • Создайте отдельный адаптер селектора для каждой поверхности.
  • Сохраняйте версию селектора.
  • Сохраняйте необработанный HTML и скриншот.
  • Помечайте каждую запись с помощью link_scope = "panel_selector" или link_scope = "page_fallback".

Рассчитывайте долю цитирования только по данным panel_selector, когда вы можете надежно идентифицировать панель. Сохраняйте резервные наблюдения в отдельном исследовательском отчете.


8. Интеграция Google Analytics и BigQuery

Google Analytics 4 может экспортировать необработанные данные о событиях в BigQuery. Ежедневные экспорты предоставляют стабильные таблицы, такие как events_ГГГГММДД; потоковые экспорты полезны для мониторинга почти в реальном времени, но могут быть неполными до завершения ежедневной таблицы. Экспорт Google Analytics в BigQuery (support.google.com)

Упрощенный запрос сессий может выглядеть так:

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;

Для зрелой реализации добавьте surface, topic и prompt_id в структуру кампании или объедините их из таблицы кликов сокращателя.

Не создавайте аналитическое измерение с высокой кардинальностью для каждого отдельного пользователя или сессии. Google предупреждает, что чрезмерно высококардинальные пользовательские измерения могут снизить качество отчетности. Пользовательские измерения и метрики Google Analytics (support.google.com)


9. Дизайн дашборда

Практичный дашборд должен содержать пять представлений.

Исполнительная сводка

Отображение:

  • Доля цитирования по теме
  • Охват цитирования по теме
  • Подтвержденный коэффициент рефералов от ассистентов
  • Расширенный коэффициент рефералов от ассистентов
  • Вовлеченность после цитирования
  • Уровень успешности краулера ИИ
  • Ключевые события или доход, атрибутированные ИИ

Тенденция доли цитирования

Линейный график:

text Дата × Тема × Поверхность

Показывать оба:

  • количество цитирований целевого домена
  • все наблюдаемые цитирования

Воронка трафика ассистента

text Наблюдаемые цитирования ↓ Клики по контролируемым ссылкам ↓ Сессии, атрибутированные ИИ ↓ Вовлеченные сессии ИИ ↓ Ключевые события

Каждый этап должен отображать свою уверенность в атрибуции.

Состояние краулера

Таблица по боту:

text bot_name requests successful_requests error_rate unique_ip_count top_paths

Оповещение при:

  • уровень ошибок превышает 2 процента
  • бот внезапно исчезает
  • ранее доступный раздел контента возвращает 403 или 429
  • запросы краулера концентрируются на непредусмотренном частном пути

Таблица возможностей контента

Ранжируйте страницы и темы по:

text высокая доля цитирования + низкий коэффициент рефералов высокие показы + низкий охват цитирования высокая вовлеченность + низкая видимость цитирования высокие ошибки краулера + высокая бизнес-важность

Эти комбинации обычно приводят к лучшим решениям, чем рассмотрение только объема трафика.

Минимальная панель мониторинга Streamlit

Сохраните как 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) )

Запустите его с помощью:

bash pip install streamlit pandas streamlit run dashboard.py

Для продакшена загружайте нормализованные таблицы в DuckDB, BigQuery, Snowflake или PostgreSQL вместо прямого чтения сырых файлов.


10. Контроль качества

Система измерения может выдавать точные, но вводящие в заблуждение числа. Добавьте следующие элементы управления.

Разделяйте ботов и людей

Никогда не считайте OAI-SearchBot, GPTBot, ClaudeBot или PerplexityBot как человеческие сессии. Это сигналы краулера или извлечения.

Сохраняйте исходные данные

Для каждого прогона панели сохраняйте:

  • исходный HTML
  • скриншот
  • текст ответа
  • временную метку
  • версию браузера и сборщика
  • промпт и локаль
  • хеш ответа

Это позволяет объяснить, почему метрика изменилась.

Отслеживайте отсутствие панели

Отсутствующая панель ИИ — это не то же самое, что ответ без вашего цитирования.

Используйте:

text panel_not_present panel_present_no_target panel_present_target_cited blocked_or_challenged collection_error

Контролируйте систематическую ошибку выборки

Запланированное сканирование зависит от:

  • местоположение
  • устройство
  • язык
  • время
  • персонализация
  • состояние браузера
  • формулировка промпта

Используйте фиксированный основной набор промптов и ротируемый набор исследовательских промптов. Держите эти две группы разделенными в отчетах.

Избегайте ложной точности

Сообщайте доверительные интервалы или минимальные размеры выборки для небольших тематических групп. Тему с тремя наблюдениями не следует напрямую сравнивать с темой, содержащей 300 наблюдений.

Сравнивайте подобное с подобным

При сравнении поверхностей сохраняйте постоянными:

  • промпт
  • локаль
  • страна
  • устройство
  • час сбора
  • правила целевого домена
  • версия парсера цитирований

11. Рекомендуемый план внедрения

Неделя 1: Инструментарий

  • Настройте структурированные серверные логи.
  • Создайте реестр ботов.
  • Создайте домен сокращателя.
  • Добавьте правила именования кампаний.
  • Определите таксономию тем.
  • Включите экспорт Google Analytics 4 в BigQuery.

Неделя 2: Контролируемые ссылки

  • Опубликуйте пять-десять отслеживаемых сводок.
  • Создайте ссылки для разных тем и версий контента.
  • Проверьте логи перенаправлений по данным аналитических сессий.
  • Подтвердите, что никакая личная информация не попадает в URL или параметры аналитики.

Неделя 3: Наблюдения за панелями

  • Создайте манифест промптов.
  • Начните с двух поверхностей и 20-50 стабильных промптов.
  • Запускайте один раз в день.
  • Сохраняйте скриншоты и HTML.
  • Отдельно измеряйте сбои сбора от отсутствия цитирования.

Неделя 4: Дашборд и управление

  • Создайте дашборды по темам и поверхностям.
  • Добавьте оповещения о состоянии краулера.
  • Определите правила доверия.
  • Сравните отчеты из первых источников с наблюдениями за панелями.
  • Документируйте политики хранения, доступа и удаления данных.

Заключение

Измерение цитирований ИИ должно быть построено как многосигнальная система атрибуции, а не как единый отчет о рефералах.

Наиболее надежная структура сочетает:

  • Отчеты Google и Bing о видимости из первых источников
  • Серверные логи для активности краулеров и извлекателей
  • Собственные короткие ссылки для контролируемого измерения кликов
  • Запланированные, соответствующие требованиям наблюдения за панелями ответов
  • Данные аналитики и конверсий для оценки последующей ценности

Три наиболее важные метрики:

  1. Доля цитирования по теме — какая часть наблюдаемого пространства источников принадлежит вашему домену.
  2. Коэффициент рефералов от ассистентов — сколько человеческого трафика можно отнести к поверхностям ИИ.
  3. Вовлеченность после цитирования — совершают ли посетители, пришедшие по рефералам ИИ, значимые действия.

Система становится полезной, когда каждое число включает в себя поверхность, период времени, знаменатель, метод атрибуции и уровень достоверности. Такая дисциплина предотвращает ошибочное принятие обращений краулеров за пользователей, сканированных ответов за показы, а цитирований — за бизнес-результаты.

Похожие статьи

Оптимизация для Perplexity и других ассистентов цитирования

Оптимизация для Perplexity и других ассистентов цитирования

Оптимизация для Perplexity и других ассистентов цитирования Киран Шарма, контент-стратег, ориентированный на рост Обновлено 14 октября 2026 г. ИИ-асси…

Читать статью
Карточки ответов и блоки TL;DR: Определяют ли резюме выбор ИИ?

Карточки ответов и блоки TL;DR: Определяют ли резюме выбор ИИ?

Карточки ответов и блоки TL;DR: Определяют ли резюме выбор ИИ? Блок TL;DR — это не кнопка для цитирования ИИ. Он может облегчить поиск и повторное исп…

Читать статью
Проектирование абзацев-ответов: Создание извлекаемых и проверяемых фрагментов

Проектирование абзацев-ответов: Создание извлекаемых и проверяемых фрагментов

Проектирование абзацев-ответов: Создание извлекаемых и проверяемых фрагментов Киран Шарма, контент-стратег, ориентированный на рост Хороший контент до…

Читать статью
Каноникализация и дедупликация: Предотвращение фрагментированных сигналов

Каноникализация и дедупликация: Предотвращение фрагментированных сигналов

Каноникализация и дедупликация: Предотвращение фрагментированных сигналов Один полезный ресурс может оказаться доступным по множеству адресов: статья…

Читать статью

Понравился этот контент?

Подпишитесь на нашу рассылку, чтобы получать последние новости контент-маркетинга и руководства по росту.

Эта статья носит исключительно информационный характер. Контент и стратегии могут варьироваться в зависимости от ваших конкретных потребностей.
Измерение влияния цитирований ИИ: аналитика, логи и сбор данных с панелей ответов | AutoPod