(更新: 2026.03.21)

SEO記事を自動生成するワークフローを構築した

SEO記事を自動生成するワークフローを構築した

はじめに

SEO記事を手動で書くのは時間がかかります。競合分析、キーワード選定、構成作成、本文執筆、校正…すべてを人力でやると1記事に数時間かかることも。

そこで、AIを使って競合分析から記事生成まで自動化するワークフローを構築しました。今回は「派遣の教育訓練は意味ない?」という記事を題材に、実際のフローを紹介します。

システム概要

ワークフロー(全10工程)

工程1: 競合記事取得(web_fetch)
   ↓
工程2: CSV読み込み・初期化
   ↓
工程3A: クエリ分析(検索意図)
   ↓
工程3B: 共起語抽出
   ↓
工程3C: 競合分析
   ↓
工程3.5: 人間味・感情生成
   ↓
工程4: 戦略的アウトライン
   ↓
工程5: 一次情報追加(厚労省データなど)
   ↓
工程6: アウトライン強化
   ↓
工程6.5: 構成案作成
   ↓
工程7A: 本文生成(初稿)
   ↓
工程7B: 本文ブラッシュアップ
   ↓
工程8: ファクトチェック + FAQ生成
   ↓
工程9: 最終リライト
   ↓
工程10: 最終出力(Markdown + HTML)

技術スタック

  • Python 3.11 – メインロジック
  • Claude 3.5 Sonnet – 記事生成AI
  • web_fetch – 競合記事取得
  • OpenClaw – ワークフロー管理
  • Markdown – 中間フォーマット
  • WordPress – 最終出力先

実装のポイント

1. 競合記事の自動取得

上位10サイトの本文を自動で取得:

# 工程1: 競合記事取得

import csv
import requests

def fetch_competitor_articles(keyword: str) -> list[dict]:
    """
    検索上位10サイトの記事を取得

    Returns:
        [
            {"url": "...", "title": "...", "content": "..."},
            ...
        ]
    """
    # Google検索で上位10件のURLを取得(手動 or API)
    urls = get_top_10_urls(keyword)

    articles = []
    for url in urls:
        try:
            # web_fetchで本文を抽出
            response = requests.post(
                'http://localhost:8765/api/web_fetch',
                json={'url': url, 'extractMode': 'markdown'}
            )

            content = response.json()['content']

            articles.append({
                'url': url,
                'title': extract_title(content),
                'content': content
            })
        except Exception as e:
            print(f"Failed to fetch {url}: {e}")

    return articles

def save_to_csv(articles: list[dict], output_path: str):
    """CSVに保存"""
    with open(output_path, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=['url', 'title', 'content'])
        writer.writeheader()
        writer.writerows(articles)

2. 競合分析

AIで競合記事を分析し、差別化ポイントを抽出:

# 工程3C: 競合分析

import anthropic

async def analyze_competitors(articles: list[dict]) -> dict:
    """
    競合記事を分析

    Returns:
        {
            "common_topics": ["topic1", "topic2", ...],
            "missing_topics": ["topic1", "topic2", ...],
            "unique_angles": ["angle1", "angle2", ...],
            "tone": "共感型 / 解決策提示型 / 比較検証型"
        }
    """
    client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))

    # 競合記事の要約を作成
    summaries = []
    for article in articles:
        summary = await summarize_article(client, article['content'])
        summaries.append(summary)

    # 分析プロンプト
    prompt = f"""
以下は「派遣 教育訓練 意味ない」で検索上位10サイトの要約です。

{format_summaries(summaries)}

これらを分析して、以下を抽出してください:

1. 共通トピック(すべての記事で触れられている内容)
2. 欠けているトピック(誰も触れていない視点)
3. ユニークな切り口(差別化できるアプローチ)
4. 全体のトーン(共感型 / 解決策提示型 / 比較検証型)

JSON形式で出力してください。
    """

    message = await client.messages.create(
        model='claude-3-5-sonnet-20241022',
        max_tokens=2000,
        messages=[{'role': 'user', 'content': prompt}]
    )

    return json.loads(message.content[0].text)

3. 人間味・感情の生成

機械的な文章にならないよう、感情や共感を追加:

# 工程3.5: 人間味・感情生成

async def generate_empathy(topic: str, target_audience: str) -> dict:
    """
    共感フレーズを生成

    Returns:
        {
            "opening": "あなたは悪くない。派遣の教育訓練が形骸化しているのは...",
            "transitions": ["でも、諦めないでほしい。", "実は、..."],
            "closing": "一緒に、この状況を変えていきましょう。"
        }
    """
    prompt = f"""
ターゲット: {target_audience}
トピック: {topic}

このターゲットに向けて、共感と寄り添いのフレーズを作成してください。

- 冒頭: 「あなたは悪くない」と肯定
- 中間: スムーズな遷移フレーズ
- 結び: 希望を持たせる言葉

説教臭くせず、同じ目線で語るトーンで。
    """

    # Claude APIで生成
    # ...

4. 一次情報の自動追加

厚労省などの公的データを自動で引用:

# 工程5: 一次情報追加

def add_primary_sources(outline: dict) -> dict:
    """
    一次情報を追加

    例:
        - 厚労省の統計データ
        - 行政のガイドライン
        - 裁判例
    """
    sources = {
        "派遣教育訓練": [
            {
                "url": "https://www.mhlw.go.jp/...",
                "title": "派遣労働者のキャリアアップ教育訓練(厚労省)",
                "data": "受講率: 39.7%(令和4年度調査)"
            },
            {
                "url": "https://jsite.mhlw.go.jp/...",
                "title": "キャリアアップ助成金(石川労働局)",
                "data": "助成額: 1人当たり最大47,000円"
            }
        ]
    }

    # アウトラインに挿入
    for section in outline['sections']:
        if '実態' in section['title']:
            section['sources'] = sources['派遣教育訓練']

    return outline

