Article

サイト構造とSEO:カテゴリ設計と内部リンクでクローラビリティ向上

高田晃太郎
サイト構造とSEO:カテゴリ設計と内部リンクでクローラビリティ向上

Googleは公式に「小中規模サイトはクロール予算を意識する必要は少ない」と述べていますが、一方で大規模サイトでは内部リンクや重複URLがクロール効率に影響することも明記しています。¹ 検索エンジンは有限のリソースでページを発見・再訪し、評価とインデックスを更新します。Impervaのレポートでは2023年版の分析で、2022年のWebトラフィックの47.4%がボット由来と報告されており、「約半分」という状況が続いています。² ここでのクローラビリティ(検索エンジンのクローラがサイトを発見・クロール・レンダリング・インデックスできる容易さ)は、純粋なSEO施策ではなく、配信アーキテクチャと情報設計(サイト構造・カテゴリ設計・内部リンク)の統合課題だと捉えるべきです。実務の事例でも、カテゴリ設計と内部リンクの見直しだけで探索深度が一段浅くなり、再クロール周期が短縮されることは繰り返し観測されます。重要なのは、見出しの飾りではなく、カテゴリ=トピックのハブ、内部リンク=グラフのエッジ、という前提で設計・計測・運用を回すことです。本稿では「サイト構造」「カテゴリ設計」「内部リンク」「クローラビリティ改善」「クロール予算」といったSEOの中核キーワードを軸に、実装と計測の手順を示します。

クローラビリティは経営課題:現状診断を計測に落とし込む

最初の仕事は可視化です。クローラビリティは抽象概念ではなく、探索深度(ハブから到達するクリック数)、発見率(サイトマップ掲載URLのクロール到達割合)、再訪間隔(同一URLへのクロール間の時間)、レンダリング完了率(JavaScriptを含むページが最終描画まで到達した比率)といった計測値で捉えるべきです。Googleのドキュメントにある通り、クロール予算はサイトの人気や健全性、リソース状況の関数です。¹ したがって、内部リンクの構造とサーバー側の応答安定性が土台になります。Googlebot はサーバーがクロール リクエストに応答できない兆候を検出するとクロールを縮小するため、配信層の健全性は直接的にクロール効率へ影響します。³ 実務では、サーバーログとサイトマップ、そしてSearch Consoleのデータを突き合わせ、どのURL集合が発見されずに滞留しているかを確認するのが定石です。特に気をつけたいのは、テンプレート由来の重複パス(同一・類似コンテンツが複数URLで公開される状態)とファセットナビゲーション(サイズや色などの組み合わせでURLが増殖する機能)です。これらはカテゴリ設計の破綻が原因のことが多く、内部リンクの意図とログの実態が乖離していればクローラビリティは確実に落ちます。

発見されないURLを特定する:サイトマップ×ログの突合

発見率の低さは体感ではなくデータで捉えます。XMLサイトマップに載せたURLがクロールされているかを、ログと結合して確認します。BigQueryにNginxログを積んでいるなら、次のようにGooglebotの訪問有無を判定し、孤立URLの集合を抽出できます。

-- sitemap_urls(url STRING) と access_logs(ts TIMESTAMP, url STRING, ua STRING, status INT)
WITH gb AS (
  SELECT url, COUNT(*) AS hits
  FROM access_logs
  WHERE REGEXP_CONTAINS(ua, r"Googlebot")
  GROUP BY url
)
SELECT s.url
FROM sitemap_urls s
LEFT JOIN gb ON s.url = gb.url
WHERE gb.hits IS NULL;

ここで抽出されたURLは、内部リンクから辿れない、もしくは優先度が低くクローラに届いていない可能性があります。優先度付けの誤りか、カテゴリからのリンク不足か、いずれにせよ情報設計側の手当てが必要です。併せて、ステータスコードの分布も確認し、200以外がスパイクしていれば配信層(CDN・オリジン)の健全性から見直します。

探索深度を測る:グラフとしてのサイトを解析

探索深度は「ホームやカテゴリハブから何クリックで到達できるか」の最短距離として計測できます。静的にサイトマップと内部リンクの一覧を生成し、グラフ解析で最短路を計算すると、設計の歪みが露わになります。PythonでNetworkXを用いれば、短時間で把握できます。

