Works
Blog Recruit Contact AI互換性診断
構造化データ
calendar_today
[構造化データの引っ越し Vol.5] 山下 太郎 山下 太郎

構造化データの引っ越し 第5回:空いていた層を埋める — 自社サイト改修ドキュメンタリー

自社サイトに欠けていたサービス構造を実際に実装した記録。Service/Offer/OfferCatalogの選定、価格の構造化(受託は出さず・診断は単価で)、@idでの接続まで。連載第5回。

構造化データの引っ越し 第5回:空いていた層を埋める — 自社サイト改修ドキュメンタリー

前回(第4回)、私たちは構造化データを4つの層——情報設計・文書構造・マークアップ・能力——として捉え直し、その地図の上に untype.jp の「空白」を置いた。会社の名前も住所も記事も機械可読なのに、肝心の「何を売っているか」が構造として書かれていない。AI可読性を売る会社の自社サイトで、売り物だけが機械に読めない。前回そう呼んだ紺屋の白袴を、今回は実際に脱ぐ。

この改修は、連載を書きながら本当に実施した。語るために作った架空の例ではない。untype.jp 本体のリポジトリで、意味のまとまりごとにコミットを分け、レビューを経て本番に反映した。だから以下に並べる before / after は、すべて私たちの git に残っている実物だ。第3回が「改修前のスナップショット」を読む回だったとすれば、今回はその差分そのものを開く。

まず棚卸し — 埋まっていた層と、空いていた層

手をつける前に、現状を地図に起こした。第3回で読んだとおり、untype.jp の構造化データは1つのファイル(schema.ts)に中央集約され、ページ種別で出し分ける設計になっている。土台は、むしろよく整っている部類だった。会社とサイトの素性(Organization / WebSite)、記事と著者(BlogPosting / Person)、ページ階層(BreadcrumbList)、よくある質問(FAQPage)、さらに llms.txt や RSS、sitemap まで揃っている。

空いていたのは、次の4か所だった。

対象 改修前 改修後
サービス各ページ カードとして人間に描画されるだけ。スキーマなし Service ノードを付与
サービス一覧 汎用 WebPage のフォールバックのみ OfferCatalog を付与
会社情報(Organization) 名前・住所・ロゴ・SNS のみ。何の会社かは未記述 説明・専門領域(knowsAbout)・連絡先を拡充
実績ページ 汎用 WebPage のフォールバックのみ CreativeWork を付与

表:改修前後の対応。土台にあたる Organization / WebSite / BlogPosting / BreadcrumbList / FAQPage は維持し(触らない)、空いていた4か所だけを埋める。改修は「足りないものを足す」最小の方針で進めた。

地図を持っていたおかげで、やることが「サイト全体をスキーマだらけにする」ではなく、「この4か所を、この語彙で埋める」という具体に絞れた。第4回で言った「地図があると優先順位が決まる」は、まずこの絞り込みとして効いた。

なぜ「サービス」の空きが、いちばん痛かったか

4か所のうち、最も埋めるべきはサービスだった。エージェントの立場で考えると理由がはっきりする。

AIエージェントが untype.jp を訪れて「この会社は何者か」と問えば、Organization から社名・所在地・設立年を推測なしに受け取れる。「どんな記事を書いているか」も BlogPosting から辿れる。ところが「何を、いくらで提供しているのか」を問うと、機械可読の層は沈黙していた。サービスは画面上のカードとしては存在するのに、構造としては存在しない。看板商品であるAIエージェント互換性診断ですら、ServiceOffer のノードを持っていなかった。

これは、エージェントが企業と取引を始める時代——当社がAIエージェントがあなたの会社と取引する日で書いた世界——では、致命的に近い欠落だ。取引の入り口は「何を提供しているか」の認識である。そこが読めなければ、エージェントの検討対象にすら入らない。会社がAIにどう知られるか——当社がAIは会社をどう知っているかで論じた問い——を左右するのは、まさにこの「提供物の構造」なのだ。

実装(1) — サービスを Service ノードにする

語彙の選定そのものは、難しくなかった。提供する役務は Service で表す。これは schema.org の素直な型だ。各サービスページに、次のかたちのノードを足した(AI統合支援を見本に、5サービスへ横展開した)。

