Loomaly

JSON-LD 查看器与结构化数据检测

输入一个网址,把页面的 JSON-LD 画成关系图:页面怎么描述自己、各部分怎样通过 @id 连起来、哪些引用指向了不存在的东西。同时检查必填属性和取值格式。

JSON-LD 是什么,为什么要当成一张图来看

JSON-LD 是写在 <script type="application/ld+json"> 标签里的一段 JSON,用 schema.org 的词汇描述这个页面:这是一个叫 Blue Mug 的商品,售价 24 美元,卖家是某个组织。有了它,搜索引擎和 AI 助手就不用再从页面布局里猜。Google 支持 JSON-LD、Microdata 和 RDFa 三种写法,推荐用 JSON-LD。

每个对象都有一个 @type,还可以有一个 @id。@id 是它的标识,一般写成页面网址加一个井号片段,比如 https://example.com/#organization。别的对象想提到它,写上这个 @id 就行,不用把整个对象再抄一遍。一个页面就是这样把话说清楚的:这个 WebPage 属于这个 WebSite,WebSite 由这个 Organization 发布,页面上的 Product 也是同一个 Organization 出品的。

引用都对得上,整页说的就是一组互相连着的事实。只要有一处对不上,比如 @id 拼错了,或者指向了一个只在首页输出的 Organization,每个块照样能解析,只是那条连接悄悄断了。把属性一条条列出来是看不出这种问题的,画成图就一目了然:本该连上的实体孤零零地待在一边,或者箭头指向一张虚线卡片。

怎么看这张图

  • 每张卡片是一个实体。彩色标签是它的 @type,下面是它的名称(没有名称就用 headline、title 或网址),再往下最多列出四个纯文本属性。
  • 箭头从拥有这个属性的实体指向它引用的实体,箭头上写的是属性名,比如 offers、brand、publisher、author、isPartOf。
  • 没有被任何实体指向的,排在最左边一列;往右每一列都再深一层。
  • 标着「被引用,但从未定义」的虚线卡片,是一个有人引用、但页面上没有任何地方描述过的 @id。
  • @id 相同的对象会合并成一张卡片,哪怕它们写在不同的 script 块里。如果你以为会有两张卡片却只看到一张,说明它们用了同一个 @id。
  • 只读取 JSON-LD,不读 Microdata 和 RDFa。图特别大时只画前 150 个实体。点「下载 PNG」会导出白底图片,可以直接贴进工单或客户报告。

得分和问题分别是什么意思

得分从 0 到 100,按六项加权:JSON-LD 本身是否合法(20%)、Google 富媒体结果要求的必填属性是否齐全(30%)、取值格式是否正确(20%)、推荐属性(15%)、@id 引用能否找到对应实体(10%),以及一些小的最佳实践(5%)。页面上没有 JSON-LD 就是 0 分。

问题按严重程度排列,错误在最前。每条前面有一个路径,比如 Product.offers.price,可以直接拿去模板里搜。问题说明目前是英文的,下面按原文列出,并说明怎么改:

block is not valid JSON
JSON 语法错误,整个块都不会被读取。常见原因是多了一个结尾逗号、字符串里的双引号没有转义,或者模板变量输出了空值。把这段内容放进 JSON 解析器里跑一下,按它指出的那一行改。
@context is missing
没有 @context,属性名就没有含义。给每个顶层对象加上 "@context": "https://schema.org"。如果写的是 http://schema.org,也能用,只是推荐写 https,所以只作为提示列出。
node has no @type
对象有属性却没有类型,就没法解读。加上说明它是什么的 @type,比如 Offer、Person 或 PostalAddress。
… requires …; without it the rich result is not eligible
Google 把这个属性列为该类型富媒体结果的必填项。比如 Event 必须有 name、startDate 和 location,BreadcrumbList 必须有 itemListElement。补上它,值要和页面上显示的一致。
… should include …
缺了一个推荐属性。没有它也符合条件,只是展示的信息会少一些。只在属实的情况下添加,不要为了消掉提示去编造评分或评论数。
expected a number / an absolute http(s) URL / an ISO 8601 date / a nested object with an @type
取值的形式不对。价格写成 "$24.00" 应改为 24.00,币种放在 priceCurrency 里;日期写成 "Sept 3, 2026" 应改为 "2026-09-03";图片写成 /img/mug.jpg 这样的路径,应改成完整网址;作者只写了一个名字,应改成带 name 的 Person 或 Organization 对象。availability 这类只能从固定列表里取值的属性,必须用列表里的值,比如 https://schema.org/InStock。
… holds …, expected …
嵌套对象的类型不对,比如 address 里放了一个 Person。把它的 @type 改成提示里要求的那个。
positions … must run 1..n with no gaps / every crumb except the last needs an item
BreadcrumbList 里的 position 必须从 1 开始连续编号,中间不能跳;除了最后一项(也就是当前页面),每一项都要有 item,填这一级的网址。
@id "…" is referenced but never defined on this page
这就是图里那张虚线卡片:有东西引用了一个 @id,但这个页面上没有任何地方描述它。通常是拼写问题(把 #organization 写成 #org、http 和 https 混用、少了结尾的斜杠),或者这个实体只在别的页面输出,最常见的是只在首页输出的 Organization。@id 引用不是链接,没人会顺着它去抓另一个页面,所以要么改对拼写,要么在这个页面上也输出这个实体。
… separate entities with no @id cross-references between them
页面上有好几个顶层实体,彼此之间没有任何引用。这不算错,但读起来就是几条互不相干的信息。给每个实体一个 @id,再把它们连起来:WebPage 的 isPartOf 指向 WebSite,Article 的 publisher 指向 Organization。
… has no rich result we can check for
我们没有这个类型的检查规则。这不是错误:它照样会出现在图里,嵌套在它里面的对象也照样会被检查。

一个实际的例子

一个商品页在同一个 @graph 里输出了 Organization、WebPage 和 Product。每一部分都能正常解析,乍一看没什么问题:

{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://example.com/#organization",
      "name": "Example Coffee Co.",
      "url": "https://example.com/",
      "logo": "https://example.com/logo.png"
    },
    {
      "@type": "WebPage",
      "@id": "https://example.com/products/blue-mug#webpage",
      "name": "Blue Mug",
      "url": "https://example.com/products/blue-mug"
    },
    {
      "@type": "Product",
      "@id": "https://example.com/products/blue-mug#product",
      "name": "Blue Mug",
      "image": "https://example.com/img/blue-mug.jpg",
      "mainEntityOfPage": { "@id": "https://example.com/products/blue-mug#webpage" },
      "brand": { "@id": "https://example.com/#org" },
      "offers": {
        "@type": "Offer",
        "price": "$24.00",
        "priceCurrency": "USD",
        "availability": "https://schema.org/InStock"
      }
    }
  ]
}

