📑

ログファイル分析とクロールバジェット

Googlebot がサイト上で実際に何をしているかを把握し、ゴミ URL でクロールバジェットを浪費するのをやめよう。

📖 13 分で読めます 🕑 更新日 2026-06-22

ほとんどの SEO ツールは、サイトに何が起こりうるかを教えてくれる。サーバーログは、実際に何が起こったかを教えてくれる。Googlebot が URL をフェッチするたびに、サーバーはログファイルに 1 行を書き込む — 実際の URL、実際のステータスコード、実際のタイムスタンプ。サンプリングも、推測も、Google のふりをするサードパーティのクローラーもない。検索エンジンがサイト上で実際に何をしているかを知りたいなら、ログだけが唯一の真実の源泉だ。

このガイドは、その真実を読み取り、それに基づいて行動することについてのものだ。レンズとなるのは crawl budget(クロールバジェット)、すなわち Google があなたのサイトに費やそうとするクロール量の有限な総量だ。小規模サイトでは、このバジェットは実質的に無限であり、次のセクションを読んだら読むのをやめてよい。大規模サイト — EC カタログ、マーケットプレイス、数十万 URL を持つプログラマティックサイト — では、crawl budget は厳しい制約であり、ログはどこでそれが漏れているかを見つける手段となる。

🧑‍💻 開発者視点: ログファイルは (timestamp, ip, method, url, status, user_agent) でキー付けされた追記専用のイベントストリームにすぎない。このガイドのすべては、そのストリームに対する GROUP BYWHERE 句だ。アクセスログからサービスをデバッグしたことがあるなら、ログベースの SEO に必要な筋肉はすでに備わっている。

クロールバジェットとは何か

「Crawl budget」は Google が公表している数値ではない。それは、Google がすべてのサイトについてバランスを取る 2 つの力が生み出す創発的な結果だ。

Crawl rate limit(クロール率の上限) は、Google がどれだけ強くサーバーを叩くかの天井だ。Googlebot は意図的に礼儀正しい。レスポンスが遅くなったり 5xx エラーを返し始めたりすると、サイトをダウンさせないよう自動的に引き下がる。高速で健全なサーバーは天井を上げ、遅い・不安定なサーバーは下げる。これは供給側 — インフラがどれだけのクロールを吸収できるか — だ。

Crawl demand(クロール需要) は、Google があなたをどれだけクロールしたいかだ。これは人気度(リンクとトラフィックの多い URL ほどクロールされる)と陳腐化度(Google は変更されたと考えるページを再クロールする)によって駆動される。記事が常に更新されるニュースサイトは需要が高く、静的なパンフレットサイトは需要が低い。これは需要側 — コンテンツが実際にどれだけのクロールに値するか — だ。

実効的な crawl budget はおおむね min(rate limit, demand) だ。両方を引き上げることで最適化する。高速に配信して上限を引き上げ(limit を上げる)、重要なページに Google の注意を集中させる(demand を形作る)。

サイト規模(インデックス可能 URL 数)クロールバジェットは重要か?
約 1 万未満ほぼ重要でない — Google が簡単にすべてクロールする
1 万〜10 万場合による — URL パラメータやファセットナビがあれば注視
10 万超重要 — これは主要なレバーになる
クロールトラップがあればどんな規模でも重要 — カレンダーやフィルターの爆発は小規模サイトすら沈める

⚠️ 注意: 最も一般的なクロールバジェットの惨事は大規模サイトではない — 無限の URL を生成するトラップ(日付ピッカー、上限のないフィルターの組み合わせ)を持つ小規模サイトだ。Google は 40 万件の無用なフィルター順列のクロールにバジェット全体を燃やし尽くし、その間に 200 件の本物の商品ページが陳腐化していく。

サーバーログ 101

ウェブサーバーのアクセスログは、1 リクエストにつき 1 行だ。古典的なフォーマットは、Apache と Nginx がデフォルトで出力する NCSA「combined log format」だ。

66.249.66.1 - - [22/Jun/2026:08:14:32 +0000] "GET /products/widget-42?color=red HTTP/1.1" 200 5123 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"

デコードすると、SEO で気にすべきフィールドは次のとおりだ。

フィールドなぜ重要か
クライアント IP66.249.66.1本物の Googlebot の検証(逆引き DNS)
タイムスタンプ22/Jun/2026:08:14:32クロール頻度、鮮度
メソッド + URLGET /products/widget-42?color=red何がクロールされたか、パラメータの浪費
ステータスコード200404、リダイレクト、5xx エラー
バイト数5123ペイロードサイズ、レスポンスの重さ
ユーザーエージェント...Googlebot/2.1...どのクローラーが URL を叩いたか

