惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
S
Security Affairs
阮一峰的网络日志
阮一峰的网络日志
L
LangChain Blog
Microsoft Azure Blog
Microsoft Azure Blog
雷峰网
雷峰网
Recent Announcements
Recent Announcements
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
博客园_首页
The Cloudflare Blog
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
MyScale Blog
MyScale Blog
S
SegmentFault 最新的问题
P
Proofpoint News Feed
Y
Y Combinator Blog
Jina AI
Jina AI
博客园 - 聂微东
A
About on SuperTechFans
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
F
Full Disclosure
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Security Blog
Microsoft Security Blog
爱范儿
爱范儿
T
Tailwind CSS Blog
J
Java Code Geeks
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Stack Overflow Blog
Stack Overflow Blog
罗磊的独立博客
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Blog of Author Tim Ferriss
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
博客园 - 三生石上(FineUI控件)
W
WeLiveSecurity
PCI Perspectives
PCI Perspectives
Attack and Defense Labs
Attack and Defense Labs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
宝玉的分享
宝玉的分享
IT之家
IT之家
Hacker News: Ask HN
Hacker News: Ask HN
The Register - Security
The Register - Security
T
The Exploit Database - CXSecurity.com
T
Threat Research - Cisco Blogs

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Как JSON‑LD начинает врать — даже когда валидатор зелёный
Сергей Купряшкин · 2026-06-19 · via Все публикации подряд на Хабре

Средний

6 мин

0

На странице услуги пользователь видел цену «от 25 000 ₽», а meta description и JSON‑LD продолжали отдавать 50000. Валидатор был зелёным.

И это был собственный сайт веб‑студии!

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

Во время аудита одного из сайтов я заметил странное расхождение. На странице услуги в видимой карточке было написано: от 25 000 ₽ В теге description и JSON‑LD при этом оставалось старое значение: от 50 000 ₽ Одна страница, одна услуга, две цены.

И вот что особенно хорошо характеризует эту ошибку: это был собственный сайт веб‑студии. Компании, которая сама разрабатывает сайты и должна понимать, что карточка, description и JSON‑LD не могут жить как три независимых источника данных.

Сайт при этом работал. Страница открывалась, вёрстка не ехала, JSON‑LD разбирался. Валидатор не ругался, потому что число 50000 было записано совершенно правильно. Просто это было не то число.

Видимая карточка: от 25000 руб.
Meta description: разработка сайта от 50000 руб.
JSON-LD offers.price: 50000

Откуда взялись две цены

Сайт работал на Yii. Код ниже обезличен, но логика ошибки сохранена.

Карточка брала цену из модели:

<?php
/** @var app\models\Service $service */
$visiblePrice = $service->price_from;
?>
<div class="service-card__price">
    от <?= number_format($visiblePrice, 0, ',', ' ') ?> руб.
</div>

Meta description жил отдельно:

<?php
$this->registerMetaTag([
    'name' => 'description',
    'content' => 'Разработка сайта от 50 000 рублей',
]);

Рядом собирался JSON‑LD:

<?php
$schema = [
    '@context' => 'https://schema.org',
    '@type' => 'Service',
    'name' => $service->title,
    'offers' => [
        '@type' => 'Offer',
        'price' => 50000,
        'priceCurrency' => 'RUB',
    ],
];

Ничего экзотического. Одно значение брали из модели, два других когда‑то написали руками. Пока цена не менялась, все три версии совпадали. Потом карточку обновили, а две строки в шаблоне забыли.

Можно заменить 50000 на 25000. Через несколько месяцев повторится то же самое. Поэтому исправлять надо не число, а способ его получения.

<?php
use yii\helpers\Html;
use yii\helpers\Json;
/** @var app\models\Service $service */
$rawPrice = $service->price_from;
$priceFrom = $rawPrice !== null && (int) $rawPrice > 0 ? (int) $rawPrice : null;
$description = $service->title;
$schema = [
    '@context' => 'https://schema.org',    
    '@type' => 'Service',    
'name' => $service->title,
];
if ($priceFrom !== null) {
    $formattedPrice = number_format($priceFrom, 0, ',', ' ');
    $description .= " от {$formattedPrice} рублей";
    $schema['offers'] = [
        '@type' => 'Offer',
        'price' => $priceFrom,
        'priceCurrency' => 'RUB',
    ];
}
$this->registerMetaTag([
    'name' => 'description',
    'content' => $description,
]);
echo Html::tag('script', Json::htmlEncode($schema), [
    'type' => 'application/ld+json',
]);

Теперь цена в карточке, meta description и JSON‑LD берётся из одного поля. Если публичной цены нет, код не превращает пустое значение в ноль и не формирует offers.

