はじめに
ブログを運営していると、定期的にアクセス状況を把握することが重要です。ただ実際にやろうとすると、以下のような作業が毎週発生します。
- GA4を開いてセッション・PV・流入元を確認する
- Search Consoleを開いて検索クエリ・掲載順位・CTRを確認する
- 先週と比べてどう変化したか数字を見比べる
- 「どの記事が伸びているか」「どのクエリに伸びしろがあるか」を自分で読み解く
- 次に何をすべきか判断する
これを毎週きちんとやろうとすると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
このスクリプトが担う処理:
-
get_credentials()— OAuth認証。初回はブラウザが開き、以降はtoken.jsonで自動認証 -
fetch_ga4_data()— GA4からサマリー・人気ページ・流入元・日別データを取得(今週・先週の2週分) -
fetch_search_console_data()— SCから検索クエリ・ページ別・日別データを取得(2週分) -
save_to_excel()— openpyxlで7シート構成のExcelを生成・保存 -
generate_all_charts()— generate_charts.pyを呼び出してグラフ5枚をPNG保存 -
save_json_summary()— Coworkがレポートを書くためのJSONサマリーを出力 -
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 |
設定手順:
- スタートメニュー →「タスクスケジューラ」
- 「タスクの作成」
- トリガー:毎週 → 月曜日 → 指定時刻
- 操作:プログラムに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コストなしで実現できるので、ブログ運営の効率化に試してみてください。