import csv
import networkx as nx

G = nx.DiGraph()
with open('internal_links.csv') as f:  # from_url,to_url のCSV
    for fr, to in csv.reader(f):
        G.add_edge(fr, to)

roots = ["https://example.com/", "https://example.com/category/"]
min_depth = {}
for n in G.nodes:
    depths = []
    for r in roots:
        try:
            depths.append(nx.shortest_path_length(G, r, n))
        except nx.NetworkXNoPath:
            pass
    min_depth[n] = min(depths) if depths else None

too_deep = [u for u, d in min_depth.items() if d is None or d > 3]
print(len(too_deep), "pages deeper than 3 clicks or unreachable")

業界では重要ページを3クリック以内に収めることが目安とされます。⁴ 到達不可や4クリック以上のページが多い場合、カテゴリ設計でトピックのハブが不足しているか、内部リンクの導線がコンポーネント化されておらずテンプレートごとに散逸している可能性が高いと言えます。

情報設計の中核:カテゴリ設計の原則とアンチパターン

カテゴリはラベルではなくトピックのハブです。検索エンジンはカテゴリページをそのテーマの要約・網羅ページとして解釈しやすく、適切なスニペットやサブリンク生成にも影響します。推奨する原則はシンプルで、URL、パンくず、ページ内リンク、サイトマップを一貫させ、カテゴリハブから代表的な子ページに十分な内部リンク権威を分配することです。ここに、キーワード戦略も必ず結び付けます。具体的には、各カテゴリを「主キーワード(例:レディース アウター)」にマッピングし、サブカテゴリや子ページには検索意図が重複しない関連語(例:コート、ジャケット、ダウン)を割り当てます。ハブから子へのアンカーテキストは、対象ページの主キーワードや近接概念を「不自然にならない範囲で」含め、同一意図のページ同士でキーワードを食い合わないよう(カニバリゼーションの回避)に整えます。アンチパターンは、フィルタの組み合わせで無限に増殖するファセットURL、日付アーカイブの乱立、言い換えカテゴリの重複、そしてタグ乱造によるテーマの希釈です。これらはクローラビリティを直接毀損します。

URL設計とパンくず整合性:意味論を崩さない

URLは情報設計の契約です。/category/ladies/outer/ のような階層がパンくずと一致し、ハブとなる各階層が固有の説明テキストと代表リンクを持つことが重要です。構造化データ(検索エンジンに文脈を伝えるためのマークアップ)のBreadcrumbListは、クローラに関係性を明示する補助になります。次のJSON-LDは、カテゴリページに最小限必要な例です。

<script type="application/ld+json">{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {"@type": "ListItem", "position": 1, "name": "Home", "item": "https://example.com/"},
    {"@type": "ListItem", "position": 2, "name": "Ladies", "item": "https://example.com/ladies/"},
    {"@type": "ListItem", "position": 3, "name": "Outer",  "item": "https://example.com/ladies/outer/"}
  ]
}</script>

注意したいのは、パンくずと内部リンクセクションが異なる階層関係を示してしまう設計です。この不一致はクローラを迷わせ、評価の集中を阻害します。カテゴリ本文にはトピックの定義、関連サブカテゴリ、代表コンテンツのリンクを置き、単なる一覧に終わらせないことが肝要です。

ファセットと重複の制御:noindex・canonical・Disallowの使い分け

サイズ・色・価格などのファセットでURLが爆発すると、クローラビリティは急落します。ここでありがちな誤りは、robots.txtで一律にDisallowしてしまうことです。クロールを止めるだけではインデックスの削除にならず、さらにブロックしたためにmetaタグのnoindexが読まれない状態に陥ります。⁵ 基本方針は、代表URLへ統合できる場合はrel=“canonical”(評価の集約先を示す)で集約し、一覧系の無限組合せにはindexを許可したい集合をホワイトリスト化して、それ以外はmeta robotsでnoindex(インデックス拒否)を返しつつクロールは許容する、です。どうしてもサーバー負荷が懸念される場合のみ、キャッシュ戦略と合わせてrobots.txtでの制限(Disallow:クロール拒否)を最小限に適用します。実装はテンプレート分岐で確実に行い、HTTPヘッダでも指示を二重化します。

