サーチコンソールのデータ取得を自動化|Claude Code・Codexが直接読める形で保存する方法

こんにちは、のた(@Nor21011)です。

Google Search Console(以下、GSC)のデータをClaude CodeやCodexに読ませるため、毎回CSVをダウンロードしてアップロードしていませんか。

私も以前、GSCからCSVをダウンロードしてAIに渡していました。表示回数やクリック数を確認しても、その数字をブログ改善へどうつなげればよいのか分からず、CSVを使った分析は1〜2回で止まりました。

伸びていないブログの方針を、感覚だけで決めたくはありません。今後の記事やリライト候補を考えるにも、実データが必要です。

そこで、分析方法は後で考えるとして、まずはGSCのデータを蓄積していこうと決めました。ただ、毎回GSCを開いてCSVをダウンロードし、月ごとに整理するのは面倒です。

その作業を毎月繰り返さなくて済むように、GSCデータの取得と保存を自動化する仕組みをAIと一緒に作りました。

一度仕組みを作れば、月に1回Claude CodeやCodexへGSCデータの取得を頼むだけで、サイト全体・ページ別・検索語別のデータが月別CSVとして保存されます。保存先を指定しておけば、そのままAIが読める状態になります。

この記事では、私が作ったGSCデータの取得・保存の仕組みと、同じ環境を作るためのセットアップ用プロンプトを紹介します。

まずは、この仕組みを使って、毎月どのようにGSCデータを取得するのか説明します。

目次

結論:Claude Code・Codexへ頼めば、GSCデータの取得と保存を自動化できる

ポイントを指差すのた
POINT
AIへ頼めば、GSCデータの取得と保存を任せられる

私が作った仕組みは、Claude CodeやCodexなどのエージェント型AIに動いてもらうことが前提です。

一度設定を済ませれば、あとは月に1回、Claude CodeやCodexへ「GSCデータを取得して」と頼むだけです。AIは保存されている手順書を確認し、取得用プログラムを実行します。取得したデータは月別CSVとして保存されます。

実際の流れは次のとおりです。

  1. Claude CodeやCodexへGSCデータの取得を頼む
  2. AIが保存済みの手順書を確認する
  3. AIが取得用プログラムを実行し、月別CSVを保存する
  4. AIが作成・更新したファイルを報告する

私が行うのは、Claude CodeやCodexへ取得を頼むところまでです。その後のデータ取得と月別CSVへの保存は仕組みに任せています。同じ月を取り直した場合も、保存済みのCSVを更新してくれます。

保存するのは、サイト全体・ページ別・検索語別の3種類

手を挙げるのた
DATA
サイト全体・ページ別・検索語別を、月別CSVに保存する

GSCから取得したデータは、サイト全体・ページ別・検索語別の3種類に分けて月別CSVへ保存します。対象はGoogleのウェブ検索のデータです。ページ別には記事だけでなく、トップページなどのデータも含まれます。

ファイル 保存する内容 主な用途
YYYY-MM_site.csv 日付、クリック数、表示回数、CTR、平均掲載順位 ブログ全体の推移を見る
YYYY-MM_page.csv 日付、ページURL、クリック数、表示回数、CTR、平均掲載順位 ページごとの実績を見る
YYYY-MM_query.csv 日付、検索語、ページURL、クリック数、表示回数、CTR、平均掲載順位 開示された検索語を見る

たとえば2026年8月分なら、2026-08_site.csv2026-08_page.csv2026-08_query.csvという3ファイルが作られます。

何をどう分析するかは決まっていません。それでも、あとからブログ全体、各記事、検索語のどこからでも確認できるように、3種類を別々に残すことにしました。

サイト全体のCSVにはブログ全体の数字が、ページ別のCSVには各ページの数字が、検索語別のCSVには開示された検索語の数字が残ります。このように、同じ表示回数やクリック数でも、CSVごとに確認できる範囲が異なります。

私は最初から、この違いを理解して3種類に分けたわけではありません。当初は、日付・検索語・ページURLをまとめた1種類のCSVだけを保存する設計でした。しかし、実際に保存した数字を比べると、1種類だけではブログの状態を読み違える可能性があると分かりました。

3種類に分けるのは、数字を混同しないようにするため

驚くのた
CHECK
同じ月の表示回数でも、集計の仕方で数字が変わる

サイト全体、ページ別、検索語別は、同じデータを細かく分けただけではありません。同じ「表示回数」でも、数え方や含まれる範囲が異なります。

実際に2026年7月のデータを比べると、同じ月の表示回数でも、保存したCSVによってこれだけの違いがありました。

2026年7月のCSV 表示回数の合計
サイト全体 72
ページ別 94
検索語別 9

検索語別のCSVだけを見ていたら、私のブログはこの月に9回しか表示されていないと思ってしまいます。しかし、サイト全体のCSVでは72回でした。つまり、検索語を調べるための数字を、そのままブログ全体の数字として使うことはできません。

GSCでは、プライバシー保護のため、一部の検索語が一覧に出てきません。その検索語による表示もサイト全体の合計には含まれるため、検索語の一覧だけでは全体を把握できないことが、Googleの公式ヘルプでも説明されています。また、取得できるデータの件数にも制限があり、検索語別のCSVにすべての明細が残るわけではありません。

一方、ページ別の合計は94回で、サイト全体の72回より多くなっています。サイト全体とページ別では、表示回数の数え方も異なります。たとえば、1回の検索結果に同じサイトの記事が2本表示された場合、サイト全体では1回、ページ別ではそれぞれ1回と数えます。Googleの集計方法の説明でも、この違いが示されています。

