メインコンテンツへスキップ
← 記事一覧に戻る

【コピペで動く】GA4 + Search ConsoleのデータをTypeScriptで自動取得する方法

6 min readAI開発ツール
#Google Analytics#Search Console#TypeScript#GitHub Actions#SEO

GA4やSearch ConsoleのCSVを毎回手動でエクスポートしていませんか?

結論: TypeScript 2ファイル + GitHub Actionsで週1完全自動化できます。 Service Accountの設定に15分、コードはコピペで動きます。

💡

の体験談

筆者はAIエージェント11体を運用しており、SEO分析もエージェントに任せています。エージェントがデータを分析するには、CSVが自動で更新されている必要がありました。この仕組みで手動作業がゼロになりました。


全体像

GitHub Actions(毎週月曜自動実行)
  ├── search_console_tracker.ts → search-console-latest.csv
  └── ga4_tracker.ts            → ga4-pages-latest.csv
                                  ga4-acquisition-latest.csv

取得するデータ:

  • Search Console: 検索クエリ別のクリック数・表示回数・CTR・掲載順位(過去28日)
  • GA4: ページ別PV・滞在時間・直帰率 + 流入元別セッション数(過去28日)

事前準備: Service Account設定(15分)

1. Google Cloudプロジェクト作成

Google Cloud Console にアクセスし、新しいプロジェクトを作成します。

2. APIを有効化

「APIとサービス」→「ライブラリ」で以下を有効化:

  • Google Search Console API
  • Google Analytics Data API

3. Service Account作成

「認証情報」→「+認証情報を作成」→「サービスアカウント」

サービスアカウント名は任意(例: analytics-reader)。作成後、メールアドレスをコピーしておきます。

4. JSONキーを発行

サービスアカウントの「キー」タブ→「鍵を追加」→「JSON」。ダウンロードされたJSONが認証キーです。

5. アクセス権を付与

Search Console: 設定→ユーザーと権限→サービスアカウントを「制限付き」で追加 GA4: 管理→プロパティのアクセス管理→サービスアカウントを「閲覧者」で追加


実装①: Search Console Tracker

// search_console_tracker.ts
import { google } from "googleapis";
import { writeFileSync, mkdirSync, existsSync } from "fs";

const SERVICE_ACCOUNT_JSON = process.env.GOOGLE_SERVICE_ACCOUNT_JSON ?? "";
const SITE_URL = process.env.SEARCH_CONSOLE_SITE_URL ?? "https://masatoman.net";
const OUTPUT_DIR = process.env.ANALYTICS_OUTPUT_DIR || "./analytics";
const OUTPUT_FILE = `${OUTPUT_DIR}/search-console-latest.csv`;

function getClient() {
  const credentials = JSON.parse(SERVICE_ACCOUNT_JSON);
  const auth = new google.auth.GoogleAuth({
    credentials,
    scopes: ["https://www.googleapis.com/auth/webmasters.readonly"],
  });
  return google.searchconsole({ version: "v1", auth });
}

async function main() {
  const client = getClient();

  const now = new Date();
  const endDate = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000);
  const startDate = new Date(endDate.getTime() - 28 * 24 * 60 * 60 * 1000);
  const fmt = (d: Date) => d.toISOString().split("T")[0];

  const res = await client.searchanalytics.query({
    siteUrl: SITE_URL,
    requestBody: {
      startDate: fmt(startDate),
      endDate: fmt(endDate),
      dimensions: ["query"],
      rowLimit: 500,
    },
  });

  const rows = res.data.rows ?? [];

  if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true });

  const header = "Query,Clicks,Impressions,CTR,Position";
  const lines = rows.map((row) => {
    const query = row.keys?.[0] ?? "";
    return `"${query.replace(/"/g, '""')}",${row.clicks},${row.impressions},${((row.ctr ?? 0) * 100).toFixed(2)}%,${(row.position ?? 0).toFixed(1)}`;
  });

  writeFileSync(OUTPUT_FILE, [header, ...lines].join("\n"), "utf-8");
  console.log(`Saved ${rows.length} queries to ${OUTPUT_FILE}`);
}

main();

実装②: GA4 Tracker

// ga4_tracker.ts
import { google } from "googleapis";
import { writeFileSync, mkdirSync, existsSync } from "fs";

const SERVICE_ACCOUNT_JSON = process.env.GOOGLE_SERVICE_ACCOUNT_JSON ?? "";
const PROPERTY_ID = process.env.GA4_PROPERTY_ID ?? "";
const OUTPUT_DIR = process.env.ANALYTICS_OUTPUT_DIR || "./analytics";