JSON
{
  "@type": "Service",
  "name": "AI統合・活用支援",
  "serviceType": "AI integration",
  "description": "(各ページの既存の説明文を流用)",
  "provider": { "@id": "https://www.untype.jp/#organization" },
  "areaServed": { "@type": "Country", "name": "日本" },
  "url": "https://www.untype.jp/services/ai"
}

ここで効いているのが provider だ。サービスの提供者である会社を、住所もろとも書き写すのではなく、@id#organization参照している。第3回で見た「実体はひとつ、参照は何本でも」という作法を、新しいノードにもそのまま適用した。

実装の進め方も記しておく。サービス判定のロジックをスキーマ生成の中心に埋め込むと、保守が重くなる。そこで、どのページからでも「自分のノードを作って渡せる」汎用の差し込み口を1つ用意し、各ページは自分の Service を作ってそこに渡すだけ、という形にした。サービス一覧(OfferCatalog)も、5サービスをハードコードせず、CMS から取得した一覧データから動的に生成している。サービスが増減しても、構造化データが本文とズレない——これも一種の「嘘をつかない」設計だ。

実装(2) — 価格をどう構造化するか、という判断

サービスを埋める作業の本当の山場は、語彙選定ではなく価格の扱いだった。ここは「どう書くか」ではなく「どう判断するか」の問題で、いちばん時間をかけた。

untype.jp の提供物は、性質が二つに分かれる。受託の制作・支援(Web・AI・SEO・アプリ・ブランディング)は、案件ごとの見積もりで、固定価格が存在しない。一方、互換性診断は価格が定義できる。前者に無理やり価格をつければ嘘になり、後者で価格を伏せれば情報を捨てることになる。そこで、次のように切り分けた。

サービス類型 schema 表現 価格の出し方
受託(Web / AI / SEO / アプリ / ブランディング) Service 出さない(要見積のため)
互換性診断・無料1ページ版 Service + Offer price = "0"
互換性診断 Pro(従量・累進) Service + Offer UnitPriceSpecification(1ページ単価「〜から」)

表:価格の構造化方針。価格が存在しないものに固定価格を書かない。無料は price = "0" と明示する。累進課金は固定価格では表現できないため、1ページあたりの単価を「基準量つきの単価」として渡す。

問題は Pro 版だった。Pro は累進課金で、ページ数に応じて1ページあたりの単価が下がる。つまり「いくら」という固定価格が原理的に存在しない。ここで price に何か1つの数字を書けば、それはどこかで必ず嘘になる。schema.org には、こういう「単位あたりの価格」を表すための UnitPriceSpecification という型がある。これを使って、こう書いた。

JSON
{
  "@type": "Service",
  "name": "AIエージェント互換性診断 Pro(サイト一括診断)",
  "provider": { "@id": "https://www.untype.jp/#organization" },
  "offers": {
    "@type": "Offer",
    "priceCurrency": "JPY",
    "priceSpecification": {
      "@type": "UnitPriceSpecification",
      "price": "550",
      "priceCurrency": "JPY",
      "referenceQuantity": {
        "@type": "QuantitativeValue",
        "value": "1",
        "unitText": "ページ"
      },
      "description": "累進課金。最初の10ページは税込550円/ページ、以降はページ数に応じて単価逓減。"
    }
  }
}

referenceQuantity(基準量=1ページ)に対する price(550円)として、単価を表している。固定の総額ではなく「1ページあたり、ここから」という事実だけを渡す。description に逓減の説明を添え、誤読の余地を減らした。

ひとつ実務上の工夫を加えた。この 550 という数字を JSON に直書きしていない。料金ロジックを持つコードから単価を算出して埋めている(1ページの基本単価500円に消費税を乗せた値だ)。料金を改定したとき、本文の価格表は直したのに構造化データだけ古い、という乖離が起きないようにするためだ。構造化データの最大の罠は、機械可読の「嘘」を放置することにある。価格は、その嘘が最も生まれやすい場所だ。

実装(3) — 会社の身元を、もう一段詳しくする

第3回の最後で、もうひとつ宿題を残していた。改修前の Organization には「名前・場所・ロゴ・SNS」はあっても、「何の会社か」を機械可読で語るプロパティが無かった。ここを拡充した。

既存のフィールドはそのまま残し、次を足している(抜粋)。