<head>
  <link rel="canonical" href="https://example.com/ladies/outer/">
  <meta name="robots" content="noindex,follow"> <!-- 非代表のファセット -->
</head>

この設定により、代表URLへの評価集中とクローラビリティ維持の両立が可能になります。Disallowを使うのは、検索面に出す必要が全くない内部ユーティリティや、セッション付きの動的ページなど、インデックスされると明確な不利益が生じる範囲に限るべきです。

内部リンク戦略:グラフ思考でエッジを設計する

内部リンクはナビゲーションの配置問題ではなく、グラフのエッジ設計です。カテゴリハブから子ページへ、子ページからハブと兄弟へ、そして横断ハブから関連クラスタへと、リンクの流れをテンプレートで保証する必要があります。アンカーテキストは、ユーザーに自然で意味が通る範囲で対象ページの主キーワードやシノニムを含め、リンク先のトピックを明確化します。JavaScriptのクリックハンドラで遷移を作ると、一部のクローラはリンクを発見しにくくなります。必ずaタグで記述し、リンクの可視範囲はCLS(Cumulative Layout Shift:レイアウトのズレ)やアクセシビリティ面も考慮してビューポート内に配置します。関連コンテンツの自動選択は、タグの一致やカテゴリ階層に基づく単純なルールから始め、運用でCTRやスクロール深度を指標に精度を高めると良いでしょう。

テンプレートに組み込む:ハブと子の双方向リンク

カテゴリハブのテンプレートには、代表子ページ群への常設リンクセクションを用意し、子ページのテンプレートにはパンくずに加えてハブへの明示リンクと、関連の兄弟ページを出すセクションを設けます。単純なサーバーサイド実装なら、タグ一致での関連抽出が運用効率と再現性の両面で扱いやすいです。

-- posts(id, title, url), post_tags(post_id, tag), current_post_id を前提
SELECT p.title, p.url
FROM posts p
JOIN post_tags t ON t.post_id = p.id
WHERE t.tag IN (
  SELECT tag FROM post_tags WHERE post_id = :current_post_id
)
AND p.id != :current_post_id
GROUP BY p.id, p.title, p.url
ORDER BY COUNT(*) DESC, p.id DESC
LIMIT 6;

フロントではa要素で確実にリンクを明示します。見た目のカードUIであっても、リンクはDOMロード時に存在していることが重要です。遅延レンダリングの範囲に入る場合は、サーバーサイドレンダリングやハイドレーション前のリンク埋め込みを検討します。

<section aria-label="Related">
  <article>
    <a href="/ladies/outer/coat-123">今季の定番コート</a>
  </article>
  <article>
    <a href="/ladies/outer/jacket-456">軽量ジャケット特集</a>
  </article>
</section>

内部リンクの重みづけ:簡易PageRankで優先度を推定

どのページにリンクを集中させるべきかは、簡易的なPageRankで見ても示唆が得られます。次のPythonスクリプトは内部リンクのCSVからスコアを計算し、カテゴリハブの相対的重要度を把握します。スコアの高いノードに露出面やリンク数を配分し、深いノードはハブからの直リンクで救済します。

import csv
import networkx as nx

G = nx.DiGraph()
with open('internal_links.csv') as f:
    for fr, to in csv.reader(f):
        G.add_edge(fr, to)

pr = nx.pagerank(G, alpha=0.85)
for url, score in sorted(pr.items(), key=lambda x: x[1], reverse=True)[:20]:
    print(f"{score:.5f}\t{url}")

このようなスコアは正解ではありませんが、設計レビューの土台になります。クローラビリティという観点では、スコア上位のハブに更新頻度の高い子を束ねるほど、再クロールの波及が効率的に広がります。

運用で効かせる:ログとSearch Consoleで反復改善