ログを入手すること自体が本当のハードルだ。 ログがどこにあるかはスタックによる。

  • セルフホストの Nginx/Apache/var/log/nginx/access.loglogrotate で日次ローテーション。最も簡単なケース。scp するだけだ。
  • クラウドのロードバランサー — AWS ALB はアクセスログを S3 に書き込み、GCP のロードバランサーは Cloud Logging に書き込む。リスナーごとに有効化する。
  • CDN の背後 — これがトラップだ。Cloudflare、Fastly、CloudFront の背後にいると、Googlebot は origin ではなく CDN エッジ を叩く。origin ログにはキャッシュミスしか見えないため、クロール数を大幅に過小評価する。CDN のログが必要だ。
  • 特に Cloudflare の場合Logpush を使って HTTP リクエストログを R2(または S3、外部シンク)へ継続的にストリームする。これは Cloudflare ホスティングのサイトにおける定番の構成であり、下の Cloudflare セクションで順を追って解説する。

💡 ヒント: 結論を出す前に少なくとも 30 日分のログを集めること。Googlebot の 1 日のクロールパターンはノイズが多いが、1 か月単位では浪費パターンが紛れもなく見えてくる。再クエリできるローリングウィンドウを目指そう。

Googlebot の分析

「Googlebot」に帰属するログ 1 行を信用する前に、それを検証しよう。ユーザーエージェント文字列は簡単に偽装できる — スクレイパーや競合は、レート制限を回避するために日常的に Googlebot を名乗る。偽の Googlebot ヒットをクロールバジェットとして数えると、幻を追いかけるはめになる。

逆引き + 正引き DNS で検証する。 本物の Googlebot の IP は googlebot.com または google.com に逆引きされ、そのホスト名は同じ IP に正引きされなければならない。偽装者はユーザーエージェントは制御できても、Google の DNS は制御できない。

# 逆引き: その IP は Google のホスト名を名乗っているか?
host 66.249.66.1
# -> 1.66.249.66.in-addr.arpa domain name pointer crawl-66-249-66-1.googlebot.com.

# 正引き: そのホスト名は同じ IP に解決されるか?
host crawl-66-249-66-1.googlebot.com
# -> crawl-66-249-66-1.googlebot.com has address 66.249.66.1   ✓ verified

IP ごとの DNS 引きをしたくない場合、Google はクローラーの IP レンジを https://developers.google.com/static/search/apis/ipranges/googlebot.json で JSON として公開している — クライアント IP をそれらの CIDR とオフラインで照合しよう。

データを信用できるようになったら、それに対して 4 つの問いを立てる。

何がクロールされているか? ヒットを URL でグループ化し、リストの上位を見る。健全なサイトでは、最もクロールされる URL は最も重要なページだ。上位クロール URL が ?sessionid= のバリアントやフィルターの順列なら、それが漏れの 1 つ目だ。

重要ページはどれくらいの頻度でクロールされているか? 収益ページの最終クロールタイムスタンプを引き出す。トップのカテゴリページが最後にフェッチされたのが 18 日前なら、Google はそれを陳腐化している、あるいは重要でないと考えている — これは鮮度の問題だ。

バジェットはどこで浪費されているか? これがログ分析の核心だ。よくある容疑者は次のとおりだ。

浪費パターンログでの見え方典型的な原因
パラメータ URL?sort=?utm_?sessionid= への多数のヒットトラッキングパラメータ、ソート、セッション ID
重複コンテンツ同じコンテンツが /p/123/products/widget の下にある1 ページへの複数ルート
リダイレクトチェーン301 -> 301 -> 200 のシーケンス移行の上に移行が重ねられている
404 / 410 の嵐ボットへの大量の 404デッドリンク、リンクが残った削除済み商品
ソフト 404実際は「見つからない」ページの 200空の検索結果、売り切れ商品
ファセットナビ組み合わせ的な ?color=&size=&brand= URLクロール制御のないフィルター

シェルだけで生ログに対して実行できる手早いトリアージ。

# Googlebot を名乗る何かにクロールされた上位 20 URL
grep -i googlebot access.log \
  | awk '{print $7}' \
  | sort | uniq -c | sort -rn | head -20

