2026年8月14日 金曜日
AI時短ラボ
活用· 約31

URL Inspection APIのレスポンスを機械判定すると壊れる3点──coverageStateは表示文字列、ROBOTS_TXT_STATE_UNSPECIFIEDはrobots.txtの問題ではない

Search ConsoleのURL Inspection APIに全351記事をかけて、レスポンスの読み違いを3つ踏んだ。①coverageStateはAPIリファレンス上string型で、languageCodeを"ja"にすると「Submitted and indexed」が「送信して登録されました」に、"de"なら「Gesendet und indexiert」に変わる(同一URLで実測)。②ROBOTS_TXT_STATE_UNSPECIFIEDはrobots.txtのブロックではなく未判定で、当サイトでは249件が「lastCrawlTime無し」249件と完全に一致した。③未登録のverdictはFAILではなくNEUTRALで、351件中FAILは0件だった。

執筆・編集:
目次

Search Console の URL Inspection API(urlInspection.index.inspect)のレスポンスをスクリプトで条件分岐に使うなら、coverageState を判定キーにしてはいけない。この項目は API リファレンス上、状態を表す他の項目と違って enum ではなく string としか書かれておらず、リクエストの languageCode によって中身の文字列が丸ごと入れ替わる(既定値は en-US)。機械判定に使えるのは verdict / robotsTxtState / indexingState / pageFetchState の enum 型4項目のほうだ。あわせて、robotsTxtState: ROBOTS_TXT_STATE_UNSPECIFIED は「robots.txt にブロックされている」ではなく「robots.txt の判定結果を持っていない」の意味で、ブロックを表す値は DISALLOWED である。そして未登録ページの verdictFAIL ではなく NEUTRAL(Search Console の「除外」に相当)になる。

  • coverageState は enum ではなく string。同一URLに対し languageCode だけを変えると Submitted and indexed送信して登録されましたGesendet und indexiert と変わり、verdictrobotsTxtState は変わらなかった(2026年8月14日実測)
  • ROBOTS_TXT_STATE_UNSPECIFIED は robots.txt のブロックではない。当サイト351記事では UNSPECIFIED 249件と「lastCrawlTime を持たない」249件が完全に1:1で一致した
  • 未登録は FAIL ではなく NEUTRAL。351件の内訳は PASS 100 / NEUTRAL 251 / FAIL 0

症状:同じURLを3回叩いて、3つ違う文字列が返る