今回の検索語別CSVは、検索語とページURLを組み合わせて取得しています。そのため、サイト全体とは表示回数の数え方が異なり、72回と9回の差を、そのまま「検索語が分からない表示回数」と考えることもできません。

保存したCSVをAIに読ませるときも、ブログ全体を見るならsite、各ページを見るならpage、検索語を見るならqueryを使い分けます。

こうした違いを踏まえ、私が作った仕組みでは、ブログ全体・ページ別・検索語別の数字を混同しないように、sitepagequeryの3種類に分けて保存しています。

GSCデータ取得の仕組みを、自分のPCに導入する方法

OKサインを出すのた
SETUP
プロンプトを渡し、AIの案内に沿って一つずつ設定する

ここからは、同じ仕組みを自分のPCへ導入する方法を紹介します。

この記事のセットアップ用プロンプトをClaude CodeやCodexへ渡せば、AIの案内に沿って自分のPCへ仕組みを導入できます。このプロンプトは、コードだけを作らせるものではなく、導入作業を一つずつ確認しながら伴走してもらうための指示書です。

セットアップ用プロンプトでは、AIがまずPCの環境やCSVの保存先を一つずつ確認し、その後、必要なファイルの作成、Google CloudとSearch Consoleの初回設定、動作テストへ順番に進むよう指示しています。AIができるファイル作成やコマンド実行はAIへ任せ、ブラウザで操作する場面だけ、「次にどこを開き、何を選ぶか」という案内に沿って進める形です。

途中で分からないことがあれば、その場で質問できます。設定がうまくいかない場合も、既存のファイルや設定を勝手に変更せず、エラーの原因と次に確認することを一つずつ説明するよう、プロンプトに指示しています。

配布ファイルをダウンロードしたり、解凍したりする必要はありません。

ブラウザ版のClaude.ai・ChatGPTだけでは導入できません
このセットアップ用プロンプトは、PC内のファイルを作成し、プログラムを実行できるClaude CodeやCodexを前提にしています。ブラウザ上で会話するだけのClaude.aiやChatGPTでは、この記事と同じ方法で自分のPCへ導入できません。

動作確認したのはmacOSです。WindowsとLinuxでは確認していません。

Google CloudとSearch Consoleを開いて行う設定は、基本的に初回だけです。導入後は月に1回、AIへGSCデータの取得を頼むだけです。

自分のPCに導入するまでの流れ

  1. セットアップ用プロンプトをClaude CodeやCodexへ渡す
  2. AIから、PCの環境、CSVの保存先、取得するサイトについて一つずつ質問を受ける
  3. AIの案内に沿って、Google CloudとSearch Consoleでデータを読み取るための設定を行う
  4. Google Cloudで作成したJSONキーを、AIと保存場所を確認して保管する
  5. AIに取得用コードと設定ファイルを保存してもらう
  6. AIにデータが確定している1か月分を取得してもらい、3種類のCSVが保存されるか確認する
  7. 2回目以降にAIが確認する通常運用の手順書を、PC内へ保存してもらう

セットアップ用プロンプトでは、各段階が終わるまで次の作業へ進まないよう指示しています。分からない設定もAIに確認しながら、一つずつ進められます。

動作確認済みのコードを使う

セットアップ用プロンプトには、私の環境で動いているコードをもとに、個人用の設定を外した公開用コードを入れています。AIに毎回新しいコードを作らせず、同じコードを使い、取得するサイトや保存先だけを各自の環境に合わせます。

公開用コードはmacOSでテストし、実際のGSCからサイト全体・ページ別・検索語別の3種類を保存できることも確認しました。

このコードが行うのは、GSCデータの読み取りとCSV保存です。GSCの設定変更、記事の公開や削除、ブログ改善の自動実行は行いません。

JSONキーの扱いも、AIが一つずつ案内する

今回の仕組みでは、Google Cloudで「サービスアカウント」というプログラム用のアカウントを作り、GSCのデータを読む権限を与えます。そのアカウントとしてデータを取得するために使うのが、JSONキーという認証用ファイルです。普段使っているGoogleアカウントのパスワードとは別のものですが、大切に管理する必要があります。

ただし、Googleは、可能であればサービスアカウントキーを使わない認証方法を推奨しています。たとえば、ブラウザで自分のGoogleアカウントにログインし、プログラムによるデータの読み取りを許可する方法です。Google Cloudの公式資料で案内されています。

この記事では、私が実際に使い、動作を確認したJSONキーを使う方法を紹介します。キーを使わない方法への変更は検証していないため、今回の手順には含めていません。JSONキーはプログラムや共有フォルダとは別の場所に保管し、AIのチャットにも中身を渡さずに設定します。

Googleへのログインは自分でブラウザ上で行い、パスワードや2段階認証コードをAIへ渡す必要はありません。JSONキーの中身も読んだりチャットへ貼ったりせず、AIとは保存場所だけを確認して設定を進めます。保存場所が分からなければ、PCの環境に合う候補を提案してもらえます。

この仕組みには、GSCデータの読み取りに必要な権限だけを与えます。ブログの記事やGSCのデータを変更・削除することはありません。また、セットアップ用プロンプトには、次の安全策も入れています。

  • JSONキーの中身を開いたり、チャットへ貼るよう求めたりしない
  • ファイルの作成やプログラムの実行前に、何をするのか説明して確認を取る
  • 同じ名前のファイルを勝手に上書きせず、設定も推測で変更しない
  • AIがJSONキーの中身を誤って読み取ったり、チャットへ表示したりした場合は作業を止め、古いキーの無効化と再作成を一つずつ案内する