# Googlebot のステータスコード分布 — 4xx/5xx の比率が浪費のシグナルだ
grep -i googlebot access.log \
  | awk '{print $9}' \
  | sort | uniq -c | sort -rn

Googlebot のリクエストの 30% が 404 を返したり ? だらけの URL を叩いたりしているなら、クロールバジェットのおよそ 3 分の 1 がゴミ箱行きだ。

クロールバジェットの最適化

漏れがどこにあるかをログが教えてくれたら、それを塞ぐ。ツールキットは小さいが、各ツールには特定の役割がある — 間違ったツールを使うことが、サイトを誤ってインデックスから外してしまう原因だ。

robots.txt で源泉でブロックする。 パスを Disallow すると Google がそれをクロールすること自体を止める。これは内部検索、ソート順、トラッキングパラメータといったクロールバジェットの浪費に対してまさに望むことだ。注意点として、Disallow は他所からリンクされている URL をインデックスから削除しない — フェッチを止めるだけだ。

User-agent: *
Disallow: /search
Disallow: /*?sort=
Disallow: /*?sessionid=
Disallow: /cart

rel=canonical で重複を統合する。 同じコンテンツが複数の URL に存在する場合(パラメータ付きバリアント、トラッキングタグ付きリンク)、それらすべてを 1 つの canonical URL に向ける。Google はシグナルをまとめ、canonical のクロールを優先する。

<link rel="canonical" href="https://example.com/products/widget-42" />

noindex で低価値ページをインデックスから外す。 ユーザーのためにクロール可能のままにしておく必要があるが検索では競わせたくないページ — 薄いタグアーカイブ、ページネーションの末尾 — には、noindex メタロボットタグを使う。重要: noindexrobots.txtDisallow と組み合わせてはいけない。Google がページをクロールできないと、noindex を見ることもできないからだ。

最も効果の高い構造的修正は 2 つ。

クロールトラップを潰す。 ファセットナビゲーションと無限カレンダーは、古典的なバジェット焼却炉だ。それぞれ 10 値を持つ 6 つのファセットを備えたフィルター UI は 100 万通りの URL の組み合わせを生成し、そのどれもインデックスする価値はない。ボットが辿らないフィルターリンク(POST フォーム、または rel=nofollow とパラメータへの Disallow)を配信し、無限カレンダーには上限を設けて /events/2099/12 が妥当な地平線を超えたら 404 を返すようにして修正しよう。

リダイレクトチェーンとソフト 404 を修復する。 301 -> 301 -> 200 チェーンの各ホップは無駄なフェッチであり、リンクエクイティのわずかな損失だ。チェーンを畳んで、各旧 URL が最終的な宛先へ 1 ホップで直接向くようにしよう。ソフト 404 — 売り切れ商品や結果なし検索のように、空に見えるのに 200 を返すページ — については、本物の 404/410 を返して、Google がそれらを生きたコンテンツのように再クロールするのをやめさせよう。

💡 ヒント: 修正をリリースしたあと、ログは検証ツールにもなる。1 週間分の Googlebot ヒットを再取得し、パラメータ URL と 404 が減ったこと、そして収益ページのクロールが増えたことを確認しよう。ゴミから解放されたクロールバジェットは良いページへ再配分される — その様子を観察できる。

ツール

grepawksort だけでもかなりのことができるが、規模が大きくなるとクエリ可能なものが欲しくなる。

BigQuery は大規模サイトの主力だ。ログをテーブルにロードすれば、クロール分析は数十億行に対するただの SQL になる。

SELECT
  REGEXP_EXTRACT(url, r'^[^?]+') AS path,   -- strip query string
  COUNT(*)                       AS hits,
  COUNTIF(status >= 400)         AS errors
FROM `logs.googlebot`
WHERE _PARTITIONTIME >= TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL 30 DAY)
GROUP BY path
ORDER BY hits DESC
LIMIT 50;

Screaming Frog Log File Analyser はノーコードの選択肢だ。生ログファイルをドラッグして読み込ませ、クロール結果を指定すると、その 2 つを相互参照してくれる。これにより、クロールされたがクロール結果に存在しない(「orphan」)URL や、重要なのにめったにクロールされないページが見える。単発の監査に最適だ。

自作のパーサー は、自分で所有したい反復的な分析には価値がある。combined-log の 1 行は、1 つのパターンでパースできるほど規則的だ。

import re
from collections import Counter

# Matches: ip ... "METHOD url HTTP/x" status bytes "ref" "ua"
LINE = re.compile(
    r'(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] '
    r'"(?P<method>\S+) (?P<url>\S+) [^"]+" '
    r'(?P<status>\d{3}) (?P<bytes>\S+) "[^"]*" "(?P<ua>[^"]*)"'
)

paths, statuses = Counter(), Counter()

with open("access.log", encoding="utf-8", errors="replace") as fh:
    for line in fh:
        m = LINE.match(line)
        if not m or "googlebot" not in m["ua"].lower():
            continue
        path = m["url"].split("?", 1)[0]   # group by path, ignore params
        paths[path] += 1
        statuses[m["status"]] += 1

print("Top crawled paths:")
for path, n in paths.most_common(20):
    print(f"{n:6d}  {path}")

print("\nStatus distribution:", dict(statuses))

これを 1 か月分のログに対して実行すれば、高価なツールが与えるのと同じ答え — 上位クロールパスと、浪費を定量化するエラー比率 — が数秒で手に入る。

🧑‍💻 Cloudflare の観点

サイトが Cloudflare 上にある場合、origin ログはほとんどのクロールを捉えられない — Googlebot は主にエッジでキャッシュ済みレスポンスを叩くからだ。解決策は Logpush だ。これは HTTP リクエストログを Cloudflare のエッジから選んだシンクへストリームする、マネージドなパイプラインだ。最も安価で最も SEO に適したシンクは R2 — Cloudflare の S3 互換オブジェクトストレージで、egress 料金がかからない。

セットアップの大枠。

  1. Cloudflare ダッシュボードで Analytics & Logs -> Logpush へ進み、HTTP requests データセットのジョブを作成する。
  2. 宛先として R2 を選び、必要なフィールドを選択する: ClientIPClientRequestURIEdgeResponseStatusClientRequestUserAgentEdgeStartTimestampCacheCacheStatus
  3. Logpush は数分ごとに gzip 圧縮された JSON バッチを R2 バケットに投下する。

そのあと、ログを直接クエリする — 例えば R2 SQL / エクスポートされたファイルに対する DuckDB セッションで。

-- Top URIs fetched by Googlebot, last 7 days, with error ratio
SELECT
  ClientRequestURI                                     AS uri,
  COUNT(*)                                             AS hits,
  SUM(CASE WHEN EdgeResponseStatus >= 400 THEN 1 ELSE 0 END) AS errors
FROM read_json_auto('r2://my-logs/http/2026/06/*.json.gz')
WHERE lower(ClientRequestUserAgent) LIKE '%googlebot%'
GROUP BY uri
ORDER BY hits DESC
LIMIT 50;

Logpush は CacheCacheStatus を含むので、origin では決して得られないクロールバジェットの超能力も手に入る。どのクロール済み URL がキャッシュから配信され(hit)、どれが origin フェッチを強制したか(miss/expired)を見られるのだ。重要ページで Googlebot の miss 率が高いということは、エッジキャッシュがクローラーの役に立っていないということ — これは Build レイヤー に直結するチューニングの好機だ。キャッシュとエッジ構成はそこに属する。

💡 ヒント: Bot Management アドオンを持っているなら、Logpush ジョブで検証済みボットのメタデータも取得するよう設定しよう — Cloudflare があなたの代わりに Googlebot を検証してくれるので、逆引き DNS のダンスを省略して、信頼できるフラグでフィルターできる。

重要ポイント

  • ✅ クロールバジェットがそもそも該当するかを判断する: クリーンな URL が約 1 万未満ならスキップ、10 万超またはクロールトラップがあるならログを主要なレバーとして扱う。
  • 本物のエッジログを入手する — CDN の背後にいるなら、キャッシュされたクロールを取りこぼす origin ログを信用せず、Cloudflare Logpush を R2 へ送り出す。
  • ✅ ログ内の「Googlebot」行を信用する前に、逆引き + 正引き DNS(または IP レンジ)で Googlebot を検証する。
  • ✅ 浪費を定量化する: パラメータ URL、404、リダイレクトチェーン、ソフト 404 に向かう Googlebot ヒットの割合を測定する。
  • ✅ 適切なツールで漏れを塞ぐ — クロールを止めるには robots.txt、統合には canonical、インデックスから外すには noindex、ファセットナビとカレンダートラップには構造的修正。
  • ✅ ループを閉じる: 修正をリリースしたあとログを再取得し、浪費が減り収益ページのクロールが増えたことを確認する。