AI活用

Search Console APIを毎朝Pythonで取る方法|サービスアカウントの権限と、ログイン切れで止まらない形【2026年】

編集責任者佐藤翔太株式会社UrakaTA 代表取締役記事はAIが社内の記録から下書きし、別のAIが公式情報で照合、公開後に編集責任者が確認しています。
小さなロボットのアシスタントが朝日の差す机で、虫眼鏡の付いたグラフのカードを担当者へ手渡し、机の上に鍵が置かれている絵
イラスト:AIで作成

この記事の結論

Search Console APIは、サービスアカウントの鍵とPythonの標準ライブラリだけで毎朝取れます。僕の会社では2026年10月2日から11月8日まで、毎日の取得記録が1日も欠けていません。

目次9項目
  1. Search Console APIを毎朝Pythonで取る仕組みの全体像
  2. searchAnalytics.query で取っている5種類のデータ
  3. URL検査APIは1日2,000件まで、僕の会社は毎日20件と週1回約150件
  4. サーチコンソールAPIの権限は、Search Console 側で人が付ける
  5. 標準ライブラリだけでJWTを作り、ログイン切れで止まらない形にした
  6. Search Console MCPではなくPythonにした理由
  7. 取ったデータは朝の報告と改善候補づくりに使う
  8. まとめ
  9. よくある質問

【無料・メールだけ】Claude Code 2daysセミナー(第二の脳構築講座)の次回開催の案内を受け取る →

Search Console APIを毎朝取るのに僕が必要だったのは、MCPではなく、サービスアカウントの鍵とPythonの標準ライブラリだけでした。検索語・ページ別の表示とクリックと順位を、Claude Codeの定期タスクが毎朝1本のpython3で取っています。

2026年10月2日の夜に初めて取ったとき、/column/ の28日の表示は30回・クリック1回でした。11月8日の朝は表示2,892回・クリック108回で、その間の38日分の記録は1日も欠けていません。

この記事では、何をどの設定で取っているか、URL検査の回数の上限、人が画面で付ける権限、ログイン切れで止まらない認証の形を順に書きます。「APIは分かったけど、毎日放っておいて動く形はどう作るの?」と思っている方向けです。

Search Console APIを毎朝Pythonで取る仕組みの全体像

僕の会社の仕組みは、Search Console APIの4つの呼び出しを、1本のPythonスクリプトにまとめたものです。外部のライブラリは使わず、Mac に最初から入っている python3 で動きます。

4つは、検索パフォーマンスの取得、URL検査、サイトマップの送信、サイトマップの一覧です。呼ぶ頻度も必要な権限も違うので、先に並べておきます。

呼び出し僕の会社での頻度APIのスコープSearch Console 側の権限
searchAnalytics.query(検索パフォーマンス)毎朝1回読み取り専用制限付きで足りる
urlInspection.index.inspect(URL検査)主要20件を毎日・全件を週1回読み取り専用で可制限付きは取得のみ
sitemaps.submit(サイトマップの送信)記事の公開の直後読み書きフル以上
sitemaps.list(登録済みの一覧)必要なときだけ読み取り専用制限付きで足りる

僕なら、最初は上の1行目だけで始めます。読み取り専用の権限で済み、失敗してもサイトに何も起きないからです。

毎朝6時10分の定期タスクから python3 を1本呼ぶ

取得を始めたのは2026年10月2日です。この日の23時58分に1回取り、翌10月3日から毎朝の定期タスクの手順に入れました。定期タスクの朝の取得は6時13分〜6時20分に並び、月曜の9時台など別の時刻の日も7日あります。

定期タスクから呼ぶのは、引数なしの python3 の1コマンドだけです。社内の運用メモには、無人の実行で長いコマンドをつなぐと許可の確認待ちで止まる、という決まりがあります。10月2日の朝に、実際に2本がそれで止まりました。

だから取得の処理は、全部スクリプトの中に閉じ込めました。定期タスクの側は「1行呼んで、出力の1行をログに書く」だけです。

searchAnalytics.query で取っている5種類のデータ

検索パフォーマンスは、searchAnalytics.query を1回の実行で何度か呼び、5種類の表にして日付ごとのファイルに保存しています。どれも /column/ を含むページだけに絞っています。

  1. 期間ごとの合計(直近7日・その前の7日・直近28日・その前の28日)
  2. 検索語の上位10語(28日)
  3. ページの一覧(28日・最大500行)
  4. ページ×検索語の組み合わせ(28日・最大2,000行)
  5. 日別の合計(90日)

期間の終わりはどれも「昨日」です。今日の分は取りに行きません。

