0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GA4 + Search Console × Claude で週次ブログレポートを完全自動化した

0
Posted at

はじめに

ブログを運営していると、定期的にアクセス状況を把握することが重要です。ただ実際にやろうとすると、以下のような作業が毎週発生します。

  1. GA4を開いてセッション・PV・流入元を確認する
  2. Search Consoleを開いて検索クエリ・掲載順位・CTRを確認する
  3. 先週と比べてどう変化したか数字を見比べる
  4. 「どの記事が伸びているか」「どのクエリに伸びしろがあるか」を自分で読み解く
  5. 次に何をすべきか判断する

これを毎週きちんとやろうとすると30〜60分かかります。しかも複数の画面を行き来しながら数字を頭の中で整理する必要があり、慣れないと何を見ればいいのかわからなくなります。

そこで以下の仕組みを作りました。

  • GA4 / Search Console API でデータを自動取得
  • matplotlib でグラフを自動生成
  • Cowork(Claude) がデータを読んでMarkdownレポートを執筆
  • Google Drive に自動アップロード
  • すべて Windowsタスクスケジューラ で毎週月曜に全自動実行

追加のAPI料金なし(Google APIは無料枠内)、Claude APIも叩かずに実現しています。

全体構成

月曜 7:00  Windowsタスクスケジューラ
           └ run.bat
              ├ GA4 / Search Console データ取得
              ├ Excel保存(openpyxl)
              ├ グラフ5枚生成(matplotlib)
              └ latest_report_data.json 保存

月曜 9:00  Cowork(Claude)スケジュールタスク
           └ JSONとグラフを読み込む
           └ Markdownレポート執筆・保存

月曜 10:00 Windowsタスクスケジューラ
           └ upload_report.bat
              └ 最新MDをGoogle Driveにアップロード

準備:Google Cloud の設定

1. APIの有効化

Google Cloud Console で以下の3つを有効化します。

  • Google Analytics Data API
  • Google Search Console API
  • Google Drive API

「APIとサービス」→「ライブラリ」から検索して有効化するだけです。

2. OAuth認証情報の取得

「APIとサービス」→「認証情報」→「認証情報を作成」→「OAuthクライアントID」

  • アプリケーションの種類:デスクトップアプリ
  • 作成後、credentials.json をダウンロード

3. OAuth同意画面の設定(重要)

同意画面が未設定の場合、実行時に アクセスをブロック エラーが出ます。

「OAuth同意画面」→「テストユーザー」に自分のGmailアドレスを追加してください。個人利用の場合、Googleの公式審査は不要です。

同意画面で「このアプリはGoogleで確認されていません」と出ても、「詳細」→「安全ではないページに移動」で続行できます。

ファイル構成

Documents/
├── credentials.json.json     ← GCPからダウンロードした認証情報
└── blog-analytics/
    ├── fetch_blog_data.py    ← メイン:データ取得・Excel・グラフ・JSON生成
    ├── generate_charts.py    ← グラフ生成モジュール
    ├── upload_report.py      ← DriveへのMDアップロード
    ├── requirements.txt
    ├── run.bat               ← 仮想環境で fetch_blog_data.py を実行
    ├── upload_report.bat     ← 仮想環境で upload_report.py を実行
    ├── setup_and_run.bat     ← 初回セットアップ用
    ├── token.json            ← OAuth認証後に自動生成
    └── data/
        ├── blog_report_YYYY-MM-DD_YYYY-MM-DD.xlsx
        ├── latest_report_data.json
        ├── weekly_report_YYYY-MM-DD.md
        └── charts_YYYY-MM-DD/
            ├── chart_daily_traffic.png
            ├── chart_traffic_sources.png
            ├── chart_top_pages.png
            ├── chart_search_queries.png
            └── chart_sc_performance.png

各ファイルの役割

