「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・Google Search Console の基本用語がわかる
この記事で紹介するスキルはすべて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インターフェースで接続されていること。ga-analyzerの出力JSONをtrends-analyzerがそのまま食べる。trends-analyzerの出力をseo-content-plannerが食べる。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
Service Account認証も対応していますが、個人・チーム利用ならOAuth推奨です(ブラウザでポチッと認証するだけ。JSONキーの管理地獄から解放されます)。
出力される4つの分析軸
ga_report.jsonは4つのセクションを含みます。
| セクション | 中身 | SEO的な意味 |
|---|---|---|
| traffic | チャネル別セッション数、デバイス比率 | Organic Search比率30%以上が健全ライン |
| content | ページ別PV、エンゲージメント率 | 人気記事=読者の関心が実証済みの領域 |
| conversion | ランディングページ × CV | 高PV×低CV=最優先改善対象 |
| device | モバイル/PC別の直帰率差 | ギャップ15pt超は深刻(モバイル最適化必須) |
ここで大事なのは、GAデータ単体ではSEOの「次の一手」は見えないということ。GAが教えてくれるのは「今どうなっているか」だけ(健康診断で「太ってますね」と言われても、痩せ方は教えてくれないのと同じ)。「次に何を書けばいいか」は、外部のトレンドデータとの掛け合わせで初めて見えてきます。
ページタイトルからキーワードを自動抽出
ga-analyzerの出力がtrends-analyzerに渡るとき、面白い変換が起きます。ページタイトルからSEOキーワードを自動抽出する処理です。
# 一般的すぎるタイトルを除外
IGNORE_TITLES = {"ブログ", "トップページ", "ホーム", "home", "blog", "404"}
# 【】内のタグをキーワードとして抽出
STRIP_PATTERNS = [
r"^【(.+?)】", # 【Claude Code】→ "Claude Code"
r"^\d+:\s*",
r"^404:.*$",
]
「ブログ」や「404」はノイズだから捨てる。【】の中身はキーワードとして残す。地味だけど、この前処理の精度がパイプライン全体の品質を左右します(ゴミを入れたらゴミが出てくる、GIGO問題ですね)。
ステージ2: trends-analyzer — 「世の中は今、何に興味がある?」を測る
pytrendsでGoogle Trendsを叩く
trends-analyzerはpytrends(Google Trends非公式API)でトレンドデータを取得します。
# 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時間有効)を実装しています。pytrendsは1リクエスト最大5キーワードなので、10キーワードなら2バッチ×5秒の待機時間。Googlebot並みにのんびり待ちましょう。
トレンドスコアの3つの軸
trends_report.jsonの各キーワードには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データ × 戦略データの4つのデータソースを統合して、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)
avg_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点 |
検討層の最大ボーナスが15点と高いのは意図的。技術ブログは「認知」記事に偏りがちで(「〇〇の使い方」ばかり書いてしまう)、お問い合わせにつながる検討層の記事が慢性的に不足するから。このボーナスがないと、永遠に入門記事ばかり提案されてしまいます(経験者は語る)。
計算式はこうです。
不足率 = 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位でインプレッション300なら15 × (10-8)/7 × 300/500 = 2.6点。さらにGSCでクリック実績がある場合、Trend Scoreに×1.1の信頼度ブーストがかかります。Trendsの「なんとなくの人気度」がGSCの実データで裏付けられた、という意味合いです。
キーワード→ファネルの自動マッピング
# 行動(最も意図が強い)
action_patterns = ["見積", "依頼", "相談", "申込", "問い合わせ", "無料"]
# 検討
consideration_patterns = ["事例", "導入", "費用", "料金", "メリット", "デメリット"]
# 興味
interest_patterns = ["比較", "vs", "おすすめ", "ランキング", "選び方"]
# 認知(デフォルト)
awareness_patterns = ["やり方", "実装", "tutorial", "入門", "とは", "使い方"]
パターンマッチで分類しているので精度は完璧ではありません(AIが苦手な分類を正規表現が頑張っている、という逆転現象)。でも「見積」が含まれるキーワードは行動ファネル、「比較」は興味ファネルというヒューリスティックは、8割方当たる。残り2割はseo-strategy側で手動オーバーライドできるようにしてあります。
seo-strategy — クラスタ設計でコンテンツを「面」にする
seo-content-plannerが「次の1記事」を提案するのに対して、seo-strategyはサイト全体のコンテンツ戦略を設計します。
GSCクエリのクラスタリング
strategy_analyzer.pyの中核機能が、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"]
}
}
設定ファイルでキーワード→クラスタのマッピングを定義し、GSCクエリを自動分類します。未分類の高インプレッションクエリは「その他」クラスタに集約され(「その他」という名前がすでに敗北感ありますが)、さらにそこから新クラスタ候補を自動提案するアルゴリズムが走ります。
クラスタ自動提案のアルゴリズム
1. impressions ≥ 50 の未分類クエリを抽出
2. 空白・ハイフン・アンダースコアでトークン分割
3. ストップワード除外(「の」「は」「を」「the」「is」等)
4. 共通トークンごとにクエリをグループ化(2件以上で候補)
5. total_impressions 降順ソート
6. 上位グループに含まれるクエリは下位から除外(重複除去)
7. 上位5件を出力
クラスタリングは手動で正解を教える半教師あり学習のようなもの。最初は空っぽのcluster_keywordsから始めて、自動提案を確認→良さそうなものを設定に追加、を繰り返すことでクラスタが育っていきます。
Issue自動検出
strategy_analyzer.pyは記事ごとの「問題」を閾値ベースで自動検出します。
| 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は地味に恐ろしいシグナル。50回以上表示されてクリックがゼロならタイトルとdescriptionが検索意図と完全にずれている可能性大。公開30日でインプレッションゼロならインデックス自体に問題があるかもしれない。どちらもblog-seo-improveの出番です。
blog-seo-improve — CTRとbounce率をデータで直す
seo-strategyが「何が問題か」を特定し、blog-seo-improveが「どう直すか」を実行します。
CTR改善モード
/blog-seo-improve --type ctr
impressions ≥ 100 かつ CTR < 3% の記事を自動検出し、titleとmeta descriptionを改善提案します。
改善のロジックはシンプル。
- キーワード前置: 重要なキーワードをタイトルの先頭に
- 数字活用: 「5つの方法」「80%削減」など具体性を持たせる
- クラスタキーワードとの整合: seo-strategy.jsonのクラスタ情報を参照して、同じクラスタ内の記事と一貫したキーワード戦略を維持
Bounce率改善モード
/blog-seo-improve --type bounce
bounceRate > 75% の記事に対して、冒頭セクションの結論先出しと見出し構成のスキャナビリティ向上を提案。
読者は3秒で「この記事が自分の求めている情報か」を判断する(Instagramのストーリーより短い)。冒頭に結論がないと離脱する。当たり前だけど、技術記事だと「前提知識の説明」から入りがちで、肝心の答えが中盤以降に埋もれているパターンが多い(自分の記事を振り返って「うっ」となりました)。
dry-runで安心
/blog-seo-improve --dry-run
--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
publish_daysで公開曜日を定義し(デフォルトは月・木)、MDXのfrontmatter日付を読み取ってカレンダーに配置。seed/ディレクトリの作成中記事やSNS投稿スケジュールとの照合も行います。
「空きスロットが3つ、seed候補が2つ」。次に何を書けばいいかは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パイプ原則: 1スキル1役割
ga-analyzerは「データ取得」だけ。trends-analyzerは「トレンド分析」だけ。seo-content-plannerは「スコアリング」だけ。1つのスキルが複数の責務を持たないようにしています。
これはテスタビリティにも直結する。ga-analyzerのテストは「正しいJSONが出力されるか」だけ見ればいい。下流のスキルを気にする必要がない。
JSONインターフェースの契約
スキル間の受け渡しはすべてJSONファイル。構造が決まっているので、途中でスキルを差し替えたり、手動でデータを書き換えたりが自由にできる。trends-analyzerがメンテ中でも、手書きのtrends_report.jsonを用意すればパイプラインは回る。
段階的なデータ充実
GA4データなし、GSCデータなし、Trendsだけ。それでも動く。すべてのデータソースがオプショナルで、あるものだけ使ってスコアリングする設計にしています。
最初はTrendsだけで始めて、GA4を設定したら精度が上がり、GSCを追加したらさらに上がる。一度に全部揃えなくても始められることが、実際に使い続けるための大事なポイントでした。
注意点・Tips
- pytrendsのレート制限に注意: 短時間に叩きすぎるとIPブロックされます。キャッシュ(24時間)を有効にしておくのが吉。「なぜかTrendsだけ動かない」の原因は大抵これ
- スコアは相対値: SEOスコア80点の記事ネタが「絶対に当たる」わけではない。あくまで手持ちのネタの中での優先順位。過信は禁物(スコアが高くても書き手のモチベが低いと質が落ちる。人間だもの)
- GA4の反映ラグ: GA4 Data APIのデータは24-48時間遅れ。「今日書いた記事の効果を今日見る」は無理。焦らず1週間待ちましょう
- クラスタは育てるもの: 最初から完璧なクラスタ定義を目指すと永遠に始まらない。3-5キーワードから始めて、自動提案を見ながら追加していくのが現実的
まとめ
「SEOはマーケの仕事」と思っていたけど、実態はデータの変換と重み付け。エンジニアが得意なやつです。
ga-analyzer → trends-analyzer → seo-content-plannerの3段パイプラインは、GA4の実績データとGoogle Trendsの需要データを掛け合わせて、「次に書くべき記事」をスコアで教えてくれる。seo-strategyがクラスタ設計でコンテンツの「面」を作り、blog-seo-improveが既存記事のCTR/bounce率を数値で改善し、blog-schedule-overviewが空きスロットを検出する。
マーケの感覚が0点でも、データが130点満点で採点してくれる。SEOの「何から手をつければ」問題、コードで解けました。



