Два движка, два разных веба: почему ChatGPT-search и Яндекс-нейропоиск цитируют из 174 доменов только 7 общих

от автора

Я гоняю один и тот же пул из 60 промптов через шесть ИИ-движков раз в месяц — это часть мониторинга, который я веду для одной ниши. В какой-то момент решил не просто читать ответы, а посчитать, откуда движки берут источники. Ожидал разную выдачу по одной теме — обычное дело для поисковиков. Получил другое: два практически несвязанных множества доменов. Из 174 уникальных источников за последний срез общих оказалось 7.

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

Что за данные и почему их вообще можно сравнивать

Логи мониторинга — это JSON-файлы, по одному на пару (движок, промпт, прогон). У каждого есть поле sources_cited — список объектов с domain и url, если движок в принципе показывает источники. Три временных среза с разницей примерно в месяц: майский, июньский, июльский — один и тот же набор из 60 промптов, одна ниша (контент-производство), русскоязычные запросы.

Первое, что выяснилось при попытке собрать пулы источников по всем пяти движкам, — три из пяти не дают материала для анализа:

  • Perplexity (sonar-pro) в ответе оставляет только числовые сноски вида [6][8], сами URL приходят отдельным полем API, которое мой прокси не пробрасывает в лог. В майском срезе изредка проскакивали (19 доменов), но это флаки, не система — для честного сравнения массив недостаточен.

  • Gemini (gemini-2.5-flash) и Claude (claude-sonnet-4.5) в этой связке работают без веб-ретрива — генеративка отвечает по весам модели, источников не цитирует по дизайну. Ноль здесь — не баг парсера, а ожидаемое поведение.

Остаются два движка, которые реально отдают машиночитаемый список источников: ChatGPT-search (gpt-4o-search-preview, ссылки идут inline в тексте вида domain) и Яндекс-нейропоиск (search-api-gen, отдаёт чистый список).

Дальше речь только о них. Не потому что остальные три неинтересны, а потому что у них физически нет данных для этого конкретного сравнения. Нельзя сказать, что источники не пересекаются у пяти движков, когда трое из пяти источники вообще не показывают. Честно сравнимы ровно два. Прежде чем считать пересечение, приходится проверить куда более скучную вещь — а движок вообще отдаёт то, что можно сравнивать?

Код: как я считал пересечение

Для каждого среза каждого движка собрать множество доменов, а потом посчитать коэффициент Жаккара между всеми парами множеств. Jaccard = |A∩B| / |A∪B|: единица — множества совпадают полностью, ноль — не пересекаются вообще.

"""Считает, насколько пересекаются пулы источников, которые разные ИИ-движкицитируют по одному и тому же набору запросов.Вход: логи мониторинга — по одному JSON на (движок, промпт, прогон).В каждом JSON есть поле sources_cited: [{"url": ..., "domain": ...}, ...].Выход: матрица коэффициентов Жаккара между пулами + доля уникальных доменов."""import globimport jsonimport os# (метка, путь к папке с логами движка за один срез времени)POINTS = [    ("ChatGPT-май", "logs/baseline-iter1/chatgpt"),    ("ChatGPT-июн", "logs/checkpoint-0611/chatgpt"),    ("ChatGPT-июл", "logs/checkpoint-0711/chatgpt"),    ("Yandex-май",  "logs/baseline-iter1/yandex_neuro"),    ("Yandex-июн",  "logs/checkpoint-0611/yandex_neuro"),    ("Yandex-июл",  "logs/checkpoint-0711/yandex_neuro"),]def domain_pool(folder):    """Множество доменов-источников, процитированных движком за срез."""    pool = set()    for path in glob.glob(os.path.join(folder, "*.json")):        with open(path, encoding="utf-8") as f:            rec = json.load(f)        for src in rec.get("sources_cited") or []:            dom = src.get("domain")            if dom:                pool.add(dom)    return pooldef jaccard(a, b):    union = a | b    return len(a & b) / len(union) if union else 0.0pools = [(label, domain_pool(folder)) for label, folder in POINTS]# --- матрица Жаккара ---print("пул источников по срезам:")for label, pool in pools:    print(f"  {label:12s} {len(pool):4d} доменов")print("\nматрица Жаккара:")print(" " * 13 + "".join(f"{lbl[:11]:>12}" for lbl, _ in pools))for l1, p1 in pools:    cells = "".join(f"{jaccard(p1, p2):12.2f}" for _, p2 in pools)    print(f"{l1:>12} {cells}")# --- доля источников, уникальных для одного движка (последний срез) ---cg = dict(pools)["ChatGPT-июл"]yx = dict(pools)["Yandex-июл"]shared = cg & yxunion = cg | yxprint(f"\nобщих доменов у ChatGPT и Yandex (июль): {len(shared)} из {len(union)}")print(f"уникальны только ChatGPT: {len(cg - yx)} ({(len(cg - yx) / len(cg)):.0%} его пула)")print(f"уникальны только Yandex:  {len(yx - cg)} ({(len(yx - cg) / len(yx)):.0%} его пула)")print(f"встречаются у обоих:      {len(shared)} ({(len(shared) / len(union)):.0%} объединения)")