А если цены нет вообще

Для услуг это обычный случай: стоимость появляется после брифа или расчёта. Тут часто ставят 0, потому что поле хочется заполнить.

'offers' => [
    '@type' => 'Offer',
    'price' => 0,
    'priceCurrency' => 'RUB',
]

Но 0 не означает «цена неизвестна». Для машины это конкретная цена — ноль рублей. В этом проекте правило простое: нет публичной цены — нет offers.

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

Почему валидатор был зелёным

Потому что с JSON всё было нормально. Тип Service существовал, Offer был записан корректно, цена была числом, валюта — RUB. Валидатор не мог знать, что в карточке уже показывается другая сумма.

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

Последняя часть обычно и выпадает. Инструмент видит 50000, но не знает, что в интерфейсе уже стоит 25000.

В правилах Google для структурированных данных отдельно сказано, что разметка должна соответствовать содержимому страницы. Зелёная галочка этого соответствия не доказывает.

Небольшая оговорка про Service

Service подходит для описания услуги, его можно связать с Offer, указать цену и валюту. Но валидная разметка не означает, что Google обязан показать расширенный результат. У поисковика есть отдельный список поддерживаемых типов, и отдельного результата для Service там нет.

Это не делает тип бесполезным. Просто Schema.org описывает сущность, а поисковик сам решает, как использовать эти данные.

Как я проверяю страницы

Я никогда не начинаю с валидатора. Сначала открываю страницу как пользователь и смотрю, какие факты она сообщает: цену, наличие, рейтинг, адрес, автора, дату изменения. Потом ищу те же значения в meta description, Open Graph, JSON‑LD, фидах и других выгрузках.

Дальше вопрос простой: откуда взялось каждое значение? Если карточка читает service.price_from, а JSON‑LD — строку в шаблоне, ошибка уже найдена, даже когда сегодня цифры случайно совпадают.

Валидаторы я запускаю позже. Они помогают увидеть ошибки структуры и требования конкретного потребителя. Причину старой цены они не найдут.

Один тест на всякий случай

Для критичных шаблонов полезно проверить не наличие JSON‑LD, а совпадение значений. Вспомогательные методы здесь условные, чтобы не тащить в статью весь тестовый код.

public function testServicePriceMarkup(): void
{    
  $service = ServiceFactory::create([
        'title' => 'Разработка сайта',
        'price_from' => 25000,
  ]);
  $html = $this->renderServicePage($service);    
  $this->assertStringContainsString('от 25 000 руб.', $html);
  $this->assertStringContainsString(
        'Разработка сайта от 25 000 рублей',
        $this->extractMetaDescription($html)
  );
  $schema = $this->extractJsonLdByType($html, 'Service');
  $this->assertSame(25000, $schema['offers']['price']);
  $this->assertSame('RUB', $schema['offers']['priceCurrency']);
}

Для страницы без цены нужен второй сценарий: в карточке не появляется «от 0 ₽», в meta description нет выдуманной суммы, а в JSON‑LD отсутствует offers.

Как поставить задачу разработчику

«Добавить Schema.org на страницы услуг» — плохая задача. После неё разработчик начинает сам решать, откуда брать цену, что делать без цены и какие поля считать основными.

Нормальная постановка выглядит примерно так:

На страницах услуг вывести JSON‑LD типа Service. Цену брать из service.price_from, как и в карточке. Видимая цена, meta description и offers.price должны использовать одно значение. Если публичной цены нет, не выводить offers и не писать сумму вручную в description. Проверить услугу с ценой и без неё.

Такую реализацию можно отдать джуниору под ревью. Но правило, какое поле считается источником цены и что делать в пограничных случаях, должен определить не он.

Где ещё это всплывает

С ценами случай самый наглядный, но далеко не единственный. После удаления блока отзывов в JSON‑LD может остаться AggregateRating. Филиал переехал, адрес на странице поправили, а старая микроразметка продолжает жить в региональном шаблоне. У товара поменялась логика наличия, но Offer по‑прежнему отдаёт InStock.

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

Сейчас к поисковикам и агрегаторам добавились системы генерации ответов. Суть от этого не поменялась: машинным потребителям тоже не стоит отдавать старые данные.

Вместо вывода

Микроразметка сама по себе не врёт. Врут данные, которым разрешили жить отдельно друг от друга. Интерфейс может выглядеть нормально, JSON‑LD может быть валидным, а страница всё равно будет сообщать разным читателям разные вещи.

Не чините строку. Чините правило, которое позволило строке устареть.


А у вас JSON‑LD собирается из тех же данных, что и видимая страница, или это отдельный шаблон, который проверяли только при внедрении?