function getClient() {
  const credentials = JSON.parse(SERVICE_ACCOUNT_JSON);
  const auth = new google.auth.GoogleAuth({
    credentials,
    scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
  });
  return google.analyticsdata({ version: "v1beta", auth });
}

async function main() {
  const client = getClient();

  // ページ別レポート
  const pagesRes = await client.properties.runReport({
    property: `properties/${PROPERTY_ID}`,
    requestBody: {
      dateRanges: [{ startDate: "28daysAgo", endDate: "yesterday" }],
      dimensions: [{ name: "pagePath" }],
      metrics: [
        { name: "screenPageViews" },
        { name: "averageSessionDuration" },
        { name: "bounceRate" },
      ],
      orderBys: [{ metric: { metricName: "screenPageViews" }, desc: true }],
      limit: 100,
    },
  });

  if (!existsSync(OUTPUT_DIR)) mkdirSync(OUTPUT_DIR, { recursive: true });

  const pagesRows = pagesRes.data.rows ?? [];
  const pagesHeader = "Page path,Views,Avg engagement time (sec),Bounce rate";
  const pagesLines = pagesRows.map((row) => {
    const path = row.dimensionValues?.[0]?.value ?? "";
    const views = row.metricValues?.[0]?.value ?? "0";
    const avgTime = parseFloat(row.metricValues?.[1]?.value ?? "0").toFixed(1);
    const bounce = (parseFloat(row.metricValues?.[2]?.value ?? "0") * 100).toFixed(1);
    return `"${path}",${views},${avgTime},${bounce}%`;
  });
  writeFileSync(`${OUTPUT_DIR}/ga4-pages-latest.csv`, [pagesHeader, ...pagesLines].join("\n"), "utf-8");

  // 集客レポート
  const acqRes = await client.properties.runReport({
    property: `properties/${PROPERTY_ID}`,
    requestBody: {
      dateRanges: [{ startDate: "28daysAgo", endDate: "yesterday" }],
      dimensions: [{ name: "sessionSourceMedium" }],
      metrics: [{ name: "sessions" }, { name: "conversions" }],
      orderBys: [{ metric: { metricName: "sessions" }, desc: true }],
      limit: 50,
    },
  });

  const acqRows = acqRes.data.rows ?? [];
  const acqHeader = "Source / Medium,Sessions,Conversions";
  const acqLines = acqRows.map((row) => {
    const source = row.dimensionValues?.[0]?.value ?? "";
    return `"${source}",${row.metricValues?.[0]?.value ?? 0},${row.metricValues?.[1]?.value ?? 0}`;
  });
  writeFileSync(`${OUTPUT_DIR}/ga4-acquisition-latest.csv`, [acqHeader, ...acqLines].join("\n"), "utf-8");

  console.log(`Pages: ${pagesRows.length} rows, Acquisition: ${acqRows.length} rows`);
}

main();

自動化: GitHub Actions

# .github/workflows/analytics-tracker.yml
name: Analytics Tracker

on:
  schedule:
    - cron: '0 1 * * 1'  # 毎週月曜 10:00 JST
  workflow_dispatch:

permissions:
  contents: write

jobs:
  track:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm ci || npm install
      - run: mkdir -p analytics-output

      - name: Search Console
        env:
          GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}
          SEARCH_CONSOLE_SITE_URL: https://masatoman.net
          ANALYTICS_OUTPUT_DIR: ./analytics-output
        run: npx tsx search_console_tracker.ts

      - name: GA4
        env:
          GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}
          GA4_PROPERTY_ID: ${{ secrets.GA4_PROPERTY_ID }}
          ANALYTICS_OUTPUT_DIR: ./analytics-output
        run: npx tsx ga4_tracker.ts

      - name: Commit CSVs
        run: |
          git config user.name "analytics-bot"
          git config user.email "bot@example.com"
          git add analytics-output/*.csv
          git diff --cached --quiet || git commit -m "data: update analytics CSVs"
          git push

必要なGitHub Secrets:

  • GOOGLE_SERVICE_ACCOUNT_JSON — Service AccountのJSON全文
  • GA4_PROPERTY_ID — GA4プロパティID(数字のみ)

応用: AIにSEO分析させる

CSVが自動更新されるので、Claude Codeに以下のように指示するだけで分析できます:

analytics/search-console-latest.csv を読んで、クリック数は多いが掲載順位が10位以下のキーワードをリストアップして。それらを元に、次に書くべき記事のタイトル案を3つ出して。

analytics/ga4-pages-latest.csv を読んで、PVは高いが滞在時間が短い記事を特定して。その記事の構成改善案を出して。


まとめ

ステップ所要時間
Service Account設定15分(初回のみ)
コードをコピペ2分
GitHub Actionsで自動実行0分(毎週自動)

手動エクスポートは今日で卒業です。

← 記事一覧に戻る