万が一、AIがJSONキーの中身を誤って読み取ったり、チャットへ表示したりした場合は、漏えいしたものとして対処する必要があります。そのキーは使い続けず、いったん作業を止めてください。

このときも、復旧の手順を一人で調べる必要はありません。セットアップ用プロンプトでは、古いキーの無効化から新しいキーの作成まで、AIが一つずつ案内するよう指示しています。その案内を確認しながら、自分でGoogle Cloudの画面を操作して交換します。

セットアップ用プロンプト

ここまでの設定手順を、すべて覚えておく必要はありません。下の「プロンプト全文をコピー」ボタンでコピーし、Claude CodeやCodexへ渡してください。内容を確認したい場合は「全文を表示」を開くと、コードを含めて読めます。セットアップ用プロンプトでは、AIが最初の確認事項を1つだけ質問し、回答と作業の完了を確認してから次へ進むよう指示しています。AIの案内に沿ってこのやり取りを繰り返し、3種類のCSVが保存されるところまで一緒に確認してください。

JSONキーについて
JSONキーの中身をAIへ貼る必要はありません。保存場所が分からない場合も、AIがPCの環境に合う候補を提案します。AIが誤って中身を読み取ったり、チャットへ表示したりした場合は、そこで作業を止め、AIの案内に沿って古いキーを無効にし、新しく作り直してください。
セットアップ用プロンプト

「全文を表示」を開くと、コードを含むプロンプト全体を確認・選択できます。

冒頭の内容
# Search Consoleデータ取得のセットアップを手伝ってください

あなたは、私のPC内のファイルを読み、コマンドを実行できるAIとして、この手順を日本語で伴走してください。
全文を表示

長い行は画面幅に合わせて折り返しています。ボタンでコピーすると、元の改行と字下げを保った全文がコピーされます。

# Search Consoleデータ取得のセットアップを手伝ってください

あなたは、私のPC内のファイルを読み、コマンドを実行できるAIとして、この手順を日本語で伴走してください。

## 目的

Google Search Consoleの検索パフォーマンスデータを読み取り専用で取得し、次の3種類の月別CSVへ保存します。

- `YYYY-MM_site.csv`:日付ごとのサイト全体値
- `YYYY-MM_page.csv`:日付・ページごとの値
- `YYYY-MM_query.csv`:日付・検索語・ページごとの値

取得後は、Claude CodeやCodexがCSVを直接読める場所へ保存します。分析やブログ改善の判断を自動実行する仕組みではありません。

## 進め方

- 私はプログラミングに詳しくない前提で、専門用語を短く説明してください。
- 一度に全手順を出さず、現在のステップが終わってから次へ進んでください。
- 各ステップでは、「今から行うこと」「なぜ必要か」「今回行う操作」を短く説明し、私が完了したと伝えるまで次の操作へ進まないでください。
- 最初の返答では、これから設定を1段階ずつ一緒に進めること、Google CloudとSearch Consoleの操作は基本的に初回だけであること、取得は読み取り専用でブログやGSCのデータを変更しないことを短く説明してください。その後、最初の確認事項を1つだけ質問してください。
- 質問は原則として1回に1つだけにし、私が分からないと答えた場合は、確認方法か安全な推奨案を1つ示してください。
- ブラウザ上で私が操作する必要がある場面では、操作内容を一つずつ案内して、完了を待ってください。
- PC内のファイル作成やコマンド実行は、内容を説明して私が了承した後、可能な限りあなたが行ってください。私に長いコマンドを手入力させないでください。
- 画面の表示が案内と違う場合や、私が分からないと答えた場合は、推測で進めず、画面に見えている秘密情報以外の項目名を一つだけ確認してください。
- ファイル作成、ライブラリのインストール、初回実行の前には、これから行うことを説明して確認を取ってください。
- エラーが出たら設定を推測で変更せず、エラー内容と確認箇所を日本語で説明してください。
- `sudo`、既存ファイルの削除、既存環境の大幅な変更はしないでください。
- `fetch_gsc.py`、`requirements.txt`、`gsc.env`、`GSC_USAGE.md`など、セットアップ用の同名ファイルがある場合は無断で上書きしないでください。内容を表示せずに存在だけを伝え、別フォルダを使うかバックアップするか確認してください。
- 月別CSVは、取得を依頼した月のファイルを更新する仕様です。実行前に更新対象のファイル名を説明し、同名ファイルがある場合は上書きされることを伝えてください。

## JSONキーを安全に扱うルール

JSONキーを作る前に、次の内容を専門用語を使わずに説明してください。

- JSONキーは、Googleに「この仕組みからのアクセスです」と伝える認証用ファイルです
- Googleアカウントのパスワードを入力するものではありません
- Googleアカウントのパスワードや2段階認証コードを、チャットへ貼るよう求めないでください。ログイン操作は私がブラウザ上で行います
- この設定ではGSCの読み取りに必要な権限だけを使い、ブログやGSCのデータを変更・削除しません
- 中身をチャットへ貼る必要はなく、AIは保存場所とファイルの有無だけを確認します
- 取得用プログラムはGoogleへの認証時にJSONキーを使用しますが、AIが中身を開いたり、チャットへ表示したりする必要はありません
- 保存場所が決まっていない場合は、OSと現在の作業場所を確認し、Git管理や共有の対象にならない安全な候補を1つ提案します
- AIが誤って中身を読み取ったり、チャットへ表示したりした場合は漏えいしたものとして扱い、そのキーを使い続けません。作業を止め、Google Cloudでの無効化・削除と新しいキーの作成を1段階ずつ案内します