ファイル 役割 実行タイミング
fetch_blog_data.py GA4・SCからデータ取得、Excel・グラフ・JSONを生成 Windowsタスクスケジューラ(月曜7:00)
generate_charts.py matplotlibでグラフPNGを生成するモジュール fetch_blog_data.pyから呼び出し
upload_report.py CoworkのMDレポートをDriveにアップロード Windowsタスクスケジューラ(月曜10:00)
run.bat .venvを有効化してfetch_blog_data.pyを実行 タスクスケジューラから呼び出し
upload_report.bat .venvを有効化してupload_report.pyを実行 タスクスケジューラから呼び出し
setup_and_run.bat 初回のみ .venv 作成・ライブラリインストール・実行 手動(初回のみ)

実装

fetch_blog_data.py

このスクリプトが担う処理:

  1. get_credentials() — OAuth認証。初回はブラウザが開き、以降は token.json で自動認証
  2. fetch_ga4_data() — GA4からサマリー・人気ページ・流入元・日別データを取得(今週・先週の2週分)
  3. fetch_search_console_data() — SCから検索クエリ・ページ別・日別データを取得(2週分)
  4. save_to_excel() — openpyxlで7シート構成のExcelを生成・保存
  5. generate_all_charts() — generate_charts.pyを呼び出してグラフ5枚をPNG保存
  6. save_json_summary() — Coworkがレポートを書くためのJSONサマリーを出力
  7. upload_to_drive() — ExcelをGoogle Driveの blog-analytics フォルダにアップロード
GA4_PROPERTY_ID = "YOUR_PROPERTY_ID"
SEARCH_CONSOLE_SITE_URL = "sc-domain:example.com"  # ドメインプロパティの場合
CREDENTIALS_FILE = Path(__file__).parent.parent / "credentials.json.json"

SCOPES = [
    "https://www.googleapis.com/auth/analytics.readonly",
    "https://www.googleapis.com/auth/webmasters.readonly",
    "https://www.googleapis.com/auth/drive.file",
]

def get_credentials():
    from google.oauth2.credentials import Credentials
    from google_auth_oauthlib.flow import InstalledAppFlow
    from google.auth.transport.requests import Request

    creds = None
    if TOKEN_FILE.exists():
        creds = Credentials.from_authorized_user_file(str(TOKEN_FILE), SCOPES)

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(str(CREDENTIALS_FILE), SCOPES)
            creds = flow.run_local_server(port=0)
        with open(TOKEN_FILE, "w") as token:
            token.write(creds.to_json())
    return creds

GA4データ取得

今週・先週の両方を取得して前週比を算出できるようにしています。

def main():
    today = datetime.date.today()
    end_date   = (today - datetime.timedelta(days=1)).strftime("%Y-%m-%d")
    start_date = (today - datetime.timedelta(days=7)).strftime("%Y-%m-%d")
    prev_end   = (today - datetime.timedelta(days=8)).strftime("%Y-%m-%d")
    prev_start = (today - datetime.timedelta(days=14)).strftime("%Y-%m-%d")

    creds = get_credentials()

    # 今週・先週をそれぞれ取得
    ga4_summary, ga4_pages, ga4_sources, ga4_daily = fetch_ga4_data(creds, start_date, end_date)
    ga4_summary_p, ga4_pages_p, ga4_sources_p, ga4_daily_p = fetch_ga4_data(creds, prev_start, prev_end)

GA4からは以下を取得しています。

  • サマリー(セッション・ユーザー・PV・直帰率・平均セッション時間・新規ユーザー)
  • 人気ページ Top20
  • 流入チャネル別セッション
  • 日別データ

Search Console データ取得

Search Console のプロパティ登録形式によってAPIのURLが変わります。

登録形式 API に渡す siteUrl
URLプレフィックス https://example.com/
ドメインプロパティ sc-domain:example.com
def fetch_search_console_data(creds, start_date, end_date):
    service = build("searchconsole", "v1", credentials=creds)

    # 検索クエリ Top30(インプレッション降順)
    queries_response = service.searchanalytics().query(
        siteUrl=SEARCH_CONSOLE_SITE_URL,
        body={
            "startDate": start_date,
            "endDate": end_date,
            "dimensions": ["query"],
            "rowLimit": 30,
            "orderBy": [{"fieldName": "impressions", "sortOrder": "DESCENDING"}],
        }
    ).execute()