JSON
"@type": "Organization",
"description": "AI時代のコミュニケーションデザイン会社。Web制作・AI統合支援・SEO・アプリ開発・ブランディングを通じ、企業の情報を人とAIの双方に伝わる形へ構造化する。",
"knowsAbout": [
  "コミュニケーションデザイン", "構造化データ (Schema.org)",
  "AIエージェント対応 (MCP / A2A)", "RAG", "SEO", "Web制作", "ブランディング"
],
"contactPoint": {
  "@type": "ContactPoint",
  "contactType": "sales",
  "url": "https://www.untype.jp/contact",
  "availableLanguage": ["ja"]
},
"founder": { "@type": "Person", "name": "山下太郎" },
"areaServed": { "@type": "Country", "name": "日本" }

核は knowsAbout だ。これは「この組織が精通している領域」を列挙するプロパティで、LLM に「何の会社か」を推測させずに渡す。description の自由文と合わせて、機械が事業内容を取り違える余地を狭めた。contactPoint は、メールアドレスや電話番号を直書きせず、問い合わせページの URL で表現している。連絡手段は伝えつつ、個人情報をマークアップに晒さないための判断だ。

型の選び方には、ひとつ留保がある。会社ノードには、より具体的な ProfessionalService という型を併記する選択肢もあり、実装の途中まではそうしていた。だが ProfessionalService は、schema.org が「Service との混同」を理由に非推奨(deprecated)にした型だと分かった。語彙には残っているので構文検証は通ってしまうが、現在の推奨は明快だ——事業者そのものは Organization、提供する役務は Service で表す。役務はすでに Service で書いたのだから、会社の身元は Organization のままでよい。そこで会社ノードは Organization 単独に据え直した。退場した ProfessionalService の非推奨理由が、ほかでもない私たちが役務に選んだ Service との混同だった——という符合まで含めて、この整理は腑に落ちるものだった。

@id で、ばらばらのノードを一つの会社に束ねる

ここまでで、サービス5つ、サービス一覧、診断、実績——新しいノードがいくつも増えた。それらが互いに無関係に散らばっていては、機械は「これらは同じ会社の話だ」と辿れない。第3回で見た @id 参照を、増えたノード全部に効かせる必要があった。