- サービスアカウントのJSONキーの**中身を読まない、表示しない、チャットへ貼らせない**でください。
- JSONキーの存在確認は、ファイルパスとファイルの有無だけで行ってください。`cat`、`head`、`tail`、エディタ表示などで開かないでください。
- JSONキーをGoogle以外へ送信しないでください。
- JSONキーを取得用プログラム、設定例、記事用テキストへ埋め込まないでください。
- JSONキーは作業フォルダやGit管理下ではなく、私が指定する非公開フォルダへ置いてください。
- JSONキーの移動や保存場所の変更が必要な場合も、移動元と移動先だけを説明して私の了承を得てください。キーの中身は開かないでください。
- macOSまたはLinuxでは、JSONキーを置いた後に所有者だけが読める権限へ変更してください。Windowsでは無理に同じコマンドを使わず、OSに合う方法を案内してください。
- AIが誤ってキーの中身を読み取ったり、チャットへ表示したりした場合は作業を止め、Google Cloudでそのキーを無効化・削除し、新しいキーを作る手順を1段階ずつ案内してください。

## 最初に確認すること

作業を始める前に、次を上から1項目ずつ確認してください。一度にまとめて質問せず、分からない項目は確認方法か安全な推奨案を案内してください。

1. OS(この記事で動作確認したのはmacOSです)
2. Pythonのバージョン。3.10以上を使いますが、私はPythonの文法を理解する必要はありません
3. この仕組みを置く作業フォルダ
4. CSVの保存先。未指定なら作業フォルダ内の`gsc_data`を使います
5. Search Consoleに表示される対象プロパティ。URLプレフィックスなら末尾の`/`まで完全一致させます
6. Google CloudのサービスアカウントJSONキーを、すでに作成しているか

もしあなたがPC内のファイル作成やコマンド実行をできないAIであれば、ここで止まり、「この手順はClaude CodeやCodexなど、PC内のファイルとコマンドを扱えるAI向けです。ブラウザ上で会話するだけのClaude.aiやChatGPTでは導入できません」と説明してください。

## 実行前に説明すること

ファイルを作る前に、このプログラムについて次を私へ説明してください。

- **入力**:Search Consoleのプロパティ、取得期間、サービスアカウントJSONキーの保存場所
- **通信先**:Googleの認証サービスとGoogle Search Console API
- **処理**:検索パフォーマンスを`site`、`page`、`query`の3種類で読み取る
- **出力**:指定フォルダの月別CSV。同じ月のCSVは再実行時に上書きする
- **しないこと**:Search Console設定の変更、記事やブログの変更、Google上のデータ削除、分析の自動実行、キー内容の表示

説明後、私の了承を得てから進んでください。

## Google CloudとSearch Consoleの準備

サービスアカウントJSONキーがまだない場合は、次を一段ずつ案内してください。画面名が変わっている場合は、現在のGoogle Cloud画面に合わせて読み替え、似た名前の別APIを選ばないよう確認してください。

案内を始める前に、Google CloudとSearch Consoleの設定は基本的に初回だけであり、設定後の通常運用では開く必要がないことを伝えてください。

1. 既存のGoogle Cloudプロジェクトを再利用するか、新しく作成する
2. **Google Search Console API**(`searchconsole.googleapis.com`)を有効にする。`Custom Search API`は別物なので選ばない
3. `gsc-fetcher`など識別できる名前でサービスアカウントを作る
4. サービスアカウント自体には、Google Cloudプロジェクトの編集者など不要なロールを付けない
5. サービスアカウントのJSONキーを1つ作り、私が指定した非公開フォルダへ移動する
6. macOSまたはLinuxでは、キーの権限を所有者だけが読める状態にする
7. Google Cloudの画面に表示されるサービスアカウントのメールアドレスをコピーする。JSONキーを開いて調べない
8. Search Consoleの対象プロパティで「設定」→「ユーザーと権限」から、そのメールアドレスをユーザー追加する
9. まずは必要最小限の「制限付き」権限を選ぶ

403エラーが出ても、すぐに権限を強くしないでください。先に、対象プロパティの完全一致、サービスアカウントのメールアドレス、APIの有効化を確認してください。

## 作成するファイル

私が指定した作業フォルダに、次を作ってください。

```text
作業フォルダ/
├── fetch_gsc.py
├── requirements.txt
├── gsc.env
├── GSC_USAGE.md      # 2回目以降にAIが確認する手順書
├── .gitignore        # Git管理している場合
└── gsc_data/         # 既定のCSV保存先
```

### 1. `fetch_gsc.py`

次のコードは検証用の固定コードです。環境差で動かない理由が判明するまでは、取得ロジックを再設計したり、別のコードへ書き換えたりせず、そのまま保存してください。変更が必要な場合は、変更理由と差分を先に説明して私の了承を得てください。

