この記事は、2025年1月30日に Search Engine Land で公開された Ludwig Makhyan氏 の「Automate SEO analysis with Google Sheets, GSC & ChatGPT API」を翻訳したものです。
Google スプレッドシートを使えば、サイト監査の自動化、ページ分析、AIを活用した最適化のためのインサイト生成を一括で行うことができます。
SEO分析は多くの時間を要しますが、自動化を取り入れることで反復作業を減らし、重要なWebサイトの最適化を迅速に行えるようになります。
本ガイドでは、Googleスプレッドシート、Google Search Console(GSC)、ChatGPT APIを活用して、カスタムツールを構築し、SEO監査を効率化する方法を詳しく解説します。これにより、AIを活用した最適化のためのインサイト生成を一括で行うことができます。
このツールでできること
仕組み
まず、GSCに接続されているドメインからURLを選択し、ChatGPT APIキーを入力。その後、ページを分析し、AIを活用したSEOインサイトを取得します。すべての作業はGoogleスプレッドシート上で完結します。
このツールを設定すると、キーワードランキング、参照 URL、最終クロール日、robots.txt のインデックス作成ステータスなど、GSC からの主要な SEO データにすばやくアクセスできるようになります。
さらに、ChatGPTを活用することで、以下の項目を分析し、改善のための推奨事項を提供します。
このスクリプトはすべてを網羅できるわけではありませんが、数秒で詳細なページ分析情報を提供し、手作業にかかる時間を大幅に削減できます。
分析結果を確認した上で、どの推奨事項を実施するかを決定できます。
スクリプトの設定は一見複雑に思えるかもしれませんが、各手順を慎重に実行し、スクリプトをコピーして適切に動作することを確認すれば、スムーズに導入できます。
事前に準備するものは以下です。
それでは、このツールを稼働させるための8つのステップを順番に説明していきます。
Googleアカウントをお持ちであれば、以下の手順で簡単に作成できます。
シート名は自由に設定できますが、今回は「GSC カスタム ツール」と名付けました。
D1 に ChatGPT API キーを貼り付けます。このキーがない場合は、上記のリンクにアクセスして、このツールが機能するために必要なキーを取得してください。
ここで、両方の列のいくつかの行を結合する必要があります。次の手順に従って、F2 ~ F30 と G2 ~ G30 を個別に結合します。
結合する必要がある F 行と G 行に対してこの手順を繰り返します。
ツール用の Google Cloud Console データ プロジェクトを作成します。
セットアップが完了したら、「GSC Custom Tool」という名前の新しいプロジェクトを作成します。
Select Project > New Project に移動し、スクリーンショットの例と同様に情報を入力します。
プロジェクトの名前を設定したら、Create をクリックします。
次に、Google Search Console API をプロジェクトに接続します。
検索バーに「Google Search Console API」と入力して選択し、次の画面で「有効にする」をクリックします。
まだやるべきことはたくさんありますが、近いうちにこのプロジェクトを再検討する予定です。
さらに詳しく: SEO タスクを自動化する 5 つの Python スクリプト
さらに調整するには、Google Cloud Console に戻ります。
画面の左側にある「Credentials」をクリックします。
画面上部の「+CREATE CREDENTIALS」をクリックし、 OAuth Client ID を選択します。
「Configure Consent Screen」に移動し、「External」を選択します。画面を作成し、以下の情報を入力します。
入力後、「Save and continue」をクリックし、次の画面(「Add or Remove Scopes」)に進みます。
「Google Search Console API」のスコープを選択し、更新します。その後、「Save and continue」をクリックします。
アクセス権を付与したいユーザーを追加し、「Save and continue」をクリックします。
ハンバーガーアイコンをクリックし、Cloud overview > Dashboard に移動します。
ページに表示されている プロジェクト番号 をコピーします。
プロジェクト番号に移動し、それを選択して CTRL + C でコピーします。
Google スプレッドシートのファイルを開き、Project Settings と書かれた歯車アイコンをクリックします。
テキストボックスにプロジェクト番号を貼り付け、Set project をクリックします。
スクリプトの管理をしやすくするために、名前を追加します。
これを行うには、Project History に移動し、画面上部にある Untitled project をクリックします。
「GSC Custom Tool」と入力して、プロジェクト名を設定します。
新しく作成した Google スプレッドシート ファイルに Apps Script を統合します。
これを行うには、ファイルを開き、Extensions > Apps Script に移動します。
次に、以下のコードをコピー&ペーストします。
(コードウィンドウ内のコードを選択し、CTRL + C を押してコピーします。その後、Apps Script に戻り、CTRL + V を押してコードを貼り付けます。)
{
“timeZone”: “America/New_York”,
“dependencies”: {},
“exceptionLogging”: “STACKDRIVER”,
“oauthScopes”: [
“https://www.googleapis.com/auth/webmasters”,
“https://www.googleapis.com/auth/webmasters.readonly”,
“https://www.googleapis.com/auth/script.external_request”,
“https://www.googleapis.com/auth/spreadsheets”,
“https://www.googleapis.com/auth/spreadsheets.currentonly”
],
“runtimeVersion”: “V8”
}
すべてを貼り付けたら、Code.js ファイルに移動して次のコードを貼り付けます。
// Store the OAuth token and logs in script properties
const scriptProperties = PropertiesService.getScriptProperties();
const OPENAI_URL = “https://api.openai.com/v1/chat/completions”;
const SYSTEM_MESSAGE = { role: “system”, content: “You are a helpful SEO expert.” };
function log(message) {
Logger.log(message); // Regular Apps Script logging
const logs = scriptProperties.getProperty(‘customLogs’) || ”;
scriptProperties.setProperty(‘customLogs’, logs + ‘\n’ + message); // Append message to logs
}
function resetLogs() {
scriptProperties.deleteProperty(‘customLogs’); // Clear logs for a new execution
}
function getLogs() {
return scriptProperties.getProperty(‘customLogs’) || ‘No logs available.’;
}
function fetchOAuthToken() {
let token = scriptProperties.getProperty(‘oauthToken’);
if (!token) {
token = ScriptApp.getOAuthToken();
scriptProperties.setProperty(‘oauthToken’, token);
log(‘OAuth token fetched and stored.’);
}
return token;
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu(‘Search Console’)
.addItem(‘Authorize GSC’, ‘promptReauthorization’)
.addItem(‘Fetch GSC Properties’, ‘fetchGSCProperties’)
.addItem(‘Inspect URL’, ‘inspectUrl’) // Add the Inspect URL button
.addItem(‘AI Analyze’, ‘aiAnalyze’) // Add the AI Analyze button
.addToUi();
}
function promptReauthorization() {
const ui = SpreadsheetApp.getUi();
const response = ui.alert(
‘Re-authorize Script’,
‘Re-authorizing will revoke current permissions and require you to authorize again. Do you want to continue?’,
ui.ButtonSet.YES_NO
);
if (response === ui.Button.YES) {
try {
scriptProperties.deleteProperty(‘oauthToken’); // Clear old token
const token = fetchOAuthToken(); // Fetch and store new token
log(“OAuth Token: ” + token);
ui.alert(‘Authorization successful. No further action is required.’);
} catch (e) {
ui.alert(‘Authorization failed: ‘ + e.toString());
}
} else {
ui.alert(‘Re-authorization canceled.’);
}
}
function fetchGSCProperties() {
resetLogs();
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const oauthToken = fetchOAuthToken();
const sites = getSitesListFromGSC(oauthToken);
if (!sites || sites.length === 0) {
SpreadsheetApp.getUi().alert(‘No GSC properties found. Please ensure you have access to GSC properties.’);
return;
}
const siteUrls = [‘Select a property’].concat(
sites.map(site => site.siteUrl).sort()
);
sheet.getRange(‘A1’).setValue(‘Select GSC property’).setFontWeight(‘bold’);
sheet.getRange(‘B1’).setDataValidation(
SpreadsheetApp.newDataValidation()
.requireValueInList(siteUrls, true)
.build()
);
sheet.getRange(‘B1’).setValue(‘Select a property’).setFontWeight(‘bold’);
sheet.setColumnWidth(1, 150);
sheet.setColumnWidth(2, 350);
}
let isProcessing = false; // Global flag to prevent recursive triggering
function onEdit(e) {
if (isProcessing) return; // Prevent re-entry during execution
isProcessing = true; // Set flag to true
try {
resetLogs();
const sheet = e.source.getActiveSheet();
const range = e.range;
// Always clear A3:D30 on edits to B1 or B2
if (range.getA1Notation() === ‘B1’ || range.getA1Notation() === ‘B2’) {
sheet.getRange(‘A3:D30’).clearContent();
sheet.getRange(‘F1:G30’).clearContent();
if (range.getA1Notation() === ‘B1’) {
const selectedProperty = range.getValue();
// Clear A2 and set loading state
sheet.getRange(‘A2’).setValue(‘Loading results…’).setFontWeight(‘bold’);
sheet.getRange(‘B2’).clearContent();
if (selectedProperty === ‘Select a property’) {
sheet.getRange(‘A2’).clearContent();
sheet.getRange(‘B2’).clearContent();
return;
}
const oauthToken = fetchOAuthToken();
const urls = getUrlsForProperty(selectedProperty, oauthToken);
if (!urls || urls.length === 0) {
sheet.getRange(‘A2’).setValue(‘No URLs found’).setFontWeight(‘bold’);
sheet.getRange(‘B2’).clearContent();
log(`No URLs found for property ${selectedProperty}`);
return;
}
sheet.getRange(‘A2’).setValue(‘Select a URL’).setFontWeight(‘bold’);
sheet.getRange(‘B2’).setDataValidation(
SpreadsheetApp.newDataValidation()
.requireValueInList([‘Select a URL’].concat(urls), true)
.build()
);
sheet.getRange(‘B2’).setValue(‘Select a URL’).setFontWeight(‘bold’);
}
if (range.getA1Notation() === ‘B2’) {
const selectedProperty = sheet.getRange(‘B1’).getValue();
const selectedUrl = range.getValue();
if (selectedUrl === ‘Select a URL’) {
return;
}
sheet.getRange(‘A3’).setValue(‘Loading keywords…’).setFontWeight(‘bold’);
const oauthToken = fetchOAuthToken();
const keywords = getTopKeywordsForUrl(selectedProperty, selectedUrl, oauthToken);
if (!keywords || keywords.length === 0) {
sheet.getRange(‘A3’).setValue(‘No keywords found’).setFontWeight(‘bold’);
log(`No keywords found for URL ${selectedUrl}`);
return;
}
// Populate keywords and metrics
sheet.getRange(‘A3:D12’).clearContent(); // Clear any loading message
keywords.forEach((keyword, index) => {
if (index < 10) {
sheet.getRange(`A${3 + index}`).setValue(keyword.query).setFontWeight(‘bold’);
sheet.getRange(`B${3 + index}`).setValue(keyword.clicks);
sheet.getRange(`C${3 + index}`).setValue(keyword.impressions);
sheet.getRange(`D${3 + index}`).setValue(keyword.ctr);
}
});
}
}
} catch (error) {
log(`Error in onEdit: ${error}`);
} finally {
isProcessing = false; // Reset the flag after execution
}
}
function getApiRequestDetails(selectedProperty) {
const payload = {
startDate: getThreeMonthsAgo(),
endDate: getToday(),
dimensions: [“page”],
rowLimit: 100,
orderBy: [{ fieldName: “clicks”, sortOrder: “DESCENDING” }]
};
const apiUrl = `https://www.googleapis.com/webmasters/v3/sites/${encodeURIComponent(selectedProperty)}/searchAnalytics/query`;
return { url: apiUrl, payload: payload };
}
function getSitesListFromGSC(oauthToken) {
try {
const url = ‘https://www.googleapis.com/webmasters/v3/sites’;
const headers = {
‘Authorization’: ‘Bearer ‘ + oauthToken,
‘Content-Type’: ‘application/json’
};
const options = {
method: ‘get’,
headers: headers,
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
log(`Response Code: ${response.getResponseCode()}`);
log(`Response Content: ${response.getContentText()}`);
if (response.getResponseCode() === 200) {
const json = JSON.parse(response.getContentText());
return json.siteEntry || [];
} else {
throw new Error(`Error fetching data: ${response.getResponseCode()} – ${response.getContentText()}`);
}
} catch (e) {
log(`Error: ${e.toString()}`);
return [];
}
}
function getUrlsForProperty(property, oauthToken) {
try {
const apiUrl = `https://www.googleapis.com/webmasters/v3/sites/${encodeURIComponent(property)}/searchAnalytics/query`;
log(`API URL: ${apiUrl}`);
log(`OAuth Token: ${oauthToken}`);
const payload = {
startDate: getThreeMonthsAgo(),
endDate: getToday(),
dimensions: [“page”],
rowLimit: 100,
orderBy: [{ fieldName: “clicks”, sortOrder: “DESCENDING” }]
};
log(`Payload: ${JSON.stringify(payload)}`);
const headers = {
Authorization: `Bearer ${oauthToken}`,
“Content-Type”: “application/json”
};
const options = {
method: “post”,
contentType: “application/json”,
headers: headers,
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(apiUrl, options);
log(`Response Code: ${response.getResponseCode()}`);
log(`Response: ${response.getContentText()}`);
if (response.getResponseCode() === 200) {
const json = JSON.parse(response.getContentText());
return json.rows ? json.rows.map(row => row.keys[0]) : [];
} else {
throw new Error(`Failed to fetch data: ${response.getResponseCode()} – ${response.getContentText()}`);
}
} catch (e) {
log(`Error: ${e.toString()}`);
return [];
}
}
function getTopKeywordsForUrl(property, url, oauthToken) {
try {
const apiUrl = `https://www.googleapis.com/webmasters/v3/sites/${encodeURIComponent(property)}/searchAnalytics/query`;
log(`API URL: ${apiUrl}`);
log(`OAuth Token: ${oauthToken}`);
const payload = {
startDate: getThreeMonthsAgo(),
endDate: getToday(),
dimensions: [“query”],
dimensionFilterGroups: [
{
filters: [
{
dimension: “page”,
operator: “equals”,
expression: url
}
]
}
],
rowLimit: 10,
orderBy: [{ fieldName: “clicks”, sortOrder: “DESCENDING” }]
};
log(`Payload: ${JSON.stringify(payload)}`);
const headers = {
Authorization: `Bearer ${oauthToken}`,
“Content-Type”: “application/json”
};
const options = {
method: “post”,
contentType: “application/json”,
headers: headers,
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(apiUrl, options);
log(`Response Code: ${response.getResponseCode()}`);
log(`Response: ${response.getContentText()}`);
if (response.getResponseCode() === 200) {
const json = JSON.parse(response.getContentText());
return json.rows ? json.rows.map(row => ({
query: row.keys[0],
clicks: row.clicks,
impressions: row.impressions,
ctr: row.ctr
})) : [];
} else {
throw new Error(`Failed to fetch data: ${response.getResponseCode()} – ${response.getContentText()}`);
}
} catch (e) {
log(`Error: ${e.toString()}`);
return [];
}
}
function getToday() {
const today = new Date();
return today.toISOString().split(“T”)[0];
}
function getThreeMonthsAgo() {
const date = new Date();
date.setMonth(date.getMonth() – 3);
return date.toISOString().split(“T”)[0];
}
function inspectUrl() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const url = sheet.getRange(‘B2’).getValue();
const property = sheet.getRange(‘B1’).getValue();
// Clear previous inspection results in A15:D30
sheet.getRange(‘A15:D30’).clearContent();
sheet.getRange(‘A15’).setValue(‘Inspecting…’).setFontWeight(‘bold’);
if (!url || url === ‘Select a URL’) {
SpreadsheetApp.getUi().alert(‘Please select a valid URL in cell B2 before inspecting.’);
sheet.getRange(‘A15’).setValue(‘No URL selected’).setFontWeight(‘bold’);
return;
}
const oauthToken = fetchOAuthToken();
try {
const result = callUrlInspectionApi(property, url, oauthToken);
// Extract fields from the response
const indexStatus = result.indexStatusResult || {};
const mobileUsability = result.mobileUsabilityResult || {};
const richResults = result.richResultsInfo || {};
const referringUrls = indexStatus.referringUrls?.join(‘, ‘) || ‘None’;
// Populate inspection results in the sheet
sheet.getRange(‘A15’).setValue(`Inspection Results`).setFontWeight(‘bold’);
sheet.getRange(‘A16’).setValue(`URL:`).setFontWeight(‘bold’);
sheet.getRange(‘B16’).setValue(url);
sheet.getRange(‘A17’).setValue(`Coverage:`).setFontWeight(‘bold’);
sheet.getRange(‘B17’).setValue(indexStatus.coverageState || ‘Unknown’);
sheet.getRange(‘A18’).setValue(`Robots.txt:`).setFontWeight(‘bold’);
sheet.getRange(‘B18’).setValue(indexStatus.robotsTxtState || ‘Unknown’);
sheet.getRange(‘A19’).setValue(`Indexing State:`).setFontWeight(‘bold’);
sheet.getRange(‘B19’).setValue(indexStatus.indexingState || ‘Unknown’);
sheet.getRange(‘A20’).setValue(`Last Crawled:`).setFontWeight(‘bold’);
sheet.getRange(‘B20’).setValue(indexStatus.lastCrawlTime || ‘Not Available’);
sheet.getRange(‘A21’).setValue(`Google Canonical:`).setFontWeight(‘bold’);
sheet.getRange(‘B21’).setValue(indexStatus.googleCanonical || ‘Unknown’);
sheet.getRange(‘A22’).setValue(`User Canonical:`).setFontWeight(‘bold’);
sheet.getRange(‘B22’).setValue(indexStatus.userCanonical || ‘Unknown’);
sheet.getRange(‘A23’).setValue(`Mobile Usability:`).setFontWeight(‘bold’);
sheet.getRange(‘B23’).setValue(mobileUsability.verdict || ‘Unknown’);
sheet.getRange(‘A24’).setValue(`Rich Results Eligibility:`).setFontWeight(‘bold’);
sheet.getRange(‘B24’).setValue(richResults.verdict || ‘Unknown’);
sheet.getRange(‘A25’).setValue(`Referring URLs:`).setFontWeight(‘bold’);
sheet.getRange(‘B25’).setValue(referringUrls);
// Log and alert full response for debugging
const fullResponse = JSON.stringify(result, null, 2);
log(`Full Inspection Result: ${fullResponse}`);
//SpreadsheetApp.getUi().alert(`Inspection Completed. Full Response:\n\n${fullResponse}`);
} catch (error) {
sheet.getRange(‘A15’).setValue(‘Inspection Failed’).setFontWeight(‘bold’);
log(`Error inspecting URL: ${error.message}`);
SpreadsheetApp.getUi().alert(`Error inspecting URL: ${error.message}\n\nLogs:\n${getLogs()}`);
}
}
function callUrlInspectionApi(property, url, oauthToken) {
const apiUrl = ‘https://searchconsole.googleapis.com/v1/urlInspection/index:inspect’;
const payload = {
siteUrl: property,
inspectionUrl: url,
languageCode: ‘en-US’
};
const headers = {
Authorization: `Bearer ${oauthToken}`,
‘Content-Type’: ‘application/json’
};
const options = {
method: ‘post’,
contentType: ‘application/json’,
headers: headers,
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
log(`API URL: ${apiUrl}`);
log(`Payload: ${JSON.stringify(payload)}`);
try {
const response = UrlFetchApp.fetch(apiUrl, options);
const responseCode = response.getResponseCode();
const responseText = response.getContentText();
log(`Response Code: ${responseCode}`);
log(`Response Content: ${responseText}`);
if (responseCode === 200) {
const jsonResponse = JSON.parse(responseText);
if (jsonResponse && jsonResponse.inspectionResult) {
return jsonResponse.inspectionResult;
} else {
log(`Unexpected API Response Structure: ${responseText}`);
throw new Error(‘Unexpected API response format. “inspectionResult” field is missing.’);
}
} else {
log(`Failed API Call: ${responseText}`);
throw new Error(`Failed to inspect URL. Response Code: ${responseCode}. Response: ${responseText}`);
}
} catch (error) {
log(`Error during API call: ${error}`);
throw new Error(`Error inspecting URL: ${error.message}`);
}
}
function callChatGPT(prompt, temperature = 0.9, maxTokens = 800, model = “gpt-3.5-turbo”) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const secretKey = sheet.getRange(‘D1’).getValue().trim(); // Retrieve the OpenAI API key from D1
if (!secretKey) {
throw new Error(“API Key is missing in cell D1. Please provide a valid OpenAI API key.”);
}
const payload = {
model: model,
messages: [
SYSTEM_MESSAGE,
{ role: “user”, content: prompt }
],
temperature: temperature,
max_tokens: maxTokens
};
const options = {
method: “POST”,
headers: {
“Content-Type”: “application/json”,
“Authorization”: “Bearer ” + secretKey
},
payload: JSON.stringify(payload)
};
try {
const response = UrlFetchApp.fetch(OPENAI_URL, options);
const responseData = JSON.parse(response.getContentText());
if (responseData.choices && responseData.choices[0] && responseData.choices[0].message) {
return responseData.choices[0].message.content.trim();
} else {
log(“Unexpected response format from OpenAI: ” + JSON.stringify(responseData));
return “Sorry, I couldn’t process the request.”;
}
} catch (error) {
log(“Error calling OpenAI API: ” + error);
return “Sorry, there was an error processing your request.”;
}
}
function aiAnalyze() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const url = sheet.getRange(‘B2’).getValue();
const keywords = sheet.getRange(‘A3:A12’).getValues().flat().filter(Boolean); // Get non-empty keywords
const inspectionData = sheet.getRange(‘A16:B20’).getValues();
// Validate input fields
if (!url || keywords.length === 0 || inspectionData.some(row => row.length < 2 || !row[0].trim() || !row[1].trim())) {
SpreadsheetApp.getUi().alert(“Ensure the following are filled before running AI Analyze:\n- URL in B2\n- Keywords in A3:A12\n- Inspection data in A16:B20”);
return;
}
// Prepare the prompt for ChatGPT
const prompt = `
Analyze this URL: ${url}
Also the view-source version from: ${url}
against these keywords: ${keywords.join(“, “)}
Considering the URL inspection data from Google Search Console:
${inspectionData.map(row => `${row[0]}: ${row[1]}`).join(“\n”)}
Suggest a short list of specific recommendations on how I can improve the page’s SEO. Make sure the recommendations include details such as change this to that, or add something, etc… Be concrete with SEO recommendations.
`;
// Display the prompt in G1
sheet.getRange(‘G1’).setValue(“Prompt Sent to ChatGPT”).setFontWeight(“bold”);
sheet.getRange(‘G2:G30’).clearContent(); // Clear previous content in column G
sheet.getRange(‘G2:G30’).merge(); // Merge cells G2:G30
sheet.getRange(‘G2’).setValue(prompt).setVerticalAlignment(“top”); // Add the prompt and align to top
sheet.setColumnWidth(7, 400); // Set column G width to 400px
// Call ChatGPT API
const analysisResult = callChatGPT(prompt);
// Display the result in the spreadsheet (Column F)
sheet.getRange(‘F1’).setValue(“AI Analysis Result”).setFontWeight(“bold”);
sheet.getRange(‘F2:F30’).clearContent(); // Clear previous content
sheet.getRange(‘F2:F30’).merge(); // Merge the cells
sheet.getRange(‘F2’).setValue(analysisResult).setVerticalAlignment(“top”); // Add the AI result and align to top
sheet.setColumnWidth(6, 400); // Set column F width to 400px
// Log the response
log(“AI Analysis Completed: ” + analysisResult);
}
完了したら、シートに戻って更新し、新しいSearch Console > Fetch GSC Properties を使用します。
使用しているアカウントを選択し、最終的に使用する予定のアプリを選択するように求められるまで、指示に従います。
すべてがうまくいけば、それをまとめ、新しいスクリプトを使用して最初の SEO 分析を実行するという楽しい部分に進むことができます。
これまで多くのことを行ってきましたが、ツールが実際に動作する様子を見てみましょう。仕組みは次のとおりです。
GSC、Google スプレッドシート、ChatGPT を組み合わせることで、ページの最適化により多くの時間を費やせるようになります。
スクリプトを試しながら、自分に合った使い方を見つけてください!
詳しく見る: SEO に使用すべき 15 個の AI ツール
SEO最新情報やセミナー開催のお知らせなど、お役立ち情報を無料でお届けします。