「SEO対策、やらなきゃいけないのは分かってるけど...何から手をつければ?」
キーワード選定、競合調査、CTR分析。コードは書けるけどマーケは畑違いというエンジニア、少なくないですよね。
私たちもそうでした。技術ブログを書いても検索流入がほぼゼロ。ネタ選びは勘頼みでした。マーケの知識がないなら、データに聞けばいい。エンジニアらしく、SEOをコードで解くことにしました。
この記事で学べること
- GA4 → Trends → SEO Content Planner の3段パイプライン設計
- 各ステージのデータ変換ロジック
- SEOスコアリング(理論最大130点)の5要素と計算式
- seo-strategyのクラスタ設計とファネル分類
- blog-seo-improve / blog-schedule-overview の改善・運用サイクル
前提知識
- Claude Code Skillsの基本概念を把握している
- Pythonの読み書きができる
- GA4・GSC の基本用語がわかる
紹介するスキルはGitHubリポジトリで公開しています。
全体像: 3段パイプライン
まず全体のデータフローを俯瞰します。
ga-analyzer ──→ trends-analyzer ──→ seo-content-planner
│ │ │
▼ ▼ ▼
GA4 JSON Trends JSON content-strategy.json
(トラフィック (キーワード (SEOスコア付き
実績データ) スコアリング) 記事ネタ提案)
│
┌──────────────┼──────────────┐
▼ ▼ ▼
seo-strategy blog-seo-improve blog-schedule-
(クラスタ設計 (CTR/bounce overview
ファネル分類) 自動改善) (空きスロット検出)
各スキルは独立したPythonスクリプトとJSONインターフェースで接続されています。UNIXパイプの思想そのものです。
ステージ1: ga-analyzer — 「今、何が起きている?」を数値化する
OAuth 2.0でGA4 Data APIに接続
ga-analyzerの核はga_fetch.py。OAuth 2.0でGA4 Data APIからデータを取得します。
python scripts/ga_fetch.py \
--property-id 123456789 \
--oauth-client ~/.config/ga4/client_secret.json \
--output ga_report.json
個人利用ならOAuth推奨です。
出力される4つの分析軸
4つのセクションを含みます。
| セクション | 中身 | SEO的な意味 |
|---|---|---|
| traffic | チャネル別セッション数、デバイス比率 | Organic Search比率30%以上が健全ライン |
| content | ページ別PV、エンゲージメント率 | 人気記事=読者の関心が実証済みの領域 |
| conversion | ランディングページ × CV | 高PV×低CV=最優先改善対象 |
| device | モバイル/PC別の直帰率差 | ギャップ15pt超は深刻(モバイル最適化必須) |
GAデータ単体ではSEOの「次の一手」は見えません。外部のトレンドデータとの掛け合わせで初めて見えてきます。
ページタイトルからキーワードを自動抽出
ページタイトルからSEOキーワードを自動抽出する処理が走ります。
# 一般的すぎるタイトルを除外
IGNORE_TITLES = {"ブログ", "トップページ", "ホーム", "home", "blog", "404"}
# 【】内のタグをキーワードとして抽出
STRIP_PATTERNS = [
r"^【(.+?)】", # 【Claude Code】→ "Claude Code"
r"^\d+:\s*",
r"^404:.*$",
]
「ブログ」等はノイズとして捨て、【】の中身はキーワードとして残す前処理です。
ステージ2: trends-analyzer — 「世の中は今、何に興味がある?」を測る
pytrendsでGoogle Trendsを叩く
trends-analyzerはpytrendsでトレンドデータを取得します。
# GAレポートからキーワードを自動抽出してTrendsを取得
python scripts/trends_fetch.py \
--ga-report ga_report.json \
--output trends_report.json
# 手動でキーワードを指定することも可能
python scripts/trends_fetch.py \
--keywords "Claude Code,AI開発,LLM活用" \
--geo JP \
--output trends_report.json
非公式APIなのでレート制限との戦いが地味につらく、exponential backoff(20秒→40秒→80秒)とキャッシュ(24時間)で対処しています。10キーワードなら2バッチ×5秒待ちです。
トレンドスコアの3つの軸
各キーワードには3指標が付きます。
| 指標 | 意味 | SEOアクション |
|---|---|---|
| avg_interest(0-100) | 検索関心度の平均 | 20-50がスイートスポット(競合少×需要あり) |
| trend_direction | rising/stable/declining | risingなら今すぐ書く。decliningは見送り |
| rising_queries | 上昇中の関連クエリ | 記事の切り口やロングテールKWの宝庫 |
trend_direction判定: 直近1/4期間の平均 vs 全体平均
→ +15%以上 = rising
→ ±15%以内 = stable
→ -15%以下 = declining
「15%」は感覚で決めた閾値ですが、3ヶ月の運用で妥当でした。
ステージ3: seo-content-planner — 「次に何を書くべきか」をスコアで出す
GA4実績 × Trends × GSC × 戦略データを統合し1つのSEOスコアにまとめます。
SEOスコアリング: 理論最大130点の内訳
Trend(0-40) + ContentGap(0-30) + GA(0-30) + FunnelBonus(0-15) + GSC(0-15) = 最大130点
1. Trend Score(0-40点): トレンドの風に乗る
direction_multiplier = {
"rising": 1.5, # 上昇中 → 1.5倍ブースト
"stable": 1.0, # 安定 → そのまま
"declining": 0.5, # 下降中 → 半減
}
trend_score = min(40, (avg_interest / 100) * 40 * direction_multiplier)
interest 60 × rising = 60/100×40×1.5=36点。declining なら60/100×40×0.5=12点。方向で3倍の差がつきます。
2. Content Gap Score(0-30点): まだ誰も書いていない領域
content_gap_score = min(30, rising_count * 4 + top_count * 2)
rising_queriesが多い = 満足な記事がないのに需要が伸びているシグナルです。上昇クエリ5件なら5 × 4 = 20点。
3. GA Performance Score(0-30点): 実績がある領域に上乗せ
ga_score = min(30, (views / 10) + (engagement_rate * 20))
PVがある記事の関連テーマは「関心が実証済み」。PV 200 × エンゲージメント率 60% = 200/10 + 0.6 × 20 = 32 → cap 30点。人気記事の続編は外しにくいという反映です。
4. Funnel Gap Bonus(0-15点): ファネルの穴を埋める
「認知→興味→検討→行動」の偏りを見て不足ファネルに加点する仕組みです。
| ファネル | 目標比率 | 最大ボーナス |
|---|---|---|
| 認知(やり方、入門) | 40% | +5点 |
| 興味(比較、おすすめ) | 20% | +5点 |
| 検討(事例、導入、費用) | 30% | +15点 |
| 行動(見積、依頼、相談) | 10% | +10点 |
検討層のボーナスが高いのは、技術ブログが認知記事に偏り検討層が慢性的に不足するためです。
不足率 = max(0, 目標比率 - 現状比率)
ボーナス = 不足率 / 目標比率 × 最大ボーナス
検討層が現状24%(目標30%、不足6%)なら6/30 × 15 = +3点。
5. GSC Position Bonus(0-15点): 検索順位の「惜しい」を拾う
if position <= 3:
position_score = 15.0 # トップ3は満点
elif position <= 10:
position_score = 15.0 * (10 - position) / 7 # 4-10位は線形減衰
elif position <= 20:
position_score = 5.0 * (20 - position) / 10 # 11-20位もワンチャン
else:
position_score = 0.0
# インプレッション重みで実ボリュームを加味
final = position_score * min(1.0, impressions / 500)
8位・imp300なら15 × (10-8)/7 × 300/500 = 2.6点。クリック実績があれば×1.1のブーストがかかります。
キーワード→ファネルの自動マッピング
# 行動(最も意図が強い)
action_patterns = ["見積", "依頼", "相談", "申込", "問い合わせ", "無料"]
# 検討
consideration_patterns = ["事例", "導入", "費用", "料金", "メリット", "デメリット"]
# 興味
interest_patterns = ["比較", "vs", "おすすめ", "ランキング", "選び方"]
# 認知(デフォルト)
awareness_patterns = ["やり方", "実装", "tutorial", "入門", "とは", "使い方"]
パターンマッチなので精度は完璧ではありませんが、8割方当たります。残りは手動修正できます。
seo-strategy — クラスタ設計でコンテンツを「面」にする
seo-content-plannerが「次の1記事」を提案するのに対し、seo-strategyはサイト全体の戦略を設計します。
GSCクエリのクラスタリング
中核機能は、GSCの検索クエリを意味的にグルーピングすることです。
{
"cluster_keywords": {
"Claude Code": ["claude code", "claude-code", "claude settings", "claude md"],
"シフト管理": ["シフト", "shift", "勤怠"],
"AI開発": ["ai ", "llm", "gemini", "gpt"],
"Web開発": ["next.js", "react", "typescript", "tailwind"]
}
}
キーワード→クラスタのマッピングを定義し自動分類します。未分類の高インプレッションクエリは「その他」に集約され、そこから新クラスタ候補を自動提案します。
クラスタ自動提案のアルゴリズム
1. impressions ≥ 50 の未分類クエリを抽出
2. 空白・ハイフン・アンダースコアでトークン分割
3. ストップワード除外(「の」「は」「を」「the」「is」等)
4. 共通トークンごとにクエリをグループ化(2件以上で候補)
5. total_impressions 降順ソート
6. 上位グループに含まれるクエリは下位から除外(重複除去)
7. 上位5件を出力
空っぽのcluster_keywordsから始め、自動提案を確認→追加を繰り返してクラスタを育てます。
Issue自動検出
記事の問題を閾値ベースで検出します。
| Issue | 条件 | 意味 |
|---|---|---|
low_ctr_high_imp | imp ≥ 100 かつ CTR ≤ 3% | 表示されてるのにクリックされない |
high_bounce | bounce率 ≥ 65% | 来たけどすぐ帰る |
low_engagement | エンゲージメント率 ≤ 35% | 読まれてるけど響いてない |
position_opportunity | 8位 ≤ 順位 ≤ 20位 | もうちょっとで1ページ目 |
zero_click | imp ≥ 50 かつ clicks = 0 | 表示はあるのにクリックゼロ(深刻) |
zero_impressions | 公開30日以上 かつ imp = 0 | Googleに認知すらされていない |
zero_clickとzero_impressionsは要注意。前者はタイトルと検索意図のずれ、後者はインデックスの問題の可能性があります。
blog-seo-improve — CTRとbounce率をデータで直す
seo-strategyが特定し、blog-seo-improveで直します。
/blog-seo-improve --type ctr # impressions ≥ 100 かつ CTR < 3% を検出
/blog-seo-improve --type bounce # bounceRate > 75% を検出
/blog-seo-improve --dry-run # ファイルは変更せず提案のみレポート出力
CTR改善は3点で提案します。キーワード前置(重要語をタイトル先頭に)・数字活用(「5つの方法」「80%削減」等)・クラスタ整合(同じクラスタ内の記事と一貫したキーワード戦略)です。
Bounce率改善は冒頭セクションの結論先出しと見出し構成のスキャナビリティ向上を提案します。読者は3秒で判断するため、技術記事にありがちな「前提知識から入って肝心の答えが中盤以降に埋もれる」構成は離脱を招きます。
--dry-runで提案のみ出力でき、本番の記事を壊す恐怖なしに確認できます。
blog-schedule-overview — カレンダーで「空き」を見つける
記事の公開スケジュールを可視化し、空きスロットを自動検出するスキルです。
/blog-schedule-overview --check --format calendar
出力イメージ:
Blog Schedule: 2026-04-07 - 2026-05-07
Week 15 (Apr 6-12)
Mon 04/06 ✅ nextjs-mdx-blog-system
Thu 04/09 ✅ playpark-lab-ui-components
Week 16 (Apr 13-19)
Mon 04/13 ⬜ [empty slot]
Thu 04/16 ⬜ [empty slot]
Week 17 (Apr 20-26)
Mon 04/20 ✅ rust-fullstack-zero-cost-deploy
Thu 04/23 ⬜ [empty slot]
Issues:
- 04/13 (Mon): empty slot (seed candidates: 2)
- 04/16 (Thu): empty slot
- 04/23 (Thu): empty slot
- content-strategy.json: 3 articles unscheduled
公開曜日を定義し、MDXのfrontmatter日付を読み取ってカレンダーに配置します。
次に何を書けばいいかはseo-content-plannerのスコアが教えてくれるし、空きスロットに合わせたスケジューリングもできる。データが段取りを整えてくれます。
6つのスキルの協調: 日常のワークフロー
実際の運用では、こんな流れで使っています。
月初(戦略更新):
/seo-strategy --refresh
→ GA4/GSC/Trends を再取得
→ クラスタ分析・Issue検出
→ seo-strategy.json 更新
週次(記事企画):
/seo-content-planner
→ SEOスコア付き記事ネタリスト
→ 上位候補からネタを選定
/blog-schedule-overview --check
→ 空きスロット確認
→ 選定したネタをスケジュール
随時(既存記事改善):
/blog-seo-improve --type ctr --dry-run
→ CTRが低い記事の改善案を確認
→ 納得したら --dry-run なしで適用
各スキルが独立しているので、必要なときに必要なものだけ実行できます。月初30分・週次10分で回せます。
設計で意識したこと
- UNIXパイプ原則: 各スキルは単一の責務だけを持ちます(データ取得・トレンド分析・スコアリング)。テストもga-analyzerなら「正しいJSONが出力されるか」だけ見ればよく、下流を気にする必要がありません
- JSONインターフェースの契約: スキル間の受け渡しはすべてJSONファイル。途中でスキルを差し替えたり、手動でデータを書き換えたりが自由にできます
- 段階的なデータ充実: GA4なし・GSCなし・Trendsだけでも動きます。すべてのデータソースがオプショナルで、揃えるほど精度が上がる設計です
注意点・Tips
- pytrendsのレート制限: 短時間に叩きすぎるとIPブロックされます。キャッシュ(24時間)を有効にしておくのが吉
- スコアは相対値: SEOスコア80点でも「絶対に当たる」わけではない。あくまで手持ちのネタの中での優先順位
- GA4の反映ラグ: GA4 Data APIのデータは24-48時間遅れます。焦らず1週間待ちましょう
- クラスタは育てるもの: 3-5キーワードから始めて、自動提案を見ながら追加していくのが現実的
まとめ
「SEOはマーケの仕事」と思っていたけど、実態はデータの変換と重み付けです。
3段パイプラインは、GA4実績とGoogle Trendsの需要データを掛け合わせて「次に書くべき記事」をスコアで教えてくれます。加えてseo-strategyがクラスタ設計で「面」を作ります。blog-seo-improveがCTR/bounce率を改善し、blog-schedule-overviewが空きスロットを検出します。
マーケの感覚が0点でも、データが130点満点で採点してくれます。



