ログファイル分析とクロールバジェット
Googlebot がサイト上で実際に何をしているかを把握し、ゴミ URL でクロールバジェットを浪費するのをやめよう。
ほとんどの SEO ツールは、サイトに何が起こりうるかを教えてくれる。サーバーログは、実際に何が起こったかを教えてくれる。Googlebot が URL をフェッチするたびに、サーバーはログファイルに 1 行を書き込む — 実際の URL、実際のステータスコード、実際のタイムスタンプ。サンプリングも、推測も、Google のふりをするサードパーティのクローラーもない。検索エンジンがサイト上で実際に何をしているかを知りたいなら、ログだけが唯一の真実の源泉だ。
このガイドは、その真実を読み取り、それに基づいて行動することについてのものだ。レンズとなるのは crawl budget(クロールバジェット)、すなわち Google があなたのサイトに費やそうとするクロール量の有限な総量だ。小規模サイトでは、このバジェットは実質的に無限であり、次のセクションを読んだら読むのをやめてよい。大規模サイト — EC カタログ、マーケットプレイス、数十万 URL を持つプログラマティックサイト — では、crawl budget は厳しい制約であり、ログはどこでそれが漏れているかを見つける手段となる。
🧑💻 開発者視点: ログファイルは
(timestamp, ip, method, url, status, user_agent)でキー付けされた追記専用のイベントストリームにすぎない。このガイドのすべては、そのストリームに対するGROUP BYかWHERE句だ。アクセスログからサービスをデバッグしたことがあるなら、ログベースの 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 で気にすべきフィールドは次のとおりだ。
| フィールド | 例 | なぜ重要か |
|---|---|---|
| クライアント IP | 66.249.66.1 | 本物の Googlebot の検証(逆引き DNS) |
| タイムスタンプ | 22/Jun/2026:08:14:32 | クロール頻度、鮮度 |
| メソッド + URL | GET /products/widget-42?color=red | 何がクロールされたか、パラメータの浪費 |
| ステータスコード | 200 | 404、リダイレクト、5xx エラー |
| バイト数 | 5123 | ペイロードサイズ、レスポンスの重さ |
| ユーザーエージェント | ...Googlebot/2.1... | どのクローラーが URL を叩いたか |
ログを入手すること自体が本当のハードルだ。 ログがどこにあるかはスタックによる。
- セルフホストの Nginx/Apache —
/var/log/nginx/access.log、logrotateで日次ローテーション。最も簡単なケース。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 メタロボットタグを使う。重要: noindex を robots.txt の Disallow と組み合わせてはいけない。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 が減ったこと、そして収益ページのクロールが増えたことを確認しよう。ゴミから解放されたクロールバジェットは良いページへ再配分される — その様子を観察できる。
ツール
grep、awk、sort だけでもかなりのことができるが、規模が大きくなるとクエリ可能なものが欲しくなる。
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 料金がかからない。
セットアップの大枠。
- Cloudflare ダッシュボードで Analytics & Logs -> Logpush へ進み、HTTP requests データセットのジョブを作成する。
- 宛先として R2 を選び、必要なフィールドを選択する:
ClientIP、ClientRequestURI、EdgeResponseStatus、ClientRequestUserAgent、EdgeStartTimestamp、CacheCacheStatus。 - 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、ファセットナビとカレンダートラップには構造的修正。 - ✅ ループを閉じる: 修正をリリースしたあとログを再取得し、浪費が減り収益ページのクロールが増えたことを確認する。