dimensions と rowLimit の決め方

dimensions は、合計なら指定なし、検索語なら query、ページなら page です。組み合わせは page と query の2つ、日別は date にしています。

公式では、絞り込みと集計に使えるのは page・query・country・device・searchAppearance です。date と hour は集計だけに使えます。

rowLimit の範囲は1〜25,000で、既定は1,000行です。僕の会社の28日のページ×検索語は、11月8日の朝でも147行でした。上限の2,000行には遠いので、ここは1回の呼び出しで足りています。

日別×ページのように行が多くなる取得だけは、25,000行ずつ startRow をずらして最大4回まで取る作りにしました。4回で取り切れなかったら「途中まで」と記録に残し、続きは推測で埋めません。

dataState は all、ただし直近3日は速報値として扱う

dataState は all にしています。公式では、final(または省略)だと確定したデータだけ、all だと新しいデータも含めて返ります。公開したばかりの記事の表示を早く見たいので、all を選びました。

その代わり、直近3日は速報値として扱います。公式も、未確定の日以降の値は大きく変わることがあると書いています。前後の比較やコホートの集計では、その3日を外しています。

もう1つ、公式は「すべての行ではなく上位の行を返す」と書いています。行の合計がページの合計と合わないことがあるので、合計値は1の期間ごとの取得から読みます。

実際に送っているリクエストの中身

下は、28日のページ×検索語を取るときの中身です。サイトのURLは伏せています。

body = {
    "startDate": "2026-10-11",
    "endDate": "2026-11-07",
    "dataState": "all",
    "dimensions": ["page", "query"],
    "dimensionFilterGroups": [{"filters": [
        {"dimension": "page", "operator": "contains", "expression": "/column/"}
    ]}],
    "rowLimit": 2000,
}
url = "https://www.googleapis.com/webmasters/v3/sites/<サイトのURLをエンコードしたもの>/searchAnalytics/query"

operator の contains は、大文字小文字を区別しない部分一致です。/column/ と /media/sns/ の2つのメディアを、この1語の違いで取り分けています。

URL検査APIは1日2,000件まで、僕の会社は毎日20件と週1回約150件

URL検査APIの割り当ては、公式では1サイトあたり1日2,000件・1分600件です。僕の会社では、主要な記事とハブだけを毎日20件、全部のURLを週1回まとめて検査しています。

11月1日から8日までの毎日の検査は、毎回20件でした。週1回の全件は、10月15日が98件、11月2日が153件です。記事が増えるにつれて増えていますが、1日2,000件にはまだ十分な余裕があります。

画面のURL検査からの「インデックス登録をリクエスト」は、これとは別の上限です。

そちらで12件目に止まった記録は、インデックス登録リクエストが上限で止まる時の対処に書きました。

4本並行・0.2秒あけて、429が出たらその場で止める

URL検査は1件に約5〜7秒かかります。全件を1本ずつ回すと時間がかかるので、4本並行で呼び、呼び出しの開始どうしを0.2秒あけています。1分600件の上限に対しては、十分に遅い速さです。

上限の応答(HTTP 429)を1回でも受けたら、その場で残りを呼ぶのをやめ、取れた分だけを保存します。最初の3件が全部失敗したときも止めます。認証や権限の問題なら、残りも全部失敗するからです。

もう1つの上限は時間です。1回の実行で検査に使うのは480秒までにしました。定期タスクの1コマンドの時間切れより前に、取れた分を必ず書き残すためです。

検査の結果から何を見ているか

保存しているのは、インデックスの判定、最終クロールの日時、Googleが選んだ正規URL、サイトマップに載っているか、などです。

公式の説明では、このAPIで分かるのはGoogleのインデックスにある状態だけで、いまのページを試す使い方はできません。

僕の会社では、最終クロールの日時を「前回の変更をGoogleが読んだか」の判定に使っています。読まれる前に同じ記事を何度も直さないためです。

サーチコンソールAPIの権限は、Search Console 側で人が付ける

サービスアカウントにAPIを呼ばせるには、Search Console のプロパティに、そのアカウントをユーザーとして追加する必要があります。ここは API ではできず、プロパティの所有者が画面で行う作業です。

ここでいうサービスアカウントとは、人ではなくプログラムがGoogleのサービスを使うための専用のアカウントです。メールアドレスの形をしたIDを持ち、そのIDを Search Console に人と同じように追加します。

ユーザーを追加する手順

Search Console ヘルプで確かめた手順は次の5つです。

  1. Search Console で対象のプロパティを開く
  2. 「設定」から「ユーザーと権限」を開く(所有者にしか表示されない)
  3. 「ユーザーを追加」を押す
  4. サービスアカウントのメールアドレスを入れる
  5. 権限を選んで保存する

