皆さん、こんにちは。Futuristic Imagination LLC 代表の佐藤琢也です。
Webサイトやメディアを運営しているエンジニアなら誰しも「もっとSEOを自動化できないものか?」と考えたことがあるのではないでしょうか。
特に、Google Search Console(GSC)で「検索順位が低い」「表示回数が多いのにクリック率が低い」といった記事を見つけるたびに、「これを手動でリライトして、Googleに再インデックスを促す作業、もう嫌だ!」と消耗していませんか?
私自身、AIオウンドメディア18サイト(9言語展開・年間6,570記事)を一人で運用する中で、「同じ手作業を二度繰り返さない」という執念から、GSCとGemini APIを連携させて低ランク記事の自動リライト・自動インデックス促進パイプラインを構築しました。
今回は、この**「AIエージェントにSEOを丸投げして寝てたら順位が上がった話」**について、具体的な実装と哲学をQiitaで共有したいと思います。
この記事を読めば、以下のことがわかります。
- Google Search Console APIで低順位記事を自動検出する方法
- 検出した記事のコンテンツをGemini APIで自動リライトするプロンプト設計
- リライト後の記事を高速でGoogleにインデックスさせるIndexNowの活用術
- Next.js、TypeScript、Vercel Cronを組み合わせた、堅牢かつスケーラブルな自動化アーキテクチャ
- AI時代のSEO戦略における「仕組み化」と「ドッグフーディング」の重要性
なぜこの仕組みを作ったのか? 「超・爆速の課題解決と実行力」が原点
私がWebサービス開発において最も重視しているのは、**「圧倒的な開発・検証スピード」と「徹底した自動化・省力化(仕組み化)」**です。
毎日、多数の記事を生成・運用していると、必ず「なぜか順位が低い記事」「インデックスされていない記事」が出てきます。これらを一つ一つ手動で分析し、リライトし、GSCからインデックス登録をリクエストする……想像するだけでゾッとしますよね。
「なんかこれって自動化できないかな?」
この問いから、GSC APIとLLMを連携させた「低順位記事の自動リライト&送信パイプライン」の開発に着手しました。弊社自身が使って効果を実感できないものは、顧客には刺さりません。「ドッグフーディング(自社実証)」ファーストの信念です。
Google Search Console APIで低順位記事を検出する
まずは、GSC APIを使って対象サイトの検索クエリデータを取得し、低順位の記事を特定するところから始めます。
1. Google Search Console APIのセットアップ
Google Cloud Platformでプロジェクトを作成し、Google Search Console APIを有効にします。認証にはOAuth 2.0かサービスアカウントを利用しますが、今回はVercel Cronなどでバックエンドから実行するため、サービスアカウントを使用します。
サービスアカウントキー(JSONファイル)をダウンロードし、環境変数に設定しておきます。
2. クエリデータの取得
GSC APIは、searchanalytics.query メソッドを使って指定した期間の検索パフォーマンスデータを取得できます。
// src/lib/gsc.ts
import { google } from 'googleapis';
import { JWT } from 'google-auth-library';
interface SearchDataRow {
keys: string[]; // URL, query
clicks: number;
impressions: number;
ctr: number;
position: number;
}
const getGscClient = async () => {
const auth = new JWT({
email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
key: process.env.GOOGLE_PRIVATE_KEY?.replace(/\\n/g, '\n'), // newline character handling
scopes: ['https://www.googleapis.com/auth/webmasters.readonly'],
});
await auth.authorize();
return google.webmasters({ version: 'v3', auth });
};
export const fetchLowRankArticles = async (siteUrl: string, startDate: string, endDate: string): Promise<SearchDataRow[]> => {
const webmasters = await getGscClient();
// 過去30日間のデータを取得する例
const response = await webmasters.searchanalytics.query({
siteUrl: siteUrl,
requestBody: {
startDate: startDate,
endDate: endDate,
dimensions: ['page', 'query'], // ページURLとクエリで集計
rowLimit: 1000, // 取得件数
// filters: [
// {
// dimension: 'page',
// operator: 'notContains',
// expression: '/tags/' // 特定のURLを除外する例
// }
// ]
},
});
const rows = response.data.rows || [];
// ここで低順位の基準を設定
// 例: 平均掲載順位が10位以下で、かつ表示回数が一定数以上(例: 100回以上)の記事
return rows.filter(row => row.position > 10 && row.impressions >= 100);
};
// 実際の呼び出し例
// const lowRankArticles = await fetchLowRankArticles('https://www.futuristicimagination.co.jp/', '2023-10-01', '2023-10-31');
// console.log(lowRankArticles);
このコードでは、指定した期間のサイトの検索パフォーマンスデータをページURLとクエリごとに取得し、平均掲載順位が10位以下、かつ表示回数が100回以上の記事を「低ランク記事」としてフィルタリングしています。この基準は運営するサイトの特性に合わせて調整が必要です。
「よしお願いします」とばかりに、このデータを使って次のステップに進みます。
Gemini APIで記事を自動リライトする
低ランク記事のURLが特定できたら、その記事のコンテンツを取得し、Gemini APIを使ってリライトします。
1. 記事コンテンツのスクレイピング/取得
Next.jsで運用している自社メディアであれば、サーバーサイドでコンテンツを取得するAPIを叩くか、DBから直接取得できます。外部サイトの場合は、CheerioやPlaywrightなどを使ってコンテンツをスクレイピングすることになりますが、著作権やサイトの規約には十分配慮が必要です。
今回は、自社Next.jsサイト内の記事を想定して進めます。
// src/lib/article.ts
import { JSDOM } from 'jsdom'; // HTML解析ライブラリ
// 記事URLからコンテンツ(今回はメインのテキスト部分)を取得するモック
// 実際にはDBから取得するか、内部APIを叩きます
export const fetchArticleContent = async (url: string): Promise<string | null> => {
try {
// 実際にはURLからコンテンツを取得するロジックを実装
// 例えば、自社CMSのAPIを叩く、またはSSR/SSGで生成されたHTMLを解析する
const response = await fetch(url);
const html = await response.text();
const dom = new JSDOM(html);
const document = dom.window.document;
// 記事のメインコンテンツ部分を特定するセレクタはサイトによって異なる
const articleBody = document.querySelector('article.prose');
if (articleBody) {
return articleBody.textContent;
}
return null;
} catch (error) {
console.error(`Error fetching article content from ${url}:`, error);
return null;
}
};
2. Gemini APIによるリライトプロンプト設計
ここが一番の肝です。Gemini(私は2.5 Flashをメインに使っています)にどう指示を出すかで、リライトの品質が大きく変わります。
重要なのは、以下の要素をプロンプトに含めることです。
- 目的: SEO順位向上
- 役割: SEO専門家
- ターゲット読者: (例: 初心者エンジニア、潜在顧客)
- 元記事の内容: 取得した記事コンテンツ
- キーワード: GSCで検出された低順位のクエリ(複数あればすべて含める)
-
指示:
- 元の意味を変えずに表現を豊かにする
- キーワードを自然に含める
- 導入と結論を改善する
- 読者の検索意図を満たす情報追加
- 見出し構成の改善
- 誤情報の修正(もしあれば)
// src/lib/gemini.ts
import { GoogleGenerativeAI } from '@google/generative-ai';
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY as string);
const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); // 常に最新モデルを活用
export const rewriteArticleWithGemini = async (
originalContent: string,
targetKeywords: string[],
articleTitle: string // タイトルも渡すとリライト精度が上がる
): Promise<string | null> => {
const prompt = `あなたはSEOの専門家です。以下の記事コンテンツを、指定されたキーワードで検索順位が向上するようにリライトしてください。
元の記事の意図や主要な情報を変えることなく、より読者の検索意図を満たし、情報量豊かに、そして自然な表現にしてください。
特に、導入部と結論部を強化し、必要であれば新しい段落や情報も追加してください。
---
# 記事タイトル: ${articleTitle}
# 元記事コンテンツ:
${originalContent}
# ターゲットキーワード:
${targetKeywords.join(', ')}
# 指示:
1. 元の記事の構成を参考にしつつ、より論理的で分かりやすい見出し構成に改善してください。
2. ターゲットキーワードを自然に、かつ適切に記事全体に散りばめてください。不自然なキーワードの詰め込みは厳禁です。
3. 読者が求めている情報を深掘りし、具体例や追加情報を積極的に加えてください。
4. 文章の表現を豊かにし、説得力のあるトーンで記述してください。
5. マークダウン形式で出力し、コードブロックやリストなども適切に活用してください。
6. 元の記事が誤情報を含んでいる場合は、正しい情報に修正してください。
---
# リライトされた記事コンテンツ(マークダウン形式):
`;
try {
const result = await model.generateContent(prompt);
const response = result.response;
return response.text();
} catch (error) {
console.error('Error rewriting article with Gemini API:', error);
return null;
}
};
「なるほどね」とAIが理解し、リライトしてくれれば、我々の作業は大きく削減されます。プロンプトは繰り返し検証し、精度を上げていくのがポイントです。私も「ドッグフーディング」で改善を続けています。
IndexNowでGoogleに高速インデックスを促す
記事をリライトしたら、その変更をいち早くGoogleに伝える必要があります。GSCから手動でURL検査ツールを使うのは面倒ですよね。そこで活用するのがIndexNowです。
IndexNowは、新しいコンテンツや更新されたコンテンツのURLを検索エンジンに即座に通知できるプロトコルです。Googleもサポートしているので、これを使わない手はありません。
1. IndexNowのセットアップ
サイトのルートディレクトリに、{あなたのキー}.txt というファイル名でテキストファイルを作成し、その中にIndexNow APIキーを記載します。
または、特定のパス(例: /api/indexnow)でキーを返すAPIエンドポイントを作成することも可能です。
2. IndexNow APIへのリクエスト
Node.jsでFetch APIを使ってIndexNow APIにリクエストを送ります。
// src/lib/indexnow.ts
export const submitUrlToIndexNow = async (url: string): Promise<boolean> => {
const indexNowKey = process.env.INDEXNOW_KEY; // あなたのIndexNowキー
const host = new URL(url).hostname;
if (!indexNowKey) {
console.error('INDEXNOW_KEY is not set.');
return false;
}
try {
const response = await fetch(`https://api.indexnow.org/indexnow?url=${encodeURIComponent(url)}&key=${indexNowKey}&host=${host}`, {
method: 'GET', // GETまたはPOST
});
if (response.ok) {
console.log(`Successfully submitted ${url} to IndexNow.`);
return true;
} else {
console.error(`Failed to submit ${url} to IndexNow. Status: ${response.status}, Message: ${await response.text()}`);
return false;
}
} catch (error) {
console.error(`Error submitting ${url} to IndexNow:`, error);
return false;
}
};
これで、リライトした記事が高速でインデックスされる可能性が高まります。
Next.js + Vercel Cronでパイプラインを構築する
これまでの要素技術を組み合わせ、Next.jsのAPI RoutesとVercel Cronを使って、自動実行されるパイプラインを構築します。
1. Vercel Cronの設定
vercel.json にVercel Cronの設定を追加します。例えば、毎日深夜に実行するように設定します。
// vercel.json
{
"crons": [
{
"path": "/api/cron/seo-optimizer",
"schedule": "0 0 * * *" // 毎日0時(UTC)に実行
}
]
}
2. API Routeの実装
/api/cron/seo-optimizer.ts に、GSC APIで低ランク記事を検出し、Geminiでリライトし、IndexNowで通知するロジックを実装します。
// src/pages/api/cron/seo-optimizer.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { fetchLowRankArticles } from '../../../lib/gsc';
import { fetchArticleContent } from '../../../lib/article';
import { rewriteArticleWithGemini } from '../../../lib/gemini';
import { submitUrlToIndexNow } from '../../../lib/indexnow';
import { updateArticleInDB } from '../../../lib/db'; // 記事更新用のDB関数を想定
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
return res.status(405).json({ message: 'Method Not Allowed' });
}
const siteUrl = process.env.YOUR_SITE_URL as string; // あなたのサイトURLを設定
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const startDate = new Date(yesterday.getFullYear(), yesterday.getMonth(), yesterday.getDate() - 30).toISOString().split('T')[0]; // 過去30日
const endDate = yesterday.toISOString().split('T')[0];
try {
console.log(`[SEO Optimizer Cron] Fetching low rank articles for ${siteUrl} from ${startDate} to ${endDate}...`);
const lowRankArticles = await fetchLowRankArticles(siteUrl, startDate, endDate);
if (lowRankArticles.length === 0) {
console.log('[SEO Optimizer Cron] No low rank articles found. Exiting.');
return res.status(200).json({ message: 'No low rank articles found.' });
}
console.log(`[SEO Optimizer Cron] Found ${lowRankArticles.length} low rank articles. Processing...`);
for (const article of lowRankArticles) {
const articleUrl = article.keys[0]; // page dimension
const targetQuery = article.keys[1]; // query dimension
const articleTitle = extractTitleFromUrl(articleUrl); // URLからタイトルを推測またはDBから取得
console.log(`[SEO Optimizer Cron] Processing article: ${articleUrl}, Keywords: ${targetQuery}`);
// 1. 記事コンテンツを取得
const originalContent = await fetchArticleContent(articleUrl);
if (!originalContent) {
console.warn(`[SEO Optimizer Cron] Could not fetch content for ${articleUrl}. Skipping.`);
continue;
}
// 2. Geminiでリライト
const rewrittenContent = await rewriteArticleWithGemini(originalContent, [targetQuery], articleTitle);
if (!rewrittenContent) {
console.error(`[SEO Optimizer Cron] Failed to rewrite content for ${articleUrl}. Skipping.`);
continue;
}
// 3. リライトしたコンテンツをDBに保存・公開(Next.jsのISRなどで再ビルド)
// ここでCMSやDBを更新するロジックを実装
await updateArticleInDB(articleUrl, rewrittenContent); // 仮の関数。実際にはDBを更新し、Next.jsのISRなどで新しい記事が生成されるようにする
// 4. IndexNowでGoogleに通知
await submitUrlToIndexNow(articleUrl);
console.log(`[SEO Optimizer Cron] Successfully processed and submitted ${articleUrl}`);
}
return res.status(200).json({ message: 'SEO optimization cron job completed.' });
} catch (error) {
console.error('[SEO Optimizer Cron] An error occurred:', error);
return res.status(500).json({ message: 'Internal Server Error', error: (error as Error).message });
}
}
// URLからタイトルを簡易的に抽出するヘルパー関数
function extractTitleFromUrl(url: string): string {
try {
const path = new URL(url).pathname;
const segments = path.split('/').filter(s => s !== '');
if (segments.length > 0) {
// スラッグをハイフン区切りからスペース区切りにして、最初の文字を大文字にする
return segments[segments.length - 1].replace(/-/g, ' ').split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
} catch (e) {
//
}
return url; // 失敗したら元のURLを返す
}
これで、毎日自動で低ランク記事が検出され、AIによってリライトされ、検索エンジンに通知されるパイプラインが完成しました。私はこの「補給不要の自販機型SaaS」をコンセプトに、自社メディアの運用効率を極限まで高めています。
仕組み化と自動化への執念が生む成果
この自動化パイプラインを導入してから、GSCの平均掲載順位の推移は明らかに改善傾向にあります。もちろん、すべての記事が劇的に順位アップするわけではありませんが、手動でリライトする手間と時間を考慮すれば、この自動化の恩恵は計り知れません。
「自分でエラーか確認してほしい」とAIエージェントに指示を出すように、このシステムもエラーログを監視し、必要に応じて私が介入します。しかし、基本的にはAIが自律的にSEO改善を続けてくれるため、私はより創造的な仕事や、新しいプロダクト開発に時間を費やすことができるようになりました。
「極限の少数精鋭・完全自動化モデル」の実現こそが、Futuristic Imagination LLCのビジョンです。
まとめ
本記事では、Google Search Console APIで低ランク記事を自動検出し、Gemini APIでリライトし、IndexNowで高速インデックスを促す自動化パイプラインの構築方法を解説しました。
- GSC APIで検索パフォーマンスデータを取得し、低順位記事を特定する
- Gemini 1.5 Flashを使った効果的なリライトプロンプト設計
- IndexNowによる迅速な検索エンジンへのコンテンツ更新通知
- Next.js、TypeScript、Vercel Cronを組み合わせた自動実行アーキテクチャ
この仕組みは、私自身がAIオウンドメディア18サイトの運用で実践しているものであり、実際にSEOパフォーマンスの改善に貢献しています。AI時代のゴールドラッシュを勝ち抜くためには、このような「仕組み化」と「自動化」への執念が不可欠だと確信しています。
今回紹介したようなシステムの構築代行も行っています。ご興味があれば、ぜひ弊社ウェブサイトをご確認ください。
→ https://www.futuristicimagination.co.jp/
特に、WordPressからのNext.js移行、SNS自動化、Gemini APIを活用したデータパイプライン構築などが得意です。B2B向けには、企業の採用ブログやコンテンツ生成を自動化する「MediaForge AI」も展開しています。
→ https://mediaforge-ai.vercel.app/
また、転職・副業・キャリアに関するShorts動画を毎日配信中ですので、ぜひそちらもご覧ください!
→ https://www.youtube.com/channel/UCFobIbWz1KDKaIdDqXpTPAA
それでは、皆さんもぜひ、AIと自動化の力で消耗する日々から脱却し、よりクリエイティブなエンジニアライフを送りましょう!