JSON-LD に対応してみた
以前,このサイトを Twitter Cards に対応させたのだが,今回も思いつきで JSON-LD に対応させることにした。
JSON-LD の特徴は複数の情報のかたまりを IRI1 で繋ぐことで複雑なデータ構造を記述できる点にある。 これを使ってネット上のリソースのメタデータとその関係を表現するわけだ。
ただし Web ページの場合は, RDFa や Microdata などと違って,語彙を HTML の要素に埋め込むことが出来ないため冗長にならざるを得ない。 まぁ CMS を使ってサイトや Web ページを管理しているなら,一度テンプレート等を作ってしまえば済む話なので,大した手間ではないかもしれないが。
逆に RESTful API でメタデータを提供する場合は JSON-LD はかなり有力な手段となるだろう。
真面目に JSON-LD を扱うとなると相当凝ったことができるみたいだが2,今回は軽めに Google の検索サービスが解釈できる範囲で調整してみようと思う。
なお JSON-LD を導入するのなら Microdata の記述はページから削除することをお勧めする。 Microdata は未完成のまま開発が終了しており,もはや推奨されない。
Web ページに JSON-LD を埋め込む
JSON-LD を Web ページに埋め込む際は,以下のように <script>
要素で囲む。
<script type="application/ld+json">
{
...
}
</script>
メディア・タイプに注意すること。 どうやらこの記述はページ内にいくつ置いてもいいようだ(少なくとも Google のテストツールには怒られなかった)。
<script type="application/ld+json">
{
...
}
</script>
<script type="application/ld+json">
{
...
}
</script>
JSON-LD の詳細
JSON-LD の中身だが,まずは @context
と @type
を指定する。
{
"@context": "http://schema.org",
"@type": "WebSite"
}
@context
で語彙を定義するのだが,どうやら Google は Microdata の語彙(のひとつ)である schema.org を前提にしているらしい。
@type
には schema.org で定義される content type を指定する。
このサイトは一応ブログサイトなので,以下の content type を使うことにする。
更に,これらが参照する content type として以下も使用する。
まずは WebSite から。
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "WebSite",
"@id": "https://text.baldanders.info/",
"inLanguage": "ja",
"name": "text.Baldanders.info",
"url": "https://text.baldanders.info/",
"publisher": {
"@id": "https://text.baldanders.info/#org"
},
"author": {
"@type": "Person",
"@id": "https://text.baldanders.info/#maker",
"name": "Spiegel",
"url": "https://baldanders.info/profile/",
"image": "https://text.baldanders.info/images/avatar.jpg"
},
"image": "https://text.baldanders.info/images/avatar.jpg",
"description": "帰ってきた「しっぽのさきっちょ」"
}
</script>
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Organization",
"@id": "https://text.baldanders.info/#org",
"name": "text.Baldanders.info",
"logo": {
"@type": "ImageObject",
"@id": "https://text.baldanders.info/#logo",
"url": "https://text.baldanders.info/images/avatar.jpg"
}
}
</script>
@id
は IRI を定義または参照する。
@id
によって Organization のデータを publisher
から参照しているのがお分かりだろうか。
これをテストツールにかけるとこんな感じになる。
publisher
に Organization のデータが入っているのがわかると思う。
本来の schema.org の定義では publisher
には Organization と Person のどちらも有効な筈なのだが,テストツールは Organization しか受け付けないようだ。
これではうちのような個人サイトは大変に困るのだが,しょうがないので Organization にテキトーな情報を入れてお茶を濁している。
何とかしてよ Google 先生!
次は Blog の情報をセットする。
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Blog",
"@id": "https://text.baldanders.info/remark/",
"url": "https://text.baldanders.info/remark/",
"inLanguage": "ja",
"name": "しっぽのさきっちょ",
"description": "とりとめのない四方山話。",
"image": "https://text.baldanders.info/images/attention/remark.jpg",
"publisher": {
"@id": "https://text.baldanders.info/#org"
},
"author": {
"@type": "Person",
"@id": "https://text.baldanders.info/remark/#maker",
"name": "Spiegel",
"url": "https://baldanders.info/profile/",
"image": "https://text.baldanders.info/images/avatar.jpg"
}
}
</script>
publisher
の参照先は WebSite と同じなので省略する。
これをテストツールにかけるとこんな感じになる。
どんどん行こう。 BlogPosting はこんな感じ。
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "BlogPosting",
"@id": "https://text.baldanders.info/remark/2017/11/qiitadon/",
"url": "https://text.baldanders.info/remark/2017/11/qiitadon/",
"mainEntityOfPage": "https://text.baldanders.info/remark/2017/11/qiitadon/",
"inLanguage": "ja",
"name": "Qiita って Mastodon やってたのか",
"description": "私にとって今年最初の大外しは「GW 過ぎたら Mastodon のことなんかみんな忘れてる」だったが,本当に忘れていたのは私だけだったようだ。",
"headline": "私にとって今年最初の大外しは「GW 過ぎたら Mastodon のことなんかみんな忘れてる」だったが,本当に忘れていたのは私だけだったようだ。",
"keywords": "mastodon, communication",
"image": "https://text.baldanders.info/images/attention/remark.jpg",
"datePublished": "2017-11-10T13:49:58+09:00",
"dateModified": "2017-11-16T10:09:40+09:00",
"publisher": {
"@id": "https://text.baldanders.info/#org"
},
"author": {
"@type": "Person",
"@id": "https://text.baldanders.info/remark/2017/11/qiitadon/#maker",
"name": "Spiegel",
"url": "https://baldanders.info/profile/",
"image": "https://text.baldanders.info/images/avatar.jpg"
},
"license": "https://creativecommons.org/licenses/by-sa/4.0/"
}
</script>
Google 検索は BlogPosting の内容を参照している。 Google 検索が BlogPosting で要求するプロパティは以下の通り。
Properties | Data Type | AMP | non-AMP |
---|---|---|---|
mainEntityOfPage |
URL |
recommended | ignored |
headline |
Text |
required | recommended |
image |
ImageObject or URL |
required | recommended |
publisher |
Organization |
required | ignored |
publisher.name |
Text |
required | ignored |
publisher.logo |
ImageObject |
required | ignored |
publisher.logo.url |
URL |
required | ignored |
publisher.logo.height |
Number |
required | ignored |
publisher.logo.width |
Number |
required | ignored |
datePublished |
DateTime |
required | ignored |
dateModified |
DateTime |
required | ignored |
author |
Person or Organization |
required | ignored |
author.name |
Text |
required | ignored |
description |
Text |
recommended | ignored |
AMP (Accelerated Mobile Pages) と non-AMP で要求が異なるが,テストツールは AMP を前提にしているようで,これらのプロパティがないとエラーまたは警告を吐く3。
これもテストツールにかけてみよう。 こんな感じ。
最後に BreadcrumbList (パンくずリスト4)はこんな感じになる。
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "BreadcrumbList",
"@id": "https://text.baldanders.info/remark/2017/11/qiitadon/#breadcrumb-list",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"item": {
"@id": "https://text.baldanders.info/"
}
},
{
"@type": "ListItem",
"position": 2,
"item": {
"@id": "https://text.baldanders.info/remark/"
}
}
]
}
</script>
Google 検索は BreadcrumbList の内容も参照している。 Google 検索が BreadcrumbList で要求するプロパティは以下の通り。
Properties | Data Type | Requirement |
---|---|---|
image |
URL |
optional |
item |
Thing |
required |
name |
Text |
required |
position |
Integer |
required |
上述のコードでは item
を @id
の参照先と繋げている。
具体的には,最初の階層に WebSite データの @id
を,2番目の階層に Blog データの @id
を指定している。
これによって item
の中に name
や image
が含まれるため,不足なく情報を網羅できている。
ブックマーク
- Google推奨「JSON-LD」で構造化マークアップ - Qiita
- Google リッチカードの導入 - Qiita
- schema.orgのパンくずリストをようやくGoogleがサポート開始 | 海外SEO情報ブログ
- JSON-LDによるブログの構造化データをHugoで実現する | TRIAL DANCE
- Hugo で JSON-LD 内の URL がなぜかエスケープされる問題 - Qiita
- HUGOで作れるCMSっぽいパーツ:関連記事・目次・JSON-LDなど - Qiita
-
IRI (Internationalized Resource Indicator) というのは JSON-LD 独自の用語のようだが,ここでは大雑把に URL (Uniform Resource Locator) や URI (Uniform Resource Indicator) と同じようにネット上のリソースを一意に識別する識別子であると理解しておけばよい。なお,テストツールを見るかぎり Google 検索では IRI からメタデータの場所(URL または URI)を推測して取得するといったことはしないようだ。したがって Web ページに JSON-LD を設置する場合には,関連する全てのメタデータを記述する必要がある。冗長すぎるやろ。 ↩︎
-
JSON-LD の仕様を見るかぎり,Microdata や RDFa の語彙を流用したり自前で語彙を作ったりできるっぽい。 ↩︎
-
ただし
publisher.logo.height
とpublisher.logo.width
は例外のようで,これらのプロパティがなくてもテストツールではエラーにならなかった。まぁ画像のサイズは画像データから読み取れるからね。 ↩︎ -
一応説明しておくと「パンくずリスト」とはサイト内のページ位置を見失わないようにするためのナビゲーション表示を指す。名前で想像される通り「ヘンゼルとグレーテル」の童話が語源になっているらしい。まぁ,いまどきパンくずリストがないと迷子になってしまうようなダサい構成のサイトは少なくなってると思うけど。 ↩︎