```python
#!/usr/bin/env python3
"""Google Search Consoleの日別データを月別CSVへ保存する。

この公開用スクリプトは、次の3種類を別ファイルへ保存する。

  site  : 日付ごとのサイト全体値
  page  : 日付・ページごとの値
  query : 日付・検索語・ページごとの値

設定は、このファイルと同じフォルダに置く ``gsc.env`` から読む。
サービスアカウントのJSONキー自体は、作業フォルダの外へ保存する。
"""

from __future__ import annotations

import argparse
import csv
import os
import sys
from collections import defaultdict
from datetime import date, timedelta
from pathlib import Path

try:
    from dotenv import dotenv_values
    from google.oauth2 import service_account
    from googleapiclient.discovery import build
    from googleapiclient.errors import HttpError
except ModuleNotFoundError as error:
    missing = error.name or "必要なライブラリ"
    sys.exit(
        f"[エラー] {missing} が見つかりません。\n"
        "先に requirements.txt のライブラリをインストールしてください。"
    )


SCRIPT_DIR = Path(__file__).resolve().parent
DEFAULT_CONFIG_PATH = SCRIPT_DIR / "gsc.env"

SCOPES = ["https://www.googleapis.com/auth/webmasters.readonly"]
METRICS = ["clicks", "impressions", "ctr", "position"]
ROW_LIMIT = 25_000
RETENTION_MONTHS = 16

# (ファイル名の接尾辞、APIへ渡すディメンション、説明)
DATASETS = [
    ("site", ["date"], "サイト全体の推移"),
    ("page", ["date", "page"], "記事ごとの実績"),
    ("query", ["date", "query", "page"], "開示された検索語"),
]


def resolve_path(raw_path: str, base_dir: Path) -> Path:
    """設定値の ``~`` と環境変数を展開し、絶対パスへ変換する。"""
    expanded = os.path.expandvars(os.path.expanduser(raw_path.strip()))
    path = Path(expanded)
    if not path.is_absolute():
        path = base_dir / path
    return path.resolve()


def load_config(config_path: Path) -> tuple[Path, str, Path]:
    """設定ファイルから鍵の場所、GSCプロパティ、出力先を読む。"""
    config_path = config_path.expanduser().resolve()
    if not config_path.exists():
        sys.exit(
            f"[エラー] 設定ファイルがありません: {config_path}\n"
            "fetch_gsc.py と同じフォルダへ gsc.env を作成してください。"
        )

    values = dotenv_values(config_path)
    key_value = values.get("GSC_SA_KEY_PATH")
    site_url = values.get("GSC_SITE_URL")
    output_value = values.get("GSC_OUTPUT_DIR") or "./gsc_data"

    missing = [
        name
        for name, value in (
            ("GSC_SA_KEY_PATH", key_value),
            ("GSC_SITE_URL", site_url),
        )
        if not value
    ]
    if missing:
        sys.exit(
            f"[エラー] {config_path} に次の項目がありません: "
            + ", ".join(missing)
        )

    key_path = resolve_path(str(key_value), config_path.parent)
    output_dir = resolve_path(str(output_value), config_path.parent)
    site_url = str(site_url).strip()

    if not key_path.is_file():
        sys.exit(f"[エラー] サービスアカウントのJSONキーが見つかりません: {key_path}")

    if not (site_url.startswith("https://") or site_url.startswith("http://") or site_url.startswith("sc-domain:")):
        sys.exit(
            "[エラー] GSC_SITE_URLは、Search Consoleに表示されるプロパティを"
            "そのまま指定してください。\n"
            "例: https://example.com/ または sc-domain:example.com"
        )

    return key_path, site_url, output_dir


def build_service(key_path: Path):
    """読み取り専用の認証情報でSearch Console APIへ接続する。"""
    credentials = service_account.Credentials.from_service_account_file(
        str(key_path), scopes=SCOPES
    )
    return build("searchconsole", "v1", credentials=credentials, cache_discovery=False)


def month_bounds(year: int, month: int) -> tuple[date, date]:
    """指定月の初日と末日を返す。"""
    first = date(year, month, 1)
    next_month = date(year + (month == 12), month % 12 + 1, 1)
    return first, next_month - timedelta(days=1)


def default_period(today: date) -> tuple[date, date]:
    """既定期間は先月1日から今日まで。"""
    last_month_day = today.replace(day=1) - timedelta(days=1)
    return last_month_day.replace(day=1), today


def resolve_period(args: argparse.Namespace, today: date) -> tuple[date, date]:
    """コマンド引数から取得期間を決める。月別CSVを壊さない範囲だけ許可する。"""
    if args.month:
        try:
            year, month = (int(value) for value in args.month.split("-"))
            start, end = month_bounds(year, month)
        except (ValueError, TypeError):
            sys.exit("[エラー] --month は YYYY-MM 形式で指定してください(例: 2026-07)")
        if start > today:
            sys.exit("[エラー] 未来の月は取得できません。")
        return start, min(end, today)

    if args.start or args.end:
        if not (args.start and args.end):
            sys.exit("[エラー] --start と --end は両方セットで指定してください。")
        try:
            start = date.fromisoformat(args.start)
            end = date.fromisoformat(args.end)
        except ValueError:
            sys.exit("[エラー] 日付は YYYY-MM-DD 形式で指定してください。")
        if start > end:
            sys.exit("[エラー] --start が --end より後になっています。")
        if end > today:
            sys.exit("[エラー] 未来の日付は取得できません。")
        if start.day != 1:
            sys.exit(
                "[エラー] 月別CSVを途中の日から上書きしないため、"
                "--start は月の1日を指定してください。"
            )
        _, end_of_month = month_bounds(end.year, end.month)
        if end != today and end != end_of_month:
            sys.exit(
                "[エラー] --end は月末、または今日を指定してください。"
            )
        return start, end

    return default_period(today)


def warn_if_out_of_retention(start: date, today: date) -> None:
    """GSCの保持期間より前を指定している可能性があれば警告する。"""
    approximate_limit = today - timedelta(days=RETENTION_MONTHS * 31)
    if start < approximate_limit:
        print(
            f"[注意] {start} はGSCの保持期間(約{RETENTION_MONTHS}か月)より前です。"
            "その期間は空で返る可能性があります。",
            file=sys.stderr,
        )


def iter_months(start: date, end: date):
    """開始月から終了月までを ``YYYY-MM`` 形式で返す。"""
    current = start.replace(day=1)
    final = end.replace(day=1)
    while current <= final:
        yield current.strftime("%Y-%m")
        current = date(current.year + (current.month == 12), current.month % 12 + 1, 1)


def fetch_rows(
    service,
    site_url: str,
    dimensions: list[str],
    start: date,
    end: date,
) -> list[dict]:
    """指定ディメンションの行を、25,000行ずつ取得する。"""
    rows: list[dict] = []
    start_row = 0

    while True:
        body = {
            "startDate": start.isoformat(),
            "endDate": end.isoformat(),
            "dimensions": dimensions,
            "type": "web",
            "dataState": "final",
            "rowLimit": ROW_LIMIT,
            "startRow": start_row,
        }

        try:
            response = service.searchanalytics().query(
                siteUrl=site_url, body=body
            ).execute()
        except HttpError as error:
            sys.exit(explain_http_error(error, site_url))

        batch = response.get("rows", [])
        rows.extend(batch)
        if len(batch) < ROW_LIMIT:
            break
        start_row += len(batch)

    return rows


def explain_http_error(error: HttpError, site_url: str) -> str:
    """よくあるAPIエラーを日本語で説明する。"""
    status = getattr(error.resp, "status", None)

    if status == 403:
        return (
            "[エラー] 403 権限がありません。次を確認してください。\n"
            f"  1. Search Consoleのプロパティ「{site_url}」に、サービスアカウントの\n"
            "     メールアドレスがユーザー追加されているか\n"
            "  2. プロパティが完全一致しているか(URLプレフィックスは末尾の/まで)\n"
            "  3. Google CloudでGoogle Search Console APIが有効になっているか"
        )
    if status == 400:
        return f"[エラー] 400 リクエストが不正です。日付やプロパティを確認してください。\n{error}"
    if status == 429:
        return "[エラー] 429 レート制限に達しました。しばらく待って再実行してください。"

    return f"[エラー] APIリクエストに失敗しました。\n{error}"


def to_records(rows: list[dict], dimensions: list[str]) -> list[dict]:
    """API応答をCSVへ書ける辞書へ変換する。"""
    records: list[dict] = []
    for row in rows:
        record = dict(zip(dimensions, row["keys"]))
        record["clicks"] = row.get("clicks", 0)
        record["impressions"] = row.get("impressions", 0)
        record["ctr"] = round(row.get("ctr", 0.0), 6)
        record["position"] = round(row.get("position", 0.0), 2)
        records.append(record)
    return records


def write_by_month(
    records: list[dict],
    dimensions: list[str],
    suffix: str,
    output_dir: Path,
    start: date,
    end: date,
) -> list[tuple[Path, int, float, float]]:
    """日別レコードを月ごとに分け、同じ月のCSVを上書きする。

    データが0件の月も、列名だけのCSVを作る。API接続は成功したものの
    表示実績がなかった場合と、ファイル作成の失敗を区別するため。
    """
    output_dir.mkdir(parents=True, exist_ok=True)
    fieldnames = dimensions + METRICS

    buckets: dict[str, list[dict]] = defaultdict(list)
    for record in records:
        buckets[record["date"][:7]].append(record)

    written: list[tuple[Path, int, float, float]] = []
    for month in iter_months(start, end):
        items = buckets.get(month, [])
        items.sort(
            key=lambda record: (
                record["date"],
                -float(record["clicks"]),
                -float(record["impressions"]),
            )
        )
        path = output_dir / f"{month}_{suffix}.csv"

        with path.open("w", newline="", encoding="utf-8") as file:
            writer = csv.DictWriter(file, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerows(items)

        written.append(
            (
                path,
                len(items),
                sum(float(item["clicks"]) for item in items),
                sum(float(item["impressions"]) for item in items),
            )
        )

    return written


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Search Consoleの日別データを3つの切り口で月別CSVへ保存します。"
    )
    parser.add_argument(
        "--config",
        type=Path,
        default=DEFAULT_CONFIG_PATH,
        help="設定ファイルの場所(既定: fetch_gsc.pyと同じフォルダのgsc.env)",
    )
    parser.add_argument("--month", help="取得する月(YYYY-MM)")
    parser.add_argument("--start", help="開始日(YYYY-MM-DD、月の1日)")
    parser.add_argument("--end", help="終了日(YYYY-MM-DD、月末または今日)")
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    today = date.today()
    key_path, site_url, output_dir = load_config(args.config)
    start, end = resolve_period(args, today)
    warn_if_out_of_retention(start, today)

    print(f"対象プロパティ : {site_url}", file=sys.stderr)
    print(f"取得期間       : {start} 〜 {end}", file=sys.stderr)
    print(f"保存先         : {output_dir}\n", file=sys.stderr)

    service = build_service(key_path)
    results: list[tuple[Path, int, float, float]] = []
    empty: list[str] = []
    total_rows = 0

    for suffix, dimensions, description in DATASETS:
        print(f"[{suffix}] {description}を取得中...", file=sys.stderr)
        rows = fetch_rows(service, site_url, dimensions, start, end)
        total_rows += len(rows)
        if not rows:
            empty.append(suffix)
            print("  該当データなし", file=sys.stderr)
        records = to_records(rows, dimensions)
        results.extend(
            write_by_month(records, dimensions, suffix, output_dir, start, end)
        )
        if rows:
            print(f"  {len(rows)}行", file=sys.stderr)

    if total_rows == 0:
        print(
            "\n3種類とも0件でした。期間が新しすぎるか、"
            "その期間にGoogle検索で表示されなかった可能性があります。"
            "列名だけのCSVを保存しました。",
            file=sys.stderr,
        )

    print("\n保存しました。", file=sys.stderr)
    for path, count, clicks, impressions in results:
        print(
            f"  {path}  {count}行  クリック {clicks:g}  表示 {impressions:g}",
            file=sys.stderr,
        )

    if empty:
        print(
            f"\n[注意] データが0件だった切り口: {', '.join(empty)}",
            file=sys.stderr,
        )

    print(
        "\n※ 検索語を含むqueryデータには、プライバシー保護などにより"
        "表示されない検索語があります。"
        "\n※ pageの合計とsiteの合計は集計方法が異なるため、"
        "一致しない場合があります。",
        file=sys.stderr,
    )


if __name__ == "__main__":
    main()
```