追加できるのは所有者だけなので、AIの定期処理を作る人と、サイトの所有者が別の会社では、ここで先方に作業をお願いすることになります。

制限付きかフルかは、サイトマップを送るかで決まる

ヘルプの表では、制限付きユーザーはサイトマップを送信できず、URL検査も取得のみです。フルユーザーはサイトマップの送信とURL検査ができます。ユーザーの追加と削除は所有者だけです。

僕の会社の仕組みは、記事の公開の直後にサイトマップを送信しています。11月の公開のたびに HTTP 204 が返っているので、フル以上の権限で動いていることになります。読むだけなら、制限付きで始める方が安全です。

APIのスコープも合わせます。検索パフォーマンスとURL検査は、読み取り専用(webmasters.readonly)で呼べます。

サイトマップの送信だけは、読み書き(webmasters)が必要です。僕の会社では、送信のときだけスコープを切り替えています。

標準ライブラリだけでJWTを作り、ログイン切れで止まらない形にした

認証は、サービスアカウントの鍵からJWTを自分で作り、アクセストークンに引き換える方式です。人のログインを経由しないので、Googleのセッションが切れても毎朝の取得は止まりません。

ここでいうJWTとは、「誰が・どの権限で・いつまで」を書いた小さな文書に、鍵で署名したものです。Googleのトークンの窓口にこれを送ると、アクセストークンが返ってきます。

JWTの中身とトークンの引き換え

公式の手順どおり、ヘッダーは alg が RS256・typ が JWT です。中身には、発行者(サービスアカウント)・スコープ・宛先(トークンの窓口)・発行時刻・期限の5つを入れます。公式では、期限は発行から最大1時間です。

僕の会社では期限を15分にしています。1回の実行は数分で終わるので、長くする理由がないからです。同じ実行の中では、発行したトークンを10分まで使い回します。

10月14日まではトークンを取得の種類ごとに発行していて、1回の実行で署名と通信が4回走っていました。使い回しに変えてから、署名は1回で済んでいます。

署名だけは macOS の openssl に任せた

RS256の署名(SHA-256とRSA)は、Pythonの標準ライブラリだけではできません。そこで署名だけは、macOS に最初から入っている /usr/bin/openssl を、Pythonから subprocess で呼んでいます。

秘密鍵はいったん一時ファイルに書き、署名が終わったらすぐに消します。下は流れだけを抜き出したもので、値はすべて伏せています。

claim = {"iss": "xxx@<domain>", "scope": SCOPE,
         "aud": "https://oauth2.googleapis.com/token",
         "iat": now, "exp": now + 900}
signing = b64(header) + b"." + b64(claim)
sig = subprocess.run(["/usr/bin/openssl", "dgst", "-sha256", "-sign", "<一時ファイル>"],
                     input=signing, capture_output=True).stdout