5. FAQ自動生成

記事から自動でFAQを作成:

# 工程8: FAQ生成

async def generate_faq(article_content: str) -> list[dict]:
    """
    記事からFAQを生成

    Returns:
        [
            {"question": "派遣の教育訓練は本当に意味がない?", "answer": "..."},
            {"question": "受講率が低い理由は?", "answer": "..."},
            ...
        ]
    """
    prompt = f"""
以下の記事から、読者が持ちそうな疑問を5つ抽出し、FAQを作成してください。

【記事】
{article_content}

【出力形式】
Q1: (質問)
A1: (回答)

Q2: (質問)
A2: (回答)

...
    """

    # Claude APIで生成
    # ...

6. WordPress用HTMLの生成

最終的にWordPress用のHTMLを出力:

# 工程10: WordPress用HTML生成

def convert_to_wordpress_html(markdown: str) -> str:
    """
    MarkdownをWordPress用HTMLに変換

    - 画像: プレースホルダー挿入
    - 脚注: 双方向リンク
    - 内部リンク: プレースホルダー
    """
    html = markdown_to_html(markdown)

    # 画像プレースホルダー
    html = re.sub(
        r'!\[([^\]]+)\]\([^\)]+\)',
        r'#IMAGE_PLACEHOLDER_\1',
        html
    )

    # 脚注
    html = add_footnote_links(html)

    # 内部リンク
    html = re.sub(
        r'\[([^\]]+)\]\(#child-article-(\d+)\)',
        r'<a href="#CHILD_ARTICLE_\2_URL">\1</a>',
        html
    )

    return html

def add_footnote_links(html: str) -> str:
    """双方向リンクの脚注を追加"""
    # [1] → <sup id="ref1"><a href="#cite-1">[1]</a></sup>
    html = re.sub(
        r'\[(\d+)\]',
        r'<sup id="ref\1"><a href="#cite-\1">[\1]</a></sup>',
        html
    )

    return html

実際の成果物

例: 「派遣の教育訓練は意味ない?」記事

  • ターゲット文字数: 4,000字
  • 実際の文字数: 4,200字
  • 生成時間: 約30分(全10工程)
  • 一次情報: 厚労省データ3件引用
  • FAQ: 5問生成
  • 内部リンク: 子記事3本へのリンク

構成

1. 導入(共感)
   - あなたは悪くない
   - 形骸化している実態

2. 派遣教育訓練の実態
   - 受講率10%以下
   - なぜ受講しないのか

3. 3つの構造的問題
   - 無関係なコンテンツ
   - キャリア関心の欠如
   - 形式的な研修

4. 解決策
   - 実践的コース
   - 多言語対応
   - 自動リマインダー

5. 事例紹介
   - CrossLearning導入事例

6. まとめ
   - 一緒に変えていこう

7. FAQ(5問)

8. 参考文献(脚注)

開発で工夫した点

1. トピッククラスター戦略

親記事(ハブ)+ 子記事3本の構成:

  • 親記事: 「派遣の教育訓練は意味ない?」(概要)
  • 子記事1: 「派遣スタッフが受講しない3つの理由」
  • 子記事2: 「教育訓練担当者の本音」
  • 子記事3: 「外部委託ソリューション」

内部リンクで相互に結びつけ、SEO効果を高める。

2. 感情スコアリング

機械的な文章を避けるため、感情スコアを測定:

def calculate_emotion_score(text: str) -> float:
    """
    感情スコア(0〜1)

    - ポジティブワード: 0.1点
    - 共感フレーズ: 0.2点
    - 一人称: 0.1点
    """
    score = 0.0

    positive_words = ['やりがい', '成長', '希望', '一緒に']
    empathy_phrases = ['あなたは悪くない', '分かります', '気持ちは理解できます']
    first_person = ['私たち', 'あなた']

    for word in positive_words:
        if word in text:
            score += 0.1

    for phrase in empathy_phrases:
        if phrase in text:
            score += 0.2

    for pronoun in first_person:
        if pronoun in text:
            score += 0.1

    return min(score, 1.0)

3. サブエージェント実行

OpenClawのサブエージェント機能で、バックグラウンド実行:

# OpenClawでサブエージェント起動

def start_subagent_workflow(keyword: str):
    """
    サブエージェントでワークフローを実行

    メインセッションを占有せず、バックグラウンドで処理
    """
    import subprocess

    subprocess.run([
        'openclaw', 'sessions_spawn',
        '--task', f'SEO記事生成ワークフロー: {keyword}',
        '--agent', 'main',
        '--cleanup', 'keep'
    ])

改善点・今後の展望

現状の課題

  • ファクトチェックが甘い
    → AIが生成した内容に誤りがある可能性

  • 画像生成は手動
    → プレースホルダーを手動で画像に置換

  • 多言語対応なし
    → 日本語のみ

今後の改善

  • ファクトチェック強化
    → 引用元URLを必須化

  • 画像自動生成
    → DALL-E / Midjourney連携

  • 多言語対応
    → 英語・中国語版を自動生成

まとめ

SEO記事の自動生成で得られた効果:

作業時間を90%削減
→ 手動5時間 → 自動30分

一次情報の引用率向上
→ 厚労省データを自動で追加

内部リンク戦略
→ トピッククラスターで相互リンク

感情・共感の表現
→ 機械的にならない工夫


開発期間: 約2週間
生成記事数: 10本以上
平均生成時間: 30分/記事

AIを使えばSEO記事の量産が可能です。ただし、ファクトチェックと人間味の追加は必須。完全自動化ではなく、「人間 + AI」の協働が理想です!