2026年8月14日 06:05〜06:20 UTC、当サイトのプロパティ(https://www.ai-jitan-hub.com/・URLプレフィックス型)に対して、同じURL・同じスクリプトで連続して languageCode だけを変えて3回叩いた(呼び出し間隔は記録していない)。返ってきた indexStatusResult の抜粋をそのまま貼る。

[
 {"languageCode": "(omitted)", "verdict": "PASS", "coverageState": "Submitted and indexed",
  "robotsTxtState": "ALLOWED", "lastCrawlTime": "2026-08-09T00:41:00Z"},
 {"languageCode": "ja",        "verdict": "PASS", "coverageState": "送信して登録されました",
  "robotsTxtState": "ALLOWED", "lastCrawlTime": "2026-08-09T00:41:00Z"},
 {"languageCode": "de",        "verdict": "PASS", "coverageState": "Gesendet und indexiert",
  "robotsTxtState": "ALLOWED", "lastCrawlTime": "2026-08-09T00:41:00Z"}
]

未登録のURLでも同じことが起きる。

[
 {"languageCode": "(omitted)", "verdict": "NEUTRAL",
  "coverageState": "Discovered - currently not indexed",
  "robotsTxtState": "ROBOTS_TXT_STATE_UNSPECIFIED", "lastCrawlTime": null},
 {"languageCode": "ja",        "verdict": "NEUTRAL",
  "coverageState": "検出 - インデックス未登録",
  "robotsTxtState": "ROBOTS_TXT_STATE_UNSPECIFIED", "lastCrawlTime": null},
 {"languageCode": "de",        "verdict": "NEUTRAL",
  "coverageState": "Gefunden – zurzeit nicht indexiert",
  "robotsTxtState": "ROBOTS_TXT_STATE_UNSPECIFIED", "lastCrawlTime": null}
]

ただし未登録側のこの3行は「その時の1回」として読んでほしい。後述のとおり未登録URLの coverageState は呼び出しごとに揺れており、公開前の検品で同じURLを引き直したときは ja の回に「URL が Google に認識されていません」(URL is unknown to Google の日本語ラベル)が返った。言語による入れ替わりだけを綺麗に見られるのは、状態そのものが動いていない登録済みURLのほうだ(引き直しでも verdict / robotsTxtState / lastCrawlTime は3言語とも同一だった)。

重複判定のURLでは日本語が長文で返る。

{"languageCode": "ja", "verdict": "NEUTRAL",
 "coverageState": "重複しています。Google により、ユーザーがマークしたページとは異なるページが正規ページとして選択されました",
 "robotsTxtState": "ALLOWED", "lastCrawlTime": "2026-07-20T04:56:26Z"}

verdictrobotsTxtStatelastCrawlTime は3言語とも同一で、変わったのは coverageState だけだった。

誤読1:coverageState は enum ではなく「表示用の文字列」

API リファレンス(UrlInspectionResult)の IndexStatusInspectionResult の項を見ると、型の書き分けがそのまま答えになっている。原文は次のとおり。

verdict — enum (Verdict) — High-level verdict about whether the URL is indexed. coverageState — string — Could Google find and index the page. More details about page indexing appear in 'indexingState'. robotsTxtState — enum (RobotsTxtState) — Whether or not the page is blocked to Google by a robots.txt rule.

(訳:verdict は enum 型で、URL がインデックスされているかについての高レベルの判定。coverageState は string 型で、Google がページを見つけてインデックスできたか。ページのインデックス登録の詳細は indexingState に出る。robotsTxtState は enum 型で、robots.txt のルールによって Google にブロックされているかどうか)

同じレスポンスの中で verdict / robotsTxtState / indexingState / pageFetchState / crawledAsenum (...) と型が明示されているのに、coverageState には string としか書かれていない。ここが分岐点だ(googleCanonicallastCrawlTimestring だが、これらは URL とタイムスタンプであって状態の判定値ではない)。

そして文字列が言語で変わる根拠は、inspect メソッドのリクエストボディ仕様にある。

languageCode — string — Optional. An IETF BCP-47 language code representing the requested language for translated issue messages, e.g. "en-US", "or "de-CH". Default value is "en-US".

(訳:省略可。翻訳された問題メッセージについて、要求する言語を表す IETF BCP-47 の言語コード。例:en-USde-CH。既定値は en-US。※引用中の "or は原文ママ)

つまり languageCode を渡さないスクリプトは黙って en-US で動く。日本語のダッシュボードを見ている人が API を叩くと英語が返ってきて驚く、という順序で最初のズレが起きる。

なお、公式ドキュメントが languageCode の対象として書いているのは "translated issue messages" であって、coverageState を名指ししてはいない。coverageState が実際に翻訳対象に含まれることは、上の実測でしか確認していない。

Google 自身のヘルプページの日英版を突き合わせると、返ってきた文字列がヘルプに載っている表示ラベルと同じものであることも確認できる。ページのインデックス登録レポートのヘルプでは、英語版の Discovered - currently not indexed が日本語版で「検出 - インデックス未登録」、Duplicate, Google chose different canonical than user が「重複しています。Google により、ユーザーがマークしたページとは異なるページが正規ページとして選択されました」になっている。URL 検査ツールのヘルプでは、英語版の URL is unknown to Google が日本語版で「URL が Google に認識されていません」になっている。API が返しているのは、Search Console のヘルプに載っているレポートの表示ラベルと同じ文字列だった(画面そのものと突き合わせたわけではなく、ヘルプの日英版との一致で確認している)。

誤読2:ROBOTS_TXT_STATE_UNSPECIFIED は robots.txt の問題ではない

ROBOTS_TXT_STATE_UNSPECIFIED は、名前に ROBOTS_TXT が入っているせいで「robots.txt が何かしている」と読みたくなる。公式の enum 定義はそう書いていない。

ROBOTS_TXT_STATE_UNSPECIFIED — Unknown robots.txt state, typically because the page wasn't fetched or found, or because robots.txt itself couldn't be reached. ALLOWED — Crawl allowed by robots.txt. DISALLOWED — Crawl blocked by robots.txt.

(訳:ROBOTS_TXT_STATE_UNSPECIFIED は robots.txt の状態が不明であることを表す。典型的には、ページが取得されなかったか見つからなかったため、あるいは robots.txt 自体に到達できなかったため。ALLOWED は robots.txt によりクロールが許可されている。DISALLOWED は robots.txt によりクロールがブロックされている)

ブロックを表す値は DISALLOWED であって、UNSPECIFIED は「判定していない/できていない」を表す。当サイトの351記事の実測では、この機序がそのまま数字に出た。

robotsTxtState lastCrawlTime あり lastCrawlTime なし
ALLOWED 102 0
ROBOTS_TXT_STATE_UNSPECIFIED 0 249

ALLOWED の102件は全部クロール時刻を持ち、UNSPECIFIED の249件は全部クロール時刻を持たない。例外は0件だった。lastCrawlTime の仕様は「Last time this URL was crawled by Google using the primary crawler. Absent if the URL was never crawled successfully.」(訳:主要クローラーで最後にクロールされた時刻。一度もクロールに成功していない場合は項目自体が無い)なので、この1:1一致は「取得していないから robots.txt の判定結果も持っていない」という読みと整合する。

ページのインデックス登録レポートのヘルプにある Discovered - currently not indexed の説明も同じ方向を向いている。

The page was found by Google, but not crawled yet. Typically, Google wanted to crawl the URL but this was expected to overload the site; therefore Google rescheduled the crawl. This is why the last crawl date is empty on the report.

日本語版ヘルプの同じ箇所は「ページは Google により検出されましたが、まだクロールされていません。これは通常、Google が URL をクロールしようとしたものの、サイトへの過負荷が予想されたため、クロールの再スケジュールが必要となった場合です。そのため、レポート上で最終クロール日が空欄になっています。」となっている。

robotsTxtState の内訳(ALLOWED 102 / ROBOTS_TXT_STATE_UNSPECIFIED 249 / DISALLOWED 0)を「robots.txt で249本ブロックされている」と読んで robots.txt を書き直しにいくと、robots.txt には何の問題も無いので当然何も直らない。

誤読3:未登録ページの verdict は FAIL ではなく NEUTRAL

Verdict の enum は5つある。原文は次のとおり。

VERDICT_UNSPECIFIED — Unknown verdict. PASS — Equivalent to "Valid" for the page or item in Search Console. PARTIAL — Reserved, no longer in use. FAIL — Equivalent to "Error" or "Invalid" for the page or item in Search Console. NEUTRAL — Equivalent to "Excluded" for the page or item in Search Console.

(訳:PASS は Search Console 上の「有効」に相当。PARTIAL は予約済みで現在は使われていない。FAIL は「エラー」または「無効」に相当。NEUTRAL は「除外」に相当)

ここで効くのは NEUTRAL = 「除外」という対応だ。インデックスされていないページは Search Console 上で「除外」に入るので、verdictNEUTRAL になる。FAIL は「エラー」側の値で、未登録を拾う条件ではない。当サイト351件の実測は次のとおりだった。

verdict 件数
PASS 100
NEUTRAL 251
FAIL 0
PARTIAL 0
VERDICT_UNSPECIFIED 0

verdictcoverageState の対応も全件で割れなかった。

verdict coverageStatelanguageCode 省略時) 件数
PASS Submitted and indexed 100
NEUTRAL Discovered - currently not indexed 201
NEUTRAL URL is unknown to Google 48
NEUTRAL Duplicate, Google chose different canonical than user 2

「未登録の記事を洗い出す」ために verdict == "FAIL" で絞ると、当サイトでは0件が返る。探すべきは NEUTRAL のほうだった。

もうひとつ踏んだ:coverageState は同じリクエストでもブレる

languageCode を固定すれば安心か、というと、そうでもなかった。未登録の1本に対し、languageCode を渡さず同じリクエストを連続10回投げた結果が次だ。

項目 10回の内訳
coverageState Discovered - currently not indexed 7回 / URL is unknown to Google 3回
verdict NEUTRAL 10回
robotsTxtState ROBOTS_TXT_STATE_UNSPECIFIED 10回

同じ条件の10回で coverageState だけが2つの値の間で揺れた。比較のためインデックス済みの1本でも同じ10回を回したが、そちらは Submitted and indexed 10回・PASS 10回・ALLOWED 10回で完全に一定だった。未登録側の coverageState は、少なくとも当サイトの当該URLでは1回の呼び出しで確定しない。

対処

  1. 機械判定は enum 4項目で行う。 verdict / robotsTxtState / indexingState / pageFetchState は enum なので言語で変わらない。coverageState は人間が読むログにだけ残す
  2. coverageState を使うなら languageCode を必ず明示する。 既定の en-US に暗黙に依存しない。そのうえで日本語リテラルを辞書のキーにしない
  3. 「クロールされたか」は robotsTxtState ではなく lastCrawlTime の有無で判定する。 仕様上、一度もクロール成功していないと項目自体が消える
  4. robots.txt のブロックを検出したいなら DISALLOWED を探す。 UNSPECIFIED は「未判定」
  5. 未登録を探すなら NEUTRAL FAIL は「エラー」側
  6. 1回の呼び出しで結論を出さない。 少なくとも未登録側の coverageState は連続呼び出しでも揺れた

コードにするとこうなる。

s = r["inspectionResult"]["indexStatusResult"]

# NG: 表示文字列で分岐する(languageCode で全部外れる)
if s.get("coverageState") == "送信して登録されました":
    ...

# OK: enum で分岐する
indexed = s.get("verdict") == "PASS"
blocked_by_robots = s.get("robotsTxtState") == "DISALLOWED"
crawled = bool(s.get("lastCrawlTime"))   # 項目の有無がそのまま答え

踏んだ記録(2026年8月14日・当サイト1件)

環境は macOS 15.1.1 / Python 3.14.5 / google-api-python-client 2.198.0 / google-auth 2.56.0、対象は https://www.ai-jitan-hub.com/(URLプレフィックス型プロパティ)。認証はサービスアカウントで、要求スコープはスクリプトごとに違う(全数検査の scripts/gsc_full_inspect.pyhttps://www.googleapis.com/auth/webmasters、日次スナップショットの scripts/gsc_daily_snapshot.py と本記事の対照実験は webmasters.readonly)。

この誤読が表に出たのは、当サイトのリポジトリに同じ API を叩く2本のスクリプトが別々に存在していたからだった。片方は languageCode を渡していない。

# scripts/gsc_full_inspect.py — languageCode を渡していない(=既定の en-US)
r = svc.urlInspection().index().inspect(
    body={"inspectionUrl": url, "siteUrl": SITE}
).execute()
s = r["inspectionResult"]["indexStatusResult"]
rec.update(
    verdict=s.get("verdict"),
    coverage=s.get("coverageState"),
    lastCrawl=s.get("lastCrawlTime"),
    robots=s.get("robotsTxtState"),
    ...
)

もう片方は "ja" を渡し、返ってきた日本語をそのまま機械判定のキーにしていた

# scripts/gsc_daily_snapshot.py:69 — languageCode を "ja" で固定
r = sc.urlInspection().index().inspect(body={
    "inspectionUrl": f"{SITE}news/{s}", "siteUrl": SITE, "languageCode": "ja"}).execute()
st = r["inspectionResult"]["indexStatusResult"].get("coverageState", "不明")

# 同ファイル 75行目 — 日本語リテラルが判定キーになっている
indexed = states.get("送信して登録されました", 0)

結果、同じ日の同じサイトについて、data/gsc/full-inspect-2026-08-14.json には英語(Submitted and indexed 100 / Discovered - currently not indexed 201 / URL is unknown to Google 48 / Duplicate, Google chose different canonical than user 2)が、data/gsc/2026-08-14.json には日本語(「検出 - インデックス未登録」29 /「送信して登録されました」10 /「URL が Google に認識されていません」1)が入った。同じ API の同じ項目である。

この構造の怖いところは壊れ方が静かな点にある。gsc_daily_snapshot.py から languageCode の1行を消すと、states のキーは英語になり、75行目の states.get("送信して登録されました", 0) は例外を出さずに 0 を返す。CSV に記録される indexed_rate_pct0.0 になり、ログには「インデックス率: 0.0%(40本抽出)」と表示される。API は正常、スクリプトも正常終了、数字だけが嘘になる。

ROBOTS_TXT_STATE_UNSPECIFIED のほうは、351件の全数検査の集計を最初に見たときに「robots.txt が249本ブロックしているのか」と読んだ。robots.txt を確認しても該当する記述は無く、クロス集計を取って lastCrawlTime の有無と完全に1:1で対応していることが分かってから公式の enum 定義に戻り、DISALLOWED という別の値がブロック用に用意されていることを確認した。順序が逆だった。

verdict については、未登録記事を絞り込むつもりで FAIL を数えて0件になり、そこで初めて enum 定義の NEUTRAL = "Excluded" という対応を読んだ。

正直な但し書き

  • FAIL が「絶対に出ない」とは書けない。 公式の enum 定義上 FAIL は Search Console の「エラー」に相当する値として存在する。当サイトの351件で0件だった、というのが確認できた事実の範囲であり、5xx や 404 を返す URL で FAIL が出るかどうかは当サイトでは観測していない
  • languageCodecoverageState を翻訳する、とは公式には書かれていない。 ドキュメントの文言は "translated issue messages" で、coverageState を名指ししていない。翻訳されることは実測(4URL × 3言語=12回)でしか確認していない。将来仕様が変わる可能性はある
  • verdictrobotsTxtState が言語で変わらないことも、実測の範囲は狭い。 4URL × 3言語(12回)+同一条件の連続20回で変化を観測しなかっただけで、全言語で検証したわけではない。ただし型が enum である以上、翻訳対象になる設計とは考えにくい
  • coverageState のブレは1URLでの10回試行にすぎない。 「未登録側は揺れる/登録済み側は揺れない」を一般化できるだけの標本数は取っていない
  • 公開前の検品で同じ2URLを引き直した。 登録済みURLは3言語とも同じ結果を再現したが、未登録URLは ja の回で別の状態文字列(URL is unknown to Google の日本語ラベル)が返った。この再実行の生データは data/gsc/langcode-flicker-2026-08-14.jsontest3_independent_recheck に置いてある
  • 数字はすべて2026年8月14日時点の当サイト1件(351記事)のもの。 インデックス状態は日々変わるので、同じ集計を別の日に取れば内訳は変わる
  • この API は「ライブURLのテスト」ではない。公式にも「Presently only the status of the version in the Google index is available; you cannot test the indexability of a live URL.」(訳:現時点では Google のインデックスにあるバージョンの状態のみが取得可能で、ライブURLのインデックス可能性はテストできない)と書かれている

出典

  • Method: index.inspect | Search Console API(最終更新2024年7月23日UTC、2026年8月14日確認) — languageCode の仕様と既定値 en-US、ライブURLをテストできない旨
  • UrlInspectionResult | Search Console API(最終更新2025年1月21日UTC、2026年8月14日確認) — coverageState が string 型である旨、Verdict / RobotsTxtState の enum 定義、lastCrawlTime の「一度もクロール成功していなければ項目が無い」旨
  • Page indexing report - Search Console Help(2026年8月14日確認・日英両版) — Discovered - currently not indexed の説明と「最終クロール日が空欄になる」旨、日本語版の対応ラベル
  • URL Inspection Tool - Search Console Help(2026年8月14日確認・日英両版) — URL is unknown to Google の日本語版対応ラベル
シェア: ポスト はてブ

出典・参照資料

AIニュースの解説を動画でも

YouTubeでは注目ニュースの背景を解説し、Xでは新着記事をお知らせしています。

コメント

まだコメントはありません。最初のコメントを書いてみませんか?

AIについて聞きたいことはありますか?

質問箱で無料で受け付けています。回答は公開され、他の方の参考にもなります。

質問箱を見る →

新しい記事をメールで受け取る

AIの新しい発表を、出典付きで整理して届けます。

関連記事

AI時短ラボ
活用08.14読了35

www化の308を入れてもSearch Consoleの旧プロパティは集計を続ける──URLプレフィックス型で5週間データを取り逃した記録

出典 ─ Add a website or platf
AI時短ラボ
活用08.14読了26

出典URLの死活チェックで404だけ見てはいけない──1,517件を全数検査したら、HEADに404を返す公式ドキュメントと403の一次資料が出てきた

出典 ─ RFC 9110: HTTP Semanti
AI時短ラボ
活用08.14読了36

Vercelの自動生成ドメインがGSCの参照元に117本入っていた──*.vercel.appと「Duplicate, Google chose different canonical than user」を全351記事で実測

出典 ─ Accessing Deployments
ドイツ裁判所、GoogleのAI Overviewsに直接責任を認定──AI生成コンテンツは「Google自身の発言」の記事画像
業界06.20読了7

ドイツ裁判所、GoogleのAI Overviewsに直接責任を認定──AI生成コンテンツは「Google自身の発言」

出典 ─ Landmark German ruling
AI検索エンジン比較──Perplexity・SearchGPT・Gemini・Arc Search【2026年】の記事画像
プロダクト06.19読了9

AI検索エンジン比較──Perplexity・SearchGPT・Gemini・Arc Search【2026年】

出典 ─ AIMLAPI
YouTube「Ask YouTube」発表、動画を会話型AI検索で探す新機能とShortsのGemini Omniリミックスの記事画像
プロダクト05.19読了11

YouTube「Ask YouTube」発表、動画を会話型AI検索で探す新機能とShortsのGemini Omniリミックス

出典 ─ All the YouTube news f
AI動画の「〜しないで」はなぜ効かないのか──Google公式が非推奨としたのは命令文だったの記事画像
活用08.14読了16

AI動画の「〜しないで」はなぜ効かないのか──Google公式が非推奨としたのは命令文だった

出典 ─ Video generation promp
動画生成AIのプロンプトはどこまで書くか──公式が「指定しない部分」を勧める理由の記事画像
活用08.14読了19

動画生成AIのプロンプトはどこまで書くか──公式が「指定しない部分」を勧める理由

出典 ─ Sora 2 Prompting Guide