画成图,代码里看不出的问题就露出来了:Organization 孤零零地待在最左边,没有任何箭头指向它;Product 的 brand 箭头则指向一张虚线卡片 https://example.com/#org。问题列表给出的是同样的结论,并附上了路径:

error  Product.offers.price
       price on Offer: expected a number, got the bare text "$24.00"
error  Product.brand
       @id "https://example.com/#org" is referenced but never defined on this page

只需要改两行:让 brand 指向 Organization 真正使用的那个 @id,再把价格里的货币符号去掉。

"brand": { "@id": "https://example.com/#organization" },
…
  "price": 24.00,

改完后得分从 79 升到 94,Organization 也和 Product 连上了。剩下四条提示分别建议补充 sameAs、sku、aggregateRating 和 offer 的 url。属实的就加上;没有评论的商品不应该带评分。

常见问题

页面明明有 JSON-LD,为什么这里显示没有?

这个工具读的是服务器返回的 HTML,不执行 JavaScript。用脚本插入的 JSON-LD,比如通过标签管理器或前端渲染加上的,这里看不到。Google 能读取 JavaScript 插入的 JSON-LD,但很多其他爬虫和 AI 工具不执行脚本,所以把标记直接写在服务器输出的 HTML 里更稳妥。另外也确认一下用的是 JSON-LD 而不是 Microdata 或 RDFa,以及网站没有拦截自动化请求。

它和 Google 富媒体搜索结果测试、Schema Markup Validator 有什么区别?

富媒体搜索结果测试(Rich Results Test)会像 Google 那样加载页面,包括执行 JavaScript,然后告诉你页面符合哪些 Google 富媒体结果的条件。validator.schema.org 上的 Schema Markup Validator 按完整的 schema.org 词汇表做检查。这个工具画出实体之间怎样相连,这是前两者都不提供的;它的检查覆盖常见的富媒体结果类型:Article、Product、Offer、BreadcrumbList、Event、Recipe、VideoObject、LocalBusiness、Organization、WebSite 等。如果要依赖某个具体的 Google 功能,最后再用富媒体搜索结果测试确认一遍。

@id 必须是能打开的网址吗?

不必。它只是一个标识,不需要能打开。常见做法是页面网址加井号片段:全站通用的实体用 https://example.com/#organization 这种,只属于某一页的用 https://example.com/products/blue-mug#product 这种。关键是同一个 @id 在所有用到的地方必须一字不差:http 和 https、带不带 www、有没有结尾斜杠,都会变成不同的 @id。

没有发现问题,就一定能拿到富媒体结果吗?

不一定。标记正确只代表页面符合条件,是否展示富媒体结果由 Google 按页面、按搜索逐个决定;而且标记描述的内容必须是访客在页面上看得到的。另外有些功能已经下线:Google 从 2026 年 5 月起不再展示 FAQ 富媒体结果,面包屑从 2025 年 1 月起只在桌面端搜索结果里显示。

能检查没有公开的页面吗,比如测试环境?

不能。这个工具只抓取标准端口上的公开 http(s) 地址,IP 地址、localhost 和内网主机名都会被拒绝,每一次跳转也按同样的规则检查。需要登录的页面或本机上的页面,可以把代码粘贴到 Google 富媒体搜索结果测试里,它支持直接检查代码片段。