### 2. `requirements.txt`

次をそのまま保存してください。

```text
google-api-python-client==2.198.0
google-auth==2.50.0
python-dotenv==1.2.1
```

### 3. `gsc.env`

このファイルにはキー本体ではなく、キーの保存場所と設定値だけを書きます。実際の値は、最初に私と確認した内容へ置き換えてください。

```dotenv
GSC_SA_KEY_PATH="サービスアカウントJSONキーの保存場所"
GSC_SITE_URL="Search Consoleに表示される対象プロパティ"
GSC_OUTPUT_DIR="CSVの保存先"
```

`GSC_SITE_URL`の例:

```text
https://example.com/
sc-domain:example.com
```

URLプレフィックスプロパティでは、`http`と`https`、`www`の有無、末尾の`/`までSearch Consoleの表示と一致させてください。

### 4. `.gitignore`

作業フォルダがGit管理下なら、少なくとも次を追記してください。既存の`.gitignore`は上書きしないでください。

```gitignore
.venv/
gsc.env
*.json
gsc_data/
```

### 5. `GSC_USAGE.md`

初回テストに成功した後、2回目以降の取得方法を残すために作成してください。次の内容を、実際のOS、作業フォルダ、仮想環境、CSV保存先に合わせて記載します。

- AIへ送る依頼文:`GSC_USAGE.mdを確認し、GSCの最新データを取得してください。`
- 通常取得でAIが実行するコマンド
- 特定月を指定するときにAIが実行するコマンド
- 取得対象のSearch Consoleプロパティ
- CSVの保存先
- 作成される`site`、`page`、`query`の3種類
- JSONキーの中身を読まない、表示しないという注意
- 実行後は、更新したファイル名、行数、クリック合計、表示合計だけを報告すること
- 検索語やページURLを、依頼なくチャットへ大量表示しないこと