generate_charts.py

このモジュールが担う処理:

fetch_blog_data.py から呼び出され、取得済みデータを受け取ってグラフPNGを生成する専用モジュールです。スクリプトの肥大化を防ぐためにファイルを分離しています。

生成するグラフは5種類で、今週・先週の比較を重ねて表示することで変化がひと目でわかるようにしています。

matplotlib で5種類のグラフをPNGとして保存します。ポイントは日本語フォントの設定です。

import matplotlib
matplotlib.use('Agg')  # GUI不要のバックエンド(スケジュール実行対応)
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

def _setup_font():
    available = {f.name for f in fm.fontManager.ttflist}
    for font in ['Yu Gothic', 'Meiryo', 'MS Gothic', 'IPAexGothic']:
        if font in available:
            plt.rcParams['font.family'] = font
            return

matplotlib.use('Agg') はバックグラウンド実行(タスクスケジューラ)でGUIなしに動かすために必要です。

生成するグラフ一覧:

ファイル名 内容
chart_daily_traffic.png 日別セッション・PV推移(今週vs先週)
chart_traffic_sources.png 流入チャネル別セッション(横棒)
chart_top_pages.png 人気ページ Top10(横棒)
chart_search_queries.png 検索クエリ Top15(インプレッション・クリック)
chart_sc_performance.png SC日別クリック・インプレッション推移

Cowork向けJSONサマリーの保存

Cowork(Claude)がレポートを書きやすいようにデータをJSONで出力します。

def save_json_summary(current, prev, start_date, end_date, prev_start, prev_end, excel_path):
    def sc_totals(daily_list):
        if not daily_list:
            return {}
        return {
            "総クリック数": sum(d["クリック数"] for d in daily_list),
            "総インプレッション数": sum(d["インプレッション数"] for d in daily_list),
            "平均CTR(%)": round(sum(d["CTR(%)"] for d in daily_list) / len(daily_list), 1),
            "平均掲載順位": round(sum(d["平均掲載順位"] for d in daily_list) / len(daily_list), 1),
        }

    summary = {
        "period": {"start": start_date, "end": end_date},
        "prev_period": {"start": prev_start, "end": prev_end},
        "ga4_summary": current["ga4_summary"],
        "ga4_summary_prev": prev["ga4_summary"] if prev else {},
        "sc_summary": sc_totals(current["sc_daily"]),
        "sc_summary_prev": sc_totals(prev["sc_daily"]) if prev else {},
        "sc_queries": current["sc_queries"][:15],
        # ...
    }
    json_path = DATA_DIR / "latest_report_data.json"
    with open(json_path, "w", encoding="utf-8") as f:
        json.dump(summary, f, ensure_ascii=False, indent=2)

upload_report.py

このスクリプトが担う処理:

Coworkが執筆して data/ フォルダに保存したMarkdownレポートをGoogle Driveにアップロードします。fetch_blog_data.py とは分離されており、Coworkのレポート執筆完了後(10:00)に単独で実行します。

  • find_latest_report()weekly_report_*.md をファイル名の日付降順でソートして最新のものを取得
  • 同名ファイルが既にDriveにある場合は削除してから再アップロード(同じ週を再実行しても重複しない)
  • token.json(fetch_blog_data.py の認証済みトークン)を流用するため追加認証不要

Coworkが書いた最新のMDをDriveにアップロードします。同一週のファイルは上書き(削除→再アップロード)するので重複しません。

def find_latest_report():
    """ファイル名の日付順ソートで最新レポートを取得"""
    reports = sorted(DATA_DIR.glob("weekly_report_*.md"), reverse=True)
    if not reports:
        sys.exit(1)
    return reports[0]

日付入りファイル名(weekly_report_2026-03-10.md)をアルファベット逆順ソートすることで、今日の日付を使わずに最新ファイルを特定できます。

仮想環境のセットアップ

グローバルのPython環境を汚さないよう .venv を使います。