Явзял не пару «ChatGPT сейчас vs Яндекс сейчас», а шесть точек — три среза каждого движка. Это нужно, чтобы отличить структурное непересечение от банального шума. Если ChatGPT-май и ChatGPT-июль пересекаются между собой не сильно лучше, чем ChatGPT и Яндекс в один момент времени, то весь разговор про «разную природу индексов» можно закрывать — окажется, что источники просто дрейфуют случайно у всех.

Что получилось: пересечение не шум, оно структурное

Размеры пулов: ChatGPT май/июнь/июль — 47/78/79 доменов, Яндекс — 92/91/102.

Матрица Жаккара по всем шести срезам. распадается на два блока чётко, как по линейке

Матрица Жаккара по всем шести срезам. распадается на два блока чётко, как по линейке

Внутри блока ChatGPT (три среза друг с другом) — 0.17–0.25. Внутри блока Yandex — 0.35–0.46, заметно стабильнее во времени, чем ChatGPT. А между блоками, в любой паре срезов — 0.04–0.07. Разница в 4–9 раз между «движок сам с собой два месяца спустя» и «два движка в один момент» — это и есть ответ на вопрос из предыдущего раздела: непересечение не шум, потому что шум размазал бы значения примерно поровну. Клетки группируются по движку, а не по времени и не по теме запроса.

Меня удивила асимметрия внутри блоков. Ожидал, что оба движка будут одинаково текучими во времени. ChatGPT ощутимо более волатилен — за месяц пул обновляется почти наполовину. У Яндекса core источников устойчивее. Кажется, ChatGPT-search в этой связке чаще подмешивает свежепроиндексированные страницы (в том числе из недавно появившихся нишевых сайтов), а топ органики Яндекса по устоявшимся коммерческим запросам меняется медленнее — крупные площадки годами удерживают позиции.

Уникальность по последнему срезу: 7 доменов из 174

На июльском срезе объединение пулов — 174 домена. Пересечение — 7. Уникальны только для ChatGPT — 72 (91% его пула). Уникальны только для Yandex — 95 (93% его пула). Иначе говоря, 96% всех доменов, которые встретились хоть у одного движка, живут ровно в одном из двух.

У ChatGPT в его 91% уникального пула — плотный хвост нишевых продуктовых и инструментальных сайтов: opusdigital.ru, aizavod.io, contentmatrix.ru, aicontentforge.ru, pixorion.ai, luminai.ru, sellscard.ru, textmark.io и подобные. Заметная доля доменов на .io, .ai, .guru — типичные для стартап-инструментов и небольших продуктовых сайтов зоны, международные по формату, даже когда контент на русском.

У Яндекса в его 93% — крупные российские медиа и площадки экосистемы: vc.ru, dzen.ru, sostav.ru, dtf.ru, companies.rbc.ru, seonews.ru, tenchat.ru, etxt.ru, raketadigital.com. По сути это узнаваемый топ органической выдачи Яндекса по коммерческим и около-коммерческим запросам этой ниши.

В 7 общих источниках — крупные тематические агрегаторы и каталоги ниши, плюс habr.com. Да, эта площадка попала в пул источников у обоих движков одновременно — что логично: авторитетный технический ресурс индексируется и краулится широко, попадает в оба индекса.

Почему так: разные индексы, не разные мнения

ChatGPT-search в этой связке ходит в собственный веб-индекс — по механике похоже на связку с Bing-подобным индексом плюс собственный краулинг OpenAI. Индекс заточен под покрытие широкого веба, включая небольшие и свежие сайты. Отсюда обилие .io/.ai/.guru-доменов и продуктовых страниц, которые никогда не попадут в топ-10 обычной поисковой выдачи по коммерческому запросу, но вполне могут быть проиндексированы и процитированы как источник конкретного факта.

С Яндексом иначе. Нейропоиск строит ответ поверх топа собственной органической выдачи: сначала работает классический поисковый ранжировщик Яндекса — тот же самый, что формирует SERP для обычного пользователя, — а генеративная надстройка суммирует и цитирует то, что уже прошло отбор в топ. Отсюда и объяснение, почему в пуле Яндекса крупные, давно проиндексированные, коммерчески сильные медиа — это буквально топ-10 обычного поиска по теме, только пересказанный моделью.

Это не значит, что Яндекс и ChatGPT по-разному оценили одни и те же 174 страницы. Это два движка, у каждого из которых своя предварительная выборка кандидатов, и выборки почти не пересекаются на входе до ранжирования и генерации. И асимметрия волатильности из прошлого раздела ложится сюда же: если ChatGPT-search сегодня краулит свежую страницу, а завтра — уже нет, пул естественно скачет сильнее. Если Яндекс завязан на топ органики, который меняется медленно (SEO-позиции крупных сайтов инерционны), пул стабильнее во времени.

Что из этого следует

Термин «видимость в ИИ-поиске» без уточнения, в какой именно ЛЛМ — вообще не метрика. Попадание в источники ChatGPT-search и попадание в источники Яндекс-нейропоиска — две разные, почти не связанные задачи с пересечением на уровне 4–7%. Публикация на площадке, которая хорошо ранжируется в органике Яндекса, покрывает пул одного движка и почти не задевает пул другого. И наоборот: присутствие на нишевом продуктовом ресурсе, который любит краулить ChatGPT, ничего не гарантирует по части Яндекс-нейропоиска.

Если задача — присутствовать в источниках обоих движков одновременно, придётся закрывать оба типа площадок отдельно. Рассчитывать, что успех в одном автоматически конвертируется в успех в другом, не выходит: они физически не читают одно и то же.

Ограничения

  • Сравнимы 2 движка из 5, не пять. У Perplexity URL прячутся за числовыми сносками — прокси не пробросил это поле, поэтому у него нет данных, а не ноль пересечения; неправильно было бы делать вывод, что Perplexity «не цитирует источники» — это методологическая дыра сбора, а не наблюдение о движке. У Gemini и Claude в этой связке нет веб-ретрива вовсе, это архитектурная особенность их подключения, а не общий факт про модели.

  • Выборка — 60 промптов одной ниши (контент-производство), русскоязычные запросы. На другой тематике или в другом языке цифры сдвинутся, возможно ощутимо — я не проверял на второй нише.

  • Гранулярность — домен, не URL. Один домен может отдавать десятки разных страниц под разные промпты; это огрубляет картину в обе стороны.

  • Коэффициент Жаккара чувствителен к разнице размеров множеств — пулы 79 и 102 не равны, что немного занижает коэффициент математически. На порядок величины это не влияет, но на третьем знаке после запятой стоит делать скидку.

  • Три среза с шагом в месяц — маловато, чтобы строить долгосрочный тренд волатильности; для этого нужно больше точек во времени.

Дальше интересно повторить то же самое на другой нише и посмотреть, держится ли структура блоков — или это специфика именно этого набора запросов.

ссылка на оригинал статьи https://habr.com/ru/articles/1058190/