token = post("https://oauth2.googleapis.com/token", {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": (signing + b"." + b64(sig)).decode()})

外部のライブラリを入れないのは、定期タスクを動かす Mac で、追加の準備なしに動かしたいからです。

社内の記録には、別の道具のPythonをこの置き場で動かして落ちた例もあります。同じフォルダの自作ファイルが、標準のモジュールを上書きしていました。

人のログインを借りる方式はやめ、鍵だけで動く形にした

開始時は、人のログインからサービスアカウントを借りる方式でした。2026年10月4日の朝に invalid_rapt で失敗し、同じ日の午後に鍵の方式へ移しました。以降、ログイン切れで朝の取得が止まった日はありません。

この日の経緯と、GA4 MCPで再認証のエラーが5回出た記録は、GA4 MCPの「503 Reauthentication is needed」の直し方に書きました。

鍵がそもそも作れない組織の設定に当たった話は、サービスアカウントの鍵が作れないときの話にあります。

Search Console MCPではなくPythonにした理由

「search console mcp」で探すと、AIから Search Console を会話で触る方法が見つかります。僕の会社が毎朝の取得をMCPではなくPythonにしたのは、決まった数字を毎日同じ形で残すことが目的だったからです。

会話で分析するなら、MCPは便利です。僕の会社でもGA4は公式のMCPにつないで、会話で数字を見ています。

つなぎ方はGA4 MCPの接続の記録に書きました。

毎朝の取得で、Pythonにしてよかった3つの点

1つ目は、出力が毎日同じ形のファイルになる点です。会話で取ると、聞き方で項目が変わります。日付ごとのファイルなら、38日分を並べて比べられます。

2つ目は、認証を自分の手で直せる点です。社内の運用メモには、GA4のMCPで認証ファイルを作り直しても、再起動まで失敗し続けた記録があります(2026年7月28日)。

MCPのサーバーが、古い認証を覚えたままだったためです。スクリプトなら、実行のたびに鍵を新しく読みます。

3つ目は、失敗の扱いを自分で決められる点です。失敗した回は、同じ日の成功したファイルを上書きしません。理由と状態だけを残して、数字は推測で埋めない作りにしています。

使い分けの目安

目的向いている形理由
気になった語を、その場で深掘りするMCP(会話)聞きたいことを言葉で変えられる
毎日同じ数字を残して比べるスクリプト+定期実行形がそろい、失敗の扱いを決められる
サイトマップの送信など書き込みスクリプト呼ぶ時機(公開の直後)を決めて自動にできる

僕なら、まずスクリプトで毎日の記録を作り、深掘りしたい日だけ会話で触ります。記録が先にあれば、会話の答えが正しいかも照らし合わせられます。

取ったデータは朝の報告と改善候補づくりに使う

取ったデータは、社内の定期処理が毎朝読む材料になっています。届け先は大きく、朝の報告メールと、記事の改善候補づくりの2つです。

朝の報告メールには、/column/ の表示回数と順位、記事ごとの点数が入ります。2026年10月3日から、毎朝1通が届く形で回しています。

改善候補は「惜しい語」と「読まれていない記事」から作る

改善候補は、保存したファイルから機械で作っています。目安は、まだインデックスされていない記事、4〜20位にいる惜しい検索語、表示が多いのにクリックが少ない記事の3つです。

10月15日には、日別の検索語の取得を足しました。その少し前の週次の報告で、別の順位ツールの「全部圏外」を計測の不調と誤って判断したためです。日別のGSCで照らし合わせると、実際に表示が0の語でした。

10月の急落も、日別のデータで切り分けた

/column/ の表示は、10月3〜7日の725回から、10月9〜13日の178回へ落ちました。このときも、日別とページ別の保存があったので、事故か、公開直後の一時的な伸びの終わりかを切り分けられました。

切り分けの手順はSEOハネムーンの終わりか事故かの見分け方に書いています。

毎日のファイルが残っていなければ、この切り分けはできなかったと考えています。

まとめ

  • Search Console APIは、サービスアカウントの鍵とPythonの標準ライブラリ(署名だけ macOS の openssl)で毎朝取れる
  • searchAnalytics.query は rowLimit 1〜25,000・dataState all。直近3日は速報値として比較から外す
  • URL検査APIは1サイト1日2,000件・1分600件。僕の会社は毎日20件と週1回約150件で、429が出たらその場で止める
  • サービスアカウントをプロパティに追加するのは所有者の画面作業。読むだけなら制限付き、サイトマップも送るならフル以上
  • 僕なら最初に、読み取り専用で28日の合計と検索語の上位10語だけを毎朝ファイルに残すところから始めます

よくある質問

Search Console APIで1回に取れる行数は何行までですか?

searchAnalytics.query の rowLimit は1〜25,000で、既定は1,000行です。それより多いときは startRow をずらして続きを取ります。ただし公式は、すべての行ではなく上位の行を返すと書いているので、合計値と行の合計が一致しないことがあります。

サービスアカウントを Search Console に追加できるのは誰ですか?

そのプロパティの所有者だけです。「設定」の「ユーザーと権限」は所有者にしか表示されません。サービスアカウントのメールアドレスを「ユーザーを追加」に入れ、権限を選びます。ここは API からではなく、人が画面で行う作業です。

読むだけなら制限付きユーザーで足りますか?

検索パフォーマンスの取得とURL検査の結果の取得なら、制限付きで足ります。サイトマップの送信は制限付きではできず、フルユーザー以上が必要です。送信もさせるなら、API のスコープも読み取り専用ではなく読み書きのものにします。

直近の数日のデータは使えますか?

dataState を all にすると、確定前の新しいデータも返ります。ただし公式は、未確定の日以降の値は大きく変わることがあると書いています。僕の会社では直近3日を速報値として扱い、前後の比較やコホートの集計から外しています。

引用元・参考にした記事

  1. Search Analytics: query出典Google for Developers参照
  2. Usage limits出典Google for Developers参照
  3. Method: urlInspection.index.inspect出典Google for Developers参照
  4. Sitemaps: submit出典Google for Developers参照
  5. Using OAuth 2.0 for Server to Server Applications出典Google for Developers参照
  6. Search Console のプロパティに対するユーザーと権限の管理出典Search Console ヘルプ参照