結果として、新しいノードはすべて、たった一つの会社ノード(#organization)を指している。

graph LR WS["WebSite"] -->|publisher| ORG SVC["Service ×5"] -->|provider| ORG OC["OfferCatalog"] -->|itemOffered| SVC DIAG["Service(診断)"] -->|provider| ORG DIAG -->|offers| OFF["Offer"] CW["CreativeWork(実績)"] -->|creator| ORG ORG["Organization"]

図:改修後の参照構造。サービス・サービス一覧・診断・実績の各ノードが、providercreator といった関係で、一つの会社ノード(#organization)を参照する。会社の情報は一度だけ定義され、何本もの矢印で指し示される。機械から見ると「これらはすべて同じ事業者の提供物だ」と曖昧さなく辿れる。

この「全部が一点を指す」構造を作る過程で、当初の計画になかった対応が一つ必要になった。トップページ以外のページには、もともと会社ノードの実体が @graph の中に置かれていなかった。そのままサービスノードだけを足すと、provider が指す #organization が同じまとまりの中に見当たらず、参照が宙吊りになる。そこで、サービスや実績を持つページの @graph にも会社ノード(@id 参照の起点)を加え、参照が同一のまとまり内で必ず解決するようにした。@id は「書けば繋がる」のではなく、「指す先が同じ @graph にいて初めて繋がる」。地味だが、ここを外すと構造が崩れる。

実装でぶつかった、schema.org の作法

もう一つ、検証ツールに教えられた失敗を残しておく。

サービス一覧の OfferCatalog に、当初は「提供者はこの会社だ」と示すつもりで provider を付けた。ところが構文検証にかけると、警告が出た。OfferCatalog は項目のリストを表す型(ItemList のサブ型)であって、provider というプロパティをそもそも持っていなかったのだ。仕方なく provider は外し、会社との紐付けは、同じ @graph の中にいる会社ノードを @id で指す形に委ねた。schema.org は自由作文ではなく、型ごとに「持てるプロパティ」が決まっている。意味が通りそうでも、語彙にない組み合わせは通らない。

検証には、Google のリッチリザルトテストではなく、schema.org 公式の構文検証ツール(Schema Markup Validator)を使った。理由は二つある。ServiceOffer も、そもそも Google のリッチリザルト(検索結果の装飾表示)の対象型ではない。加えて、第2回で見たとおり Google の FAQ リッチリザルト(表示)はすでに終了している。装飾されるかどうかではなく、構文として正しいかどうかを見るべき局面なので、公式バリデータが正だ。

最終的に、Organization・Service・OfferCatalog・診断の Service+Offer・CreativeWork のすべてが、エラーも警告もなく通った。ビルド時の型チェックも0件で、本番に反映した。なお、診断ページにはもともと別の型(ソフトウェアとしての記述)も載っていたが、これは消さず、同じ @graph に合流させて併存させている。

この一連の作業が示しているのは、「構造化データは書けば効く」ではなく「規約に従って正しく書く仕事だ」ということだ。正しさを保証してくれるのは期待ではなく、バリデータである。

埋めた後に、何が変わったのか(と、変わらないこと)

改修前、untype.jp の機械可読層は5つのノードでできていた。改修後は、そこに5つの Service、サービス一覧の OfferCatalog、診断の Offer、実績の CreativeWork が加わり、会社ノードは事業内容と専門領域と連絡先を語るようになった。

構造的な事実として、こう言える。改修前、エージェントが「unType は何を、いくらで提供しているのか」と問えば、機械可読の層は何も答えられなかった。改修後は、サービスの一覧と、診断の価格構造を、推測なしに取り出せる。 バリデータがその構文の正しさを裏付けている。これは、外部の評価を待たずに確かめられる事実だ。

一方で、言えないことも、はっきり書いておく。だからといって「AIに引用される回数が増える」「診断スコアが必ず上がる」と約束はできない。 構造化データの整備と、AIに引用されることのあいだにあるのは相関であって、因果ではない。改修の前後でたまたまスコアや露出が動いたとしても、それを構造化データ「だけ」の手柄にするのは、この連載が最初から戒めてきた過剰な期待そのものだ。何が言えて、何が言えないのか——その線引きは、次回(第6回)でメリット・デメリットと技術ハードルとして正面から扱う。

それでも今回の改修には、外部の反応とは独立した確かさがある。少なくとも、自社の提供物が「正しい構造」で存在するようになった。これは「AIに見つけてもらうため」という外部の報酬を当てにする前に、まず自分たちの手元で確定できる価値だ。この「外部報酬から内部報酬へ」という軸は、終章でもう一度だけ引き取る。

紺屋の白袴を、自分で脱いだ

今回は、第4回で描いた地図の「空白」を、実際に埋めた記録を辿った。サービスを Service に、その提供者を @id で会社に結び、価格は性質に応じて出し分け(受託は出さず、診断は出し、累進は単価で)、会社の身元には事業内容を足し、増えたノードはすべて一つの会社ノードへ束ねた。途中、OfferCatalog の語彙の制約や、@id の宙吊りに足をすくわれながら、バリデータで全ノードの正しさを確認して本番に出した。AI可読性を売る会社が、自社の売り物をようやく機械に読めるようにした——紺屋の白袴を、自分で脱いだわけだ。

地図があったから、優先順位を間違えずに動けた。情報設計(何を、どんな単位で提供物と捉えるか)を先に決め、それを語彙に落とす、という順序だ。いきなりマークアップから始めていたら、たぶん価格の扱いで迷子になっていた。

ただし、構造化データは万能ではない。むしろ、正しく書こうとするほど、その限界とコストが見えてくる。次回は、ここまで前向きに語ってきたこの技術の、メリットとデメリット、そして実装の現場で本当にぶつかるハードルを、正直に並べる。道具を正しく使うために、道具の効かない範囲を知る回だ。

参照情報
山下 太郎

山下 太郎

代表取締役 / CEO

2000年、Webデザイナーとしてこの世界に飛び込み、フリーランスを経て2007年に株式会社アンタイプを創業。AI時代の到来とともに、効率だけを追うAI活用に違和感を覚えながら、それでも最前線でツールを使い続ける。企業のWebとコミュニケーションを設計する仕事を通じて、「人間らしさとは何か」を問い直す視点を発信し続けている。

View Profile arrow_outward

Related

あわせて読みたい