2回目以降は、Pythonのコマンドを私が覚えて入力するのではなく、初回設定で作ったファイルを扱えるClaude CodeやCodexが、この手順書を読んで実行する前提です。

## Python環境とライブラリ

1. Python 3.10以上か確認してください
2. 作業フォルダ内に`.venv`という仮想環境を作ってください
3. OSに合う仮想環境のPythonを使い、`requirements.txt`をインストールしてください
4. グローバル環境へ無断でインストールせず、`sudo`も使わないでください
5. 実行に使ったPythonのバージョンと、インストール結果だけを報告してください

## 初回テスト

まず、データが確定している直近の1か月を`--month YYYY-MM`で指定して実行してください。

実行後は次を確認してください。

- `YYYY-MM_site.csv`
- `YYYY-MM_page.csv`
- `YYYY-MM_query.csv`

検索表示が少ないサイトでは、列名だけでデータ行が0件のCSVがあっても異常とは限りません。確認時は、ファイル名・列名・行数・クリック合計・表示合計だけを報告し、検索語やページURLの一覧をチャットへ大量表示しないでください。

エラー時は次の順に確認してください。

1. 403:Search Consoleへのユーザー追加、対象プロパティの完全一致、API有効化
2. 400:日付形式、対象プロパティ
3. JSONキーが見つからない:`gsc.env`に書いたパスとファイルの有無。キーの中身は開かない
4. ライブラリがない:仮想環境のPythonを使っているか
5. 0件:新しい期間を指定していないか、その期間に検索表示があるか

## 初回テスト後

テストに成功したら、`GSC_USAGE.md`を作成したうえで、次を短くまとめてください。

- 作業フォルダ
- CSV保存先
- 通常実行で使うコマンド
- 特定月を取り直すコマンド
- 取得できた3種類のCSV
- 自動化されている範囲と、手動のまま残る範囲
- JSONキーを安全に保管し、チャットへ貼らない注意

