この記事の要点(TL;DR)
- スクリプトからSearch Console(サーチコンソール)のデータを取りたくて、サービスアカウントで所有権を確認しました。
- データは無事に取れるようになったのですが、自分のGoogleアカウントでSearch Consoleを開くと、そのサイトが一覧に出てこないという状態になりました。
- 原因は単純で、所有者がサービスアカウントだけになっていたからです。所有権は「サイト」ではなく「アカウント」に紐づきます。
- 直し方は2つ。① 確認用のタグを複数持たせる(Next.jsなら配列で書けます)② Site Verification APIで所有者を追加する。
- 最初から自分のぶんも入れておけば、この手戻りは起きません。
何をしようとしていたか
個人開発で 補助金ナビ(hojokin-db.jp) という補助金の検索サイトと、ShibaHub(shibahub.jp) という無料の計算ツールサイトを運営しています。
毎日Search Consoleを開いて数字を見るのが面倒になってきたので、APIで自動取得してMarkdownのレポートにするようにしました。
サービスアカウントとは?:人間ではなく「プログラム用のGoogleアカウント」です。メールアドレスの形(
xxx@yyy.iam.gserviceaccount.com)をしていて、鍵ファイル(JSON)を使ってログインします。スクリプトから自動でAPIを叩くときに使います。
新しく公開したサイトについては、このサービスアカウントで所有権の確認まで自動でやってしまおうと考えました。Search Consoleには Site Verification API があって、所有権の確認をプログラムから実行できます。
流れはこうです。
- APIで確認用のトークンを発行してもらう
- そのトークンを
<meta name="google-site-verification" content="..." />としてサイトに埋める - デプロイする
- APIで「確認して」とリクエストする
これは想定どおり動きました。データも取れるようになりました。
起きたこと:自分のアカウントに出てこない
数日後、ブラウザでSearch Consoleを開いて気づきました。
追加したはずのサイトが、プロパティの一覧に無いのです。
URLを直接打って開くと、こう言われました。
このプロパティへのアクセス権がありません
自分のサイトなのに、です。
原因:所有権は「アカウント」に紐づく
Search Consoleの所有権は、サイトに対して1つだけ与えられるものではなく、アカウントごとに与えられるものです。
今回のケースを整理すると、こうなっていました。
| サイト | 所有者 | 自分のアカウントで見えるか |
|---|---|---|
| shibahub.jp | 自分(DNSで確認済み)+サービスアカウント | ✅ 見える |
| hojokin-db.jp | サービスアカウントだけ | ❌ 見えない |
先に自分で登録していた shibahub.jp は問題なし。APIから登録したサイトだけが、自分に見えていませんでした。
当たり前といえば当たり前なのですが、「所有権を取った=自分のものになった」と感覚的に思い込んでいたので、しばらく気づきませんでした。
直し方①:確認用のタグを複数持たせる(おすすめ)
いちばん素直な方法です。アカウントごとに確認用のトークンが発行されるので、両方のタグをサイトに置いてしまえばいいだけです。
自分のブラウザでSearch Consoleを開いて、
- 「プロパティを追加」
- 「URLプレフィックス」を選んでサイトのURLを入力
- 所有権の確認方法で「HTMLタグ」を選ぶ
- 表示された
content="..."の中身をコピー
そして、その値をサイトに追加します。ここで元のタグを消さないことが重要です。消すとサービスアカウント側の所有権が失効して、自動取得が止まります。
Next.js(App Router)なら、app/layout.tsx の metadata に書けます。配列で複数指定できます。
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
verification: {
google: [
"vsOzNQrUbPqII_-axqUfdMG35ZqzTYJoCrZ_iHK53vM", // スクリプト用(サービスアカウント)
"ApcQGR6uVF2n0cH99xiLFXJhVQ9K52PtE62lfLVKtYk", // 自分のGoogleアカウント
],
},
};
デプロイすると、HTMLにはこう出力されます。
<meta name="google-site-verification" content="vsOzNQrUbPqII_-axqUfdMG35ZqzTYJoCrZ_iHK53vM" />
<meta name="google-site-verification" content="ApcQGR6uVF2n0cH99xiLFXJhVQ9K52PtE62lfLVKtYk" />
あとはSearch Consoleの画面で「確認」を押すだけです。
反映されているかどうかは、curl でも確かめられます。
curl -s https://example.com/ | grep google-site-verification
サイトにGoogleアナリティクスが入っているなら、もっと簡単
所有権の確認方法には「Googleアナリティクス」という選択肢もあります。サイトにGA4のタグが入っていて、かつそのGA4プロパティで自分が編集者以上の権限を持っていれば、タグを追加しなくてもその場で確認が完了します。
こちらのほうが手数は少ないので、条件が合うならこの方法が早いです。
直し方②:APIで所有者を追加する
「ブラウザ操作すらしたくない」という場合はこちらです。
ただし注意点があります。Search Console API側に、ユーザーを追加するAPIはありません。 代わりに使うのが Site Verification API の webResource.update です。確認済みリソースの owners にメールアドレスを足すと、そのアカウントが所有者になります。
Pythonだとこんな感じです。
from google.oauth2 import service_account
from googleapiclient.discovery import build
creds = service_account.Credentials.from_service_account_file(
"service-account.json",
scopes=["https://www.googleapis.com/auth/siteverification"],
)
sv = build("siteVerification", "v1", credentials=creds, cache_discovery=False)
# いま登録されているリソースと所有者を確認
items = sv.webResource().list().execute().get("items", [])
target = next(i for i in items if i["site"]["identifier"] == "https://example.com/")
# ★ owners は「全件を渡して上書き」なので、既存 + 追加 のリストを送る
owners = list(target.get("owners", [])) + ["you@gmail.com"]
sv.webResource().update(
id=target["id"],
body={"site": target["site"], "owners": owners},
).execute()
ハマりどころはコメントに書いた1行です。owners は差分ではなく丸ごと上書きなので、追加したいメールだけを渡すと既存の所有者が消えます。必ず既存のリストに足してから送ってください。
⚠️ 所有者権限はサイトの所有権そのもので、サイトマップの送信や他ユーザーの追加までできてしまいます。メールアドレスを間違えると他人に権限を渡すことになるので、実行前に見直してください。
学んだこと
- Search Consoleの所有権はアカウント単位。 「サイトの所有権を取った」ではなく「そのアカウントが所有者になった」と考えるのが正しいです
- 確認用のタグは複数置ける。 人間用と自動化用を並べておけば両方から見られます
- 確認後もタグは消せません。 消すと所有権が失効します
- 新しいサイトを登録するときは、最初から両方のトークンを入れておく。 これだけで今回の手戻りは丸ごと消えます
自動化を進めていくと、「プログラムからは見えるのに自分からは見えない」という状態が地味に起こります。権限まわりは誰の権限で動いているのかを意識しておくと、ハマる時間が短くなると思います。