設計は一度作って終わりではありません。クロールはトラフィックと同様に季節性や更新頻度の影響を受けます。したがって、30〜90日のタイムウィンドウでログとSearch Consoleのシグナルを観測し、内部リンクとカテゴリの微調整を続けるのが実務です。観測ポイントは、Googlebotのヒットとステータス、キャッシュのHIT率、重要URLの再訪間隔です。Search Consoleのサイトマップ送信とカバレッジは、カテゴリ単位で分割して監視すると変化が掴みやすくなります。

Googlebotの挙動を切り出す:ログの軽量分析

大掛かりなスタックを用意せずとも、サンプリングと軽量集計で傾向は掴めます。NginxログからGooglebotアクセスを抽出し、主要カテゴリごとの比率とステータスの異常を確認します。検証済みユーザーエージェントの逆引き(rDNSでの正当性確認)も併用すると確実です。

grep -E "Googlebot" access.log \
 | awk '{print $7, $9}' \
 | awk -F'/' '{print "/"$2"/", $0}' \
 | awk '{cnt[$1]++; status[$1":"$3]++} END {for (k in cnt) print k, cnt[k]}'

ステータスの揺れが大きいカテゴリや、ヒットが極端に少ないカテゴリは、内部リンクやテンプレート露出の再点検候補です。特に304やキャッシュのHIT率は、再クロールの効率と直接結びつくため、CDN設定と合わせて監視します。

サイトマップをカテゴリ単位で分割し、反応を見る

サイトマップは一枚岩にせず、カテゴリやタイプごとに分割します。これにより、Search Consoleでカバレッジや送信・検出の差異を可視化できます。生成はビルドパイプラインに組み込み、変更検知時に必ず更新・Ping送信します。シンプルなPythonでの分割例を示します。

from xml.etree.ElementTree import Element, SubElement, tostring

cats = {"ladies": ladies_urls, "mens": mens_urls}
for name, urls in cats.items():
    urlset = Element('urlset', xmlns="http://www.sitemaps.org/schemas/sitemap/0.9")
    for u in urls:
        url = SubElement(urlset, 'url')
        SubElement(url, 'loc').text = u
    with open(f'sitemap-{name}.xml', 'wb') as f:
        f.write(tostring(urlset))

送信後のインデックス登録の推移と、前述のログでの再訪周期を突き合わせると、クローラビリティ改善の因果を追いやすくなります。カテゴリ設計と内部リンクの変更は同時に行わず、施策を分離してインパクトを測ることを強く推奨します。

実務での意思決定に役立つ関連リソースとして、配信視点からの最適化を解説した、クローラによるレンダリングを考慮した、送信戦略の定石をまとめた、パフォーマンスと発見性の関係を掘り下げたも併せて参照してほしいと思います。

まとめ:カテゴリをハブに、内部リンクを戦略に

クローラビリティは偶然ではなく設計の結果です。カテゴリ設計がトピックの地図として機能し、内部リンクがその地図に沿って3クリック以内で主要ページへ導くとき、クロールの効率は安定して向上します。まずは現状をデータで可視化し、発見されないURLと深すぎるノードを特定してください。次に、ハブと子の双方向リンクをテンプレートに組み込み、ファセットの重複はcanonicalとnoindexの正しい組み合わせで抑制します。そして、ハブ・子・アンカーテキストのキーワードマッピングを明文化し、意図の重複を避けます。最後に、ログとSearch Consoleを30〜90日のサイクルで観測し、施策を分離して反応を見るという運用を回しましょう。次に取り組むべきは、最重要カテゴリを一つ選び、そのハブと代表子ページのリンク設計と主キーワードを今日中に書き出すことです。紙でもスプレッドシートでも構いません。地図を描けば、クローラは迷いませんし、ユーザーも迷いません。

参考文献

  1. Google 検索セントラル: 大規模サイトのクロール バジェットの管理
  2. Imperva: 2023 Imperva Bad Bot Report — Key Learnings
  3. Google 検索セントラル: 大規模サイトのクロール バジェットの管理(サーバー負荷時のクロール縮小に関する記述)
  4. The Web Almanac 2020: SEO(日本語版)
  5. Google 検索セントラル: Google 検索結果に表示しないようにする(インデックスのブロック方法)