最後に、2回目以降は同じ作業フォルダでClaude CodeやCodexを開き、次の1文を送ればよいと案内してください。

```text
GSC_USAGE.mdを確認し、GSCの最新データを取得してください。
```

通常実行では引数を付けず、先月1日から今日までを取り直します。過去分をまとめて取得したい場合は、初回テスト成功後に、月の1日を`--start`、月末または今日を`--end`として実行方法を案内してください。私の了承なく過去分の一括取得は実行しないでください。

導入後は、AIへ頼むだけでGSCデータを取得できる

ポイントを指差すのた
HOW TO
AIは保存済みの手順書を確認し、データを取得する

設定が終わった後は、Claude CodeやCodexへデータ取得を頼むだけです。

「サーチコンソールのデータを更新して」と頼んでも構いません。

導入時には、対象サイトやCSVの保存先、取得方法、安全上の注意をまとめた手順書「GSC_USAGE.md」もPC内に保存しています。新しいチャットでは、それまでの会話が引き継がれていないこともあるため、この手順書を確認するように伝えると、どの設定で取得すればよいかをAIが確認できます。

そのまま使える依頼文はこちらです。

GSC_USAGE.mdを確認し、GSCの最新データを取得してください。

このように頼むと、AIは手順書を確認して取得用プログラムを実行し、更新したファイルを報告します。実行前に確認を求められた場合は、内容を確認して許可してください。

AIへ取得を頼んだ後、CSVが保存されるまでの流れを担当別に整理しました。

担当 内容
自分 AIへデータ取得を頼む
AI 手順書を確認し、取得用プログラムを実行する
取得用プログラム GSCから3種類のデータを取得し、月別CSVへ保存・更新する

取得したCSVはClaude CodeやCodexが直接読めるため、分析のたびにチャットへアップロードする必要はありません

通常は、実行日の前月1日から実行日までが取得対象です。そのうち、GSC側で確定しているデータだけを保存します。月初すぐに取得すると、前月末のデータがまだ確定しておらず、CSVに入らない場合があります。その場合は、数日後に前月分を指定して取り直してください

過去の特定月を取り直したい場合も、「2026年7月分を取り直して」とAIへ頼めば、月を指定するオプションを付けて実行してもらえます。同じ月のCSVは上書きされるため、途中時点の数字を別ファイルとして増やし続けることもありません。

私は月1回、この仕組みでGSCデータを取得する

考え中ののた
MY USE
私は月1回の取得を目安に、リマインダーも設定している

私はこの仕組みを使い、月に1回GSCデータを取得することにしています。現時点ではあくまで目安で、決まった日時に自動実行するような厳密なスケジュールは組んでいません。ただ、取得を忘れたまま何か月も過ぎると、必要なときに手元のCSVが古いままになってしまいます。そこで念のため、私のClaude CodeにはHook機能を使ったリマインダーを設定しています。

このHookはPCに保存したCSVの更新日を確認し、前回の更新から21日以上たった状態でClaude Codeを開くと、そろそろデータを取得する時期だと知らせてくれます。追加したばかりなので、記事執筆時点では実際の通知をまだ確認していません。通知が出たら、そのままClaude Codeへデータ取得を頼む予定です。

保存したCSVについては、Claude CodeとCodexのどちらでも、PC内の保存先から直接読めることを確認しています。以前はGSCの数字をどう使えばよいのか分からず、CSVをAIへ渡したのも1〜2回で終わりました。今は保存と受け渡しを続けられる状態まで整ったので、今後は保存した実データをAIと確認しながら、分析方法やブログ運営への活用法を考えていきます。

まとめ

この記事では、GSCデータを月別CSVとしてPCへ保存し、Claude CodeやCodexが直接読める状態にする方法を紹介しました。

  • PC内のファイル作成とプログラム実行ができるClaude Code・Codexを使う
  • ブラウザ版のClaude.aiやChatGPTだけでは導入できない
  • セットアップ用プロンプトを渡し、環境確認から動作テストまでAIの案内に沿って進める
  • Google CloudとSearch Consoleを開いて行う設定は、基本的に初回だけ
  • サイト全体・ページ別・検索語別の3種類を、数字が混ざらないように分けて保存する
  • 導入後は月に1回AIへ頼み、データの取得・分類・保存・同じ月のデータ更新を任せる
  • 保存したCSVは、チャットへアップロードせずにClaude CodeやCodexが直接読める

同じ仕組みを導入する場合は、記事内のセットアップ用プロンプト全体をClaude CodeやCodexへ渡し、AIからの質問に答えながら一つずつ進めてください。

Claude Code・Codexでブログ運営
サーチコンソールの
データ取得・保存をAIに任せる
PC内のファイル操作とプログラム実行ができる環境が必要です。ブラウザ版のClaude.ai・ChatGPTだけでは導入できません。
1
毎回のダウンロードと整理を省く
AIへ取得を頼めば、月別CSVの保存・更新まで任せられます。
2
数字を混同しないよう、3種類を分けて残す
サイト全体・ページ別・検索語別を、確認したい内容に合わせて使います。
3
保存した実データからAIへ相談できる
CSVをチャットへアップロードし直す必要はありません。
セットアップ用プロンプトを渡し、
AIの案内に沿って自分のPCへ導入できます。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

AIをガチで使い倒しているウルフ系ブロガー、のたです。
毎日Claude・ChatGPT・Geminiを使いながら、「AIで何ができるか」を実験中。
このブログでは、実際に使って感じたことをそのまま書いています。

目次