REM setup_and_run.bat(初回のみ)
python -m venv .venv
call .venv\Scripts\activate.bat
pip install -r requirements.txt -q
python fetch_blog_data.py
deactivate
REM run.bat(2回目以降・タスクスケジューラ用)
cd /d "%~dp0"
call .venv\Scripts\activate.bat
python fetch_blog_data.py
deactivate

requirements.txt

google-auth
google-auth-oauthlib
google-auth-httplib2
google-api-python-client
openpyxl
matplotlib

Windowsタスクスケジューラの設定

タスクスケジューラで以下の2つを登録します。

タスク名 プログラム 実行時刻
Blog_DataFetch run.bat 毎週月曜 7:00
Blog_ReportUpload upload_report.bat 毎週月曜 10:00

設定手順:

  1. スタートメニュー →「タスクスケジューラ」
  2. 「タスクの作成」
  3. トリガー:毎週 → 月曜日 → 指定時刻
  4. 操作:プログラムにbatファイルのフルパス、開始フォルダに blog-analytics フォルダを指定

Coworkでのレポート自動生成

Cowork(Anthropicが提供するデスクトップ向けのClaude)のスケジュールタスクを9時に設定し、latest_report_data.json とグラフを読み込んでMarkdownレポートを執筆させています。

# 週次ブログレポート 2026-03-05 〜 2026-03-11

## 📊 今週のサマリー
| 指標 | 今週 | 先週 | 増減 |
|---|---|---|---|
| セッション数 | 1,341 | 1,198 | +11.9% ▲ |
...

## 💡 今週の傾向と考察
STM32連載第3回が今週もPV全体の約70%を占めており...

## 🚀 来週に向けた提言
1. ubuntu 26.04 関連の記事を追記して検索流入をさらに伸ばす
2. 掲載順位8〜15位のクエリ「74hc595 arduino」のタイトルを改善する
...

Claudeが数値を読んで文章を書くため、定型のルールベース分析より読みやすいレポートになります。

設計上の注意点
CoworkのスケジュールタスクはLinuxサンドボックス内で動作するため、Windowsの .venv は実行できません。そのためデータ収集(Python)はWindowsタスクスケジューラ、レポート執筆はCoworkと役割を分けています。

今後の課題:ローカルPC依存をなくしたい

現状の構成には一つ課題が残っています。データ収集とDriveアップロードがWindowsローカルPC前提になっている点です。

今回 Cowork から直接 Google Drive にアップロードしようとしましたが、MCP コネクターが読み取り専用だったため断念し、Windows タスクスケジューラで代替しました。

Cowork(Claude)から Google Drive への書き込みをうまく実現できた方がいれば、ぜひコメントで教えてください!

現状(ローカルPC必須)
  Windows 7:00  → run.bat(データ収集)
  Cowork  9:00  → レポート執筆
  Windows 10:00 → upload_report.bat(Driveアップロード)

理想(クラウド完結)
  Cloud Run / GitHub Actions など
      → データ収集・グラフ生成・Drive保存
  Cowork 9:00
      → レポート執筆・Drive保存

PCの電源が入っていないとタスクが実行されない・旅行中でも動いてほしいといった場面では、データ収集部分をクラウド化するのが次のステップになります。

現在 Azure Functions での実装を検討しています。Timer Trigger で毎週月曜に起動し、GA4・Search Console からデータ取得 → グラフ生成 → Drive 保存までをクラウドで完結させる構成です。

理想(クラウド完結)
  Azure Functions(Timer Trigger・月曜 7:00)
      → データ収集・グラフ生成・JSON保存・Drive保存
  Cowork 9:00
      → レポート執筆・Drive保存

Drive アップロードも含めてクラウド側に寄せれば、ローカル環境への依存をゼロにできます。この部分は今後改善していく予定です。

おわりに

毎週のデータ確認が完全に自動化され、月曜の朝にはレポートがDriveに届くようになりました。

Coworkによるレポート執筆は「数値を見て何を言うか」を自動化できるのが大きく、単なる集計表と違って次のアクションまで示してくれます。追加APIコストなしで実現できるので、ブログ運営の効率化に試してみてください。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?