IndexNow для 1С-Битрикс: уведомление Яндекса об изменениях в инфоблоке
Зачем выносить переобход из инструментов только на робота
Робот поисковой системы заходит периодически; между визитами свежая карточка каталога или новость могут неделями не попадать в индекс. Протокол IndexNow даёт возможность сообщить партнёрским системам адрес изменённой страницы через обычный HTTP-запрос вместо пассивного ожидания очередного обхода.
Поддержка у поисковиков ограничена конкретным списком площадок; среди них фигурируют Яндекс и Bing по данным производителей. Для интеграции на стороне «1С-Битрикс» достаточно открытых возможностей ядра: события инфоблока, сохранение URL в файл и отправка JSON на endpoint партнёра.
Шаг первый: файл ключа у корня домена
Генерируют строку длиной 8–128 символов из латиницы, цифр и дефиса. Удобно начать с префикса indexnow-, чтобы через год понимать назначение файла. Положите в документ текстовый файл СТРОКА.txt с тем же содержимым, что и строка-ключ, чтобы по адресу https://ваш-домен/key.txt отдавался корректный ответ без редиректа на авторизацию.
Отдельные проекты кладут файлы статики в родительские каталоги — убедитесь, что веб-сервер не блокирует прямое чтение и не добавляет авторизацию к корню.
Шаг второй: обработчики на изменение элементов
Подключайте код в /local/php_interface/init.php; если файл отсутствует, временно возможен путь через /bitrix/php_interface/init.php, но поддержку проще оставить в /local/.
Ниже класс ставит простую файловую очередь абсолютных URL и отправку стоит выполнять не в каждом хите админки, а отложенным сценарием (крон ниже). Использованы регистрация событий через EventManager и клиент из ядра HttpClient вместо ручной обёртки над cURL.
use Bitrix\Main\EventManager;
use Bitrix\Main\Web\HttpClient;
EventManager::getInstance()->addEventHandler(
'iblock',
'OnAfterIBlockElementAdd',
['BitrixIndexNowRelay', 'afterElementPersisted']
);
EventManager::getInstance()->addEventHandler(
'iblock',
'OnAfterIBlockElementUpdate',
['BitrixIndexNowRelay', 'afterElementPersisted']
);
EventManager::getInstance()->addEventHandler(
'iblock',
'OnBeforeIBlockElementDelete',
['BitrixIndexNowRelay', 'beforeRemoval']
);
class BitrixIndexNowRelay
{
private const KEY_MATERIAL = 'indexnow-q7wP4mLz9Kv';
private const ROOT_HOST = 'shop.example.ru';
private static function queueRelativePath(): string
{
return '/upload/bitrix_now_queue.txt';
}
public static function afterElementPersisted(&$payload): void
{
if (isset($payload['RESULT']) && $payload['RESULT'] === false) {
return;
}
$nodeId = (int)($payload['ID'] ?? 0);
if ($nodeId <= 0) {
return;
}
self::enqueueByPrimaryKey($nodeId);
}
public static function beforeRemoval($primaryKey): void
{
self::enqueueByPrimaryKey((int)$primaryKey);
}
private static function enqueueByPrimaryKey(int $primaryKey): void
{
$canonical = self::resolvePublicPath($primaryKey);
if ($canonical === '') {
return;
}
self::appendTarget('https://' . self::ROOT_HOST . $canonical);
}
private static function resolvePublicPath(int $primaryKey): string
{
$rows = CIBlockElement::GetList(
[],
['ID' => $primaryKey],
false,
false,
['DETAIL_PAGE_URL']
);
if ($tuple = $rows->GetNext()) {
$relative = trim((string)($tuple['DETAIL_PAGE_URL'] ?? ''));
return $relative !== '' ? $relative : '';
}
return '';
}
private static function appendTarget(string $absoluteHref): void
{
$mount = rtrim($_SERVER['DOCUMENT_ROOT'], '/') . self::queueRelativePath();
file_put_contents($mount, $absoluteHref . PHP_EOL, FILE_APPEND | LOCK_EX);
}
public static function flushOutboundBatch(): void
{
$mount = rtrim($_SERVER['DOCUMENT_ROOT'], '/') . self::queueRelativePath();
if (!is_readable($mount)) {
return;
}
$blob = file_get_contents($mount) ?: '';
$lines = array_values(array_unique(array_filter(explode(PHP_EOL, $blob))));
if (!$lines) {
return;
}
file_put_contents($mount, '', LOCK_EX);
$reply = self::postJsonEnvelope($lines);
if (($reply['success'] ?? '') !== true) {
CAdminNotify::Add([
'MESSAGE' => 'Ошибка IndexNow: «' . ($reply['message'] ?? json_encode($reply, JSON_UNESCAPED_UNICODE)) . '».',
'TAG' => 'BITRIX_NOW_RELAY',
'NOTIFY_TYPE' => CAdminNotify::TYPE_ERROR,
]);
}
}
private static function postJsonEnvelope(array $hrefs): array
{
$envelope = json_encode(
[
'host' => self::ROOT_HOST,
'key' => self::KEY_MATERIAL,
'urlList' => $hrefs,
],
JSON_UNESCAPED_UNICODE
);
$pipe = new HttpClient([
'socketTimeout' => 12,
'streamTimeout' => 12,
]);
$pipe->setHeader('Content-Type', 'application/json; charset=UTF-8', true);
$transportBody = $pipe->post('https://yandex.com/indexnow', $envelope);
$statusDigit = $pipe->getStatus();
if ($statusDigit >= 200 && $statusDigit < 300) {
return ['success' => true];
}
if ($transportBody === false || $transportBody === '') {
return [
'success' => false,
'message' => 'HTTP ' . (string)$statusDigit,
];
}
$decoded = json_decode((string)$transportBody, true);
return is_array($decoded)
? $decoded
: ['success' => false, 'message' => (string)$transportBody];
}
}Скопируйте секретный ключ и хост заглавной витрины в константы. Путь очереди сменён на /upload/bitrix_now_queue.txt, чтобы не пересечься с чужими примерами.
Отложенная отправка: скрипт и крон
Файл /local/push_now_batch.php подтягивает пролог и дергает BitrixIndexNowRelay::flushOutboundBatch():
<?php
$_SERVER['DOCUMENT_ROOT'] = realpath(__DIR__ . '/..');
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';
BitrixIndexNowRelay::flushOutboundBatch();
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_after.php';В планировщике задайте минутный запуск интерпретатора по этому пути через CLI или wget к защищённому секретным токеном HTTP-ярлыку — не оставляйте открытым публичный вызов без дополнительных проверок, если файл доступен извне.
Проверка сценария
- Сохраните элемент инфоблока в админке и найдите свежую строку в очереди.
- Явным образом выполните сценарий отправки без ожидания крона.
- Следите за блоком уведомлений админки при сбоях отправки — туда попадёт расшифровка ответа узла партнёрского сервиса IndexNow.
Совместное использование поисковиками
Согласно описанию протокола партнёрские машины синхронизируют между собой факт отправки адреса, поэтому нет задачи многократно дублировать каждый URL во все возможные точки входа за одну операцию сохранения.
Ограничения по частоте
Открытые формулировки говорят об отсутствии жёсткого лимита, но ответ вида Too Many Requests остаётся сигналом о перегрузке и требует отложить следующую отправку.
Не гарантируют мгновенный индекс
Это лишь сигнал о новой редакции документа; решение о походе робота принимает уже сторона поиска с учётом квот и своей политики обходов.
Общий ключ на несколько доменов
Строковый ключ технически масштабируется между проектами, но файл подтверждения нужно воспроизвести для каждого хоста в его корне документов отдельно.
Не хотите копаться сами?
Починю за 1-3 дня. Без предоплаты — оплата по результату.
15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии