GPT-4o와 Google Search Console을 사용하여 키워드 침식을 검출
고급
이것은Content Creation, Multimodal AI분야의자동화 워크플로우로, 27개의 노드를 포함합니다.주로 If, Code, Merge, HttpRequest, GoogleSheets 등의 노드를 사용하며. GPT-4o와 Google Search Console을 사용하여 키워드 침식을 검출
사전 요구사항
- •대상 API의 인증 정보가 필요할 수 있음
- •Google Sheets API 인증 정보
- •OpenAI API Key
사용된 노드 (27)
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"meta": {
"instanceId": "bc8ca75c203589705ae2e446cad7181d6f2a7cc1766f958ef9f34810e53b8cb2"
},
"nodes": [
{
"id": "90168f30-8e88-4b5a-9a80-586ad28f5a8f",
"name": "GSC 데이터 키워드별 그룹화 (클라이언트 2)",
"type": "n8n-nodes-base.code",
"notes": "Groups Google Search Console data by keyword for Client 2. Takes raw GSC response and organizes it by keyword, with each keyword containing an array of URLs that rank for it, including position, clicks, impressions, and CTR data.",
"position": [
336,
464
],
"parameters": {
"jsCode": "const grouped = {};\n\nfor (const row of items[0].json.rows) {\n const query = row.keys[0]; // keyword\n const url = row.keys[1]; // page\n \n if (!grouped[query]) grouped[query] = [];\n \n grouped[query].push({\n url,\n position: row.position,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr: row.ctr // CTR added here\n });\n}\n\n// Convert grouped object into array of items for next node\nreturn Object.entries(grouped).map(([keyword, urls]) => ({\n json: { keyword, urls }\n}));"
},
"typeVersion": 2
},
{
"id": "866dd165-dd86-43b9-b24e-e54ade95c42c",
"name": "GSC 데이터 키워드별 그룹화 (클라이언트 3)",
"type": "n8n-nodes-base.code",
"notes": "Groups Google Search Console data by keyword for Client 3. Transforms the GSC API response into a keyword-centric structure where each keyword has associated URLs with their ranking metrics.",
"position": [
336,
848
],
"parameters": {
"jsCode": "const grouped = {};\n\nfor (const row of items[0].json.rows) {\n const query = row.keys[0]; // keyword\n const url = row.keys[1]; // page\n \n if (!grouped[query]) grouped[query] = [];\n \n grouped[query].push({\n url,\n position: row.position,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr: row.ctr // CTR added here\n });\n}\n\n// Convert grouped object into array of items for next node\nreturn Object.entries(grouped).map(([keyword, urls]) => ({\n json: { keyword, urls }\n}));"
},
"typeVersion": 2
},
{
"id": "e679fffe-fb2b-4cff-b5a0-104179212ac0",
"name": "GSC 데이터 키워드별 그룹화 (클라이언트 4)",
"type": "n8n-nodes-base.code",
"notes": "Groups Google Search Console data by keyword for Client 4. Processes raw GSC data and restructures it to group all URLs ranking for each keyword together with their performance metrics.",
"position": [
336,
1040
],
"parameters": {
"jsCode": "const grouped = {};\n\nfor (const row of items[0].json.rows) {\n const query = row.keys[0]; // keyword\n const url = row.keys[1]; // page\n \n if (!grouped[query]) grouped[query] = [];\n \n grouped[query].push({\n url,\n position: row.position,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr: row.ctr // CTR added here\n });\n}\n\n// Convert grouped object into array of items for next node\nreturn Object.entries(grouped).map(([keyword, urls]) => ({\n json: { keyword, urls }\n}));"
},
"typeVersion": 2
},
{
"id": "7f125793-0b03-4466-b25b-1ef3bb4a76d4",
"name": "GSC 데이터 키워드별 그룹화 (클라이언트 1)",
"type": "n8n-nodes-base.code",
"notes": "Groups Google Search Console data by keyword for Client 1. Converts the flat GSC response into a grouped structure where each keyword contains all its ranking URLs with position, clicks, impressions, and CTR.",
"position": [
336,
272
],
"parameters": {
"jsCode": "const grouped = {};\n\nfor (const row of items[0].json.rows) {\n const query = row.keys[0]; // keyword\n const url = row.keys[1]; // page\n \n if (!grouped[query]) grouped[query] = [];\n \n grouped[query].push({\n url,\n position: row.position,\n clicks: row.clicks,\n impressions: row.impressions,\n ctr: row.ctr // CTR added here\n });\n}\n\n// Convert grouped object into array of items for next node\nreturn Object.entries(grouped).map(([keyword, urls]) => ({\n json: { keyword, urls }\n}));"
},
"typeVersion": 2
},
{
"id": "94c42a7f-50aa-48c9-9643-58eeaddd8ff8",
"name": "모든 클라이언트 GSC 데이터 병합",
"type": "n8n-nodes-base.merge",
"notes": "Combines GSC data from all 4 clients plus the target keywords from the Google Sheet. This creates a unified dataset containing both the keyword targets and actual GSC performance data.",
"position": [
800,
608
],
"parameters": {
"numberInputs": 5
},
"typeVersion": 3.2
},
{
"id": "d79e5c22-ac7a-43f9-af04-0997fc7af3ce",
"name": "시트 키워드와 GSC 데이터 매칭",
"type": "n8n-nodes-base.code",
"notes": "Cross-references target keywords from the Google Sheet with actual GSC performance data. Identifies which target keywords are ranking in GSC and which are missing, adding status flags for tracking.",
"position": [
1056,
656
],
"parameters": {
"jsCode": "const out = [];\n\n// Step 1: Collect all Google Sheet keywords\nconst sheetKeywords = items\n .filter(i => i.json.Targetted_Keywords) // from Sheets\n .map(i => (i.json.Targetted_Keywords || \"\").toLowerCase().trim());\n\n// Deduplicate\nconst uniqueSheetKeywords = [...new Set(sheetKeywords)];\n\n// Step 2: Collect all GSC keywords\nconst gscData = items.filter(i => i.json.keyword);\n\n// Step 3: Match - GSC keywords found in sheet\nconst foundKeywords = new Set();\n\nfor (const item of gscData) {\n const gscKeyword = (item.json.keyword || \"\").toLowerCase().trim();\n \n if (uniqueSheetKeywords.includes(gscKeyword)) {\n foundKeywords.add(gscKeyword); // Track found keywords\n \n // Make sure URLs array includes CTR for each URL\n const urlsWithCtr = (item.json.urls || []).map(urlObj => ({\n url: urlObj.url,\n position: urlObj.position,\n clicks: urlObj.clicks,\n impressions: urlObj.impressions,\n ctr: urlObj.ctr // CTR included here\n }));\n \n out.push({\n json: {\n keyword: item.json.keyword,\n urls: urlsWithCtr,\n status: 'found_in_gsc' // Optional: to identify matched keywords\n }\n });\n }\n}\n\n// Step 4: Add sheet keywords that were NOT found in GSC\nfor (const sheetKeyword of uniqueSheetKeywords) {\n if (!foundKeywords.has(sheetKeyword)) {\n out.push({\n json: {\n keyword: sheetKeyword,\n urls: [], // No URLs since not found in GSC\n status: 'not_found_in_gsc' // Optional: to identify missing keywords\n }\n });\n }\n}\n\nreturn out;"
},
"typeVersion": 2
},
{
"id": "2f8ae342-1586-4f0f-8cd5-d650c4a62b30",
"name": "키워드 캐니벌라이제이션 위험 분석",
"type": "@n8n/n8n-nodes-langchain.agent",
"notes": "Uses AI to analyze keyword cannibalization risk by examining how many pages from the same domain rank for each keyword. Categorizes risk as High, Moderate, Low, or No risk based on page count and performance distribution.",
"position": [
1744,
352
],
"parameters": {
"text": "=You are a Keyword Cannibalization Risk Detector.\n\nYou will receive:\n\n{{ $json.keyword }}\n{{ JSON.stringify($json.urls) }}\n\n\n\n\nYour Tasks:\n\nExtract the domain from each URL and group the pages by domain.\n\nFor each domain, analyze whether multiple pages are competing for the same keyword.\n\nHigh → 5 or more pages from the same domain rank for the keyword.\n\nModerate → 3 pages from the same domain rank closely in the top 10.\n\nLow → 2 pages rank, but one clearly dominates in clicks/impressions.\n\nNo → Only 1 page from that domain ranks.\n\nIf the main domain's homepage is ranking at position 1 AND other URLs from the same domain are also ranking, still classify as appropriate risk level based on total page count (don't give them No Risk just because the homepage ranks #1).\n\nIf multiple different domains rank for the same keyword, highlight cross-domain competition separately.\n\nReturn your findings in structured and concise form, showing the keyword, domains, and their respective risk levels.",
"options": {},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.2
},
{
"id": "0957bb5f-6200-40a4-a61a-002c965b53ee",
"name": "OpenAI GPT-4o Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"notes": "Provides the AI language model (GPT-4o) for the cannibalization analysis agent. Handles the natural language processing to understand keyword competition patterns.",
"position": [
1728,
544
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "gpt-4o"
},
"options": {}
},
"typeVersion": 1.2
},
{
"id": "503e683a-53c1-4aed-a86d-49ccdecd8b84",
"name": "AI 분석을 구조화된 JSON로 파싱",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"notes": "Converts the AI agent's natural language response into a structured JSON format with specific fields for keyword, domain, URLs, risk level, reasoning, observations, summary, and remediation steps.",
"position": [
1904,
544
],
"parameters": {
"jsonSchemaExample": "{\n \"Keyword\": \"\",\n \"Domain\": \"\",\n \"URLs for Keyword\": [\n {\n \"url\": \"\",\n \"position\": \"\",\n \"clicks\": \"\",\n \"impressions\": \"\",\n \"ctr\": \"\"\n }\n ],\n \"Risk Level\": \"\",\n \"Reasoning\": \"\",\n \"Observation\": \"\",\n \"Summary\": \"\",\n \"Remediation steps\": \"\"\n}\n"
},
"typeVersion": 1.3
},
{
"id": "7ab77205-1560-45ae-8a94-fb088dcfa62e",
"name": "변경사항에 대한 모니터링 키워드 시트",
"type": "n8n-nodes-base.googleSheetsTrigger",
"notes": "Monitors the Keywords Google Sheet for any changes and triggers the workflow when modifications are detected. Polls every minute to ensure real-time processing of keyword updates.",
"maxTries": 5,
"position": [
-944,
656
],
"parameters": {
"options": {},
"pollTimes": {
"item": [
{
"mode": "everyMinute"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1256649775,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit#gid=1256649775",
"cachedResultName": "Keywords"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit?gid=1256649775#gid=1256649775"
}
},
"retryOnFail": true,
"typeVersion": 1
},
{
"id": "6dbee777-42fc-4576-a27b-4650af47455e",
"name": "캐니벌라이제이션 분석 결과 저장",
"type": "n8n-nodes-base.googleSheets",
"notes": "Writes the final cannibalization analysis results back to Google Sheets. Updates or appends rows with risk levels, reasoning, observations, remediation steps, and all associated keyword data.",
"position": [
2272,
672
],
"parameters": {
"columns": {
"value": {
"Data": "={{ $json.output['URLs for Keyword'].map(i => `${i.url} | Position: ${i.position} | Clicks: ${i.clicks} | Impressions: ${i.impressions} | CTR: ${i.ctr}`).join('\n') }}",
"Date": "={{ $now.format('yyyy-MM-dd') }}",
"Domain": "={{ $json.output.Domain }}",
"Status": "={{ $('Match Keywords from Sheet with GSC Data').item.json.status }}",
"Summary": "={{ $json.output.Summary }}",
"Reasoning": "={{ $json.output.Reasoning }}",
"Risk Level": "={{ $json.output['Risk Level'] }}",
"Observation": "={{ $json.output.Observation }}",
"Target page": "={{ $json.output['URLs for Keyword'].map(u => u.url).join(', ') }}\n",
"remediation steps": "={{ $json.output['remediation steps'] }}",
"Targetted_Keywords": "={{ $json.output.Keyword }}\n{{ $json.keyword }}"
},
"schema": [
{
"id": "Targetted_Keywords",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Targetted_Keywords",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Domain",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Date",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Target page",
"type": "string",
"display": true,
"required": false,
"displayName": "Target page",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Data",
"type": "string",
"display": true,
"required": false,
"displayName": "Data",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Risk Level",
"type": "string",
"display": true,
"required": false,
"displayName": "Risk Level",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reasoning",
"type": "string",
"display": true,
"required": false,
"displayName": "Reasoning",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Observation",
"type": "string",
"display": true,
"required": false,
"displayName": "Observation",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Summary",
"type": "string",
"display": true,
"required": false,
"displayName": "Summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "remediation steps",
"type": "string",
"display": true,
"required": false,
"displayName": "remediation steps",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Targetted_Keywords"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1761789723,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit#gid=1761789723",
"cachedResultName": "data"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit?gid=1256649775#gid=1256649775"
}
},
"typeVersion": 4.7
},
{
"id": "f78e179c-dedd-4d79-9f96-ab40fbe40f3f",
"name": "클라이언트 1로 라우팅",
"type": "n8n-nodes-base.if",
"notes": "Routes workflow execution to Client 1's GSC data fetching if the client website matches the specified URL pattern. Acts as a conditional switch for multi-client processing.",
"position": [
-288,
288
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "9e1de819-aebf-4242-8f79-7262e422eb57",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json['Client Website'].trimStart().trimEnd() }}",
"rightValue": "https://theshroomgroove.com/"
}
]
},
"looseTypeValidation": true
},
"executeOnce": false,
"typeVersion": 2.2
},
{
"id": "f44239f6-1831-4546-a829-2abf27d4eb2e",
"name": "클라이언트 2로 라우팅",
"type": "n8n-nodes-base.if",
"notes": "Routes workflow execution to Client 2's GSC data fetching when the client website matches the specified domain. Enables parallel processing of multiple clients.",
"position": [
-288,
480
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "9e1de819-aebf-4242-8f79-7262e422eb57",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json['Client Website'].trimStart().trimEnd() }}",
"rightValue": "grooveguide.io"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "fc7efa81-5870-4bc7-bd21-2181214976ff",
"name": "클라이언트 3로 라우팅",
"type": "n8n-nodes-base.if",
"notes": "Directs the workflow to fetch GSC data for Client 3 when the website URL matches the condition. Part of the multi-client routing logic to handle different domains.",
"position": [
-288,
864
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "9e1de819-aebf-4242-8f79-7262e422eb57",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json['Client Website'].trimStart().trimEnd() }}",
"rightValue": "https://groovegrillwellness.com/"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "c91f5ab4-29a1-4f83-b570-3fdf4b3e7c1a",
"name": "클라이언트 4로 라우팅",
"type": "n8n-nodes-base.if",
"notes": "Routes to Client 4's GSC data collection process when the website URL condition is met. Completes the multi-client routing system for parallel data processing.",
"position": [
-288,
1056
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "or",
"conditions": [
{
"id": "9e1de819-aebf-4242-8f79-7262e422eb57",
"operator": {
"type": "string",
"operation": "contains"
},
"leftValue": "={{ $json['Client Website'].trimStart().trimEnd() }}",
"rightValue": "https://example.com/"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "2c0ba70f-00df-4be9-98a2-7969653e10db",
"name": "클라이언트 웹사이트 URL 가져오기",
"type": "n8n-nodes-base.googleSheets",
"notes": "Retrieves the list of client website URLs from the Google Sheet. This data is used to determine which clients to process and route them to their respective GSC data collection paths.",
"position": [
-656,
656
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 146956146,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit#gid=146956146",
"cachedResultName": "URL"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit?usp=drivesdk",
"cachedResultName": "Client URLs"
}
},
"executeOnce": false,
"typeVersion": 4.7
},
{
"id": "a6a60ba4-3b48-4750-af64-bf19c22f6b5c",
"name": "시트에서 타겟 키워드 가져오기",
"type": "n8n-nodes-base.googleSheets",
"notes": "Reads the target keywords from the Google Sheet that need to be analyzed for cannibalization. These keywords serve as the reference list to match against actual GSC performance data.",
"position": [
-288,
656
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1256649775,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit#gid=1256649775",
"cachedResultName": "Keywords"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1UfQvam8UhT58LSBobnyFsd3fi1dk7ilzvbQ57rFTIIo/edit?usp=drivesdk",
"cachedResultName": "Client URLs"
}
},
"executeOnce": true,
"typeVersion": 4.7
},
{
"id": "5a72cf8f-cf73-4811-9b55-807f7966e826",
"name": "GSC 데이터 가져오기 (클라이언트 1)",
"type": "n8n-nodes-base.httpRequest",
"notes": "Makes API call to Google Search Console for Client 1 to retrieve the last 30 days of search performance data. Gets keyword-page combinations with position, clicks, impressions, and CTR metrics.",
"onError": "continueRegularOutput",
"position": [
48,
272
],
"parameters": {
"url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($json['Client Website']) }}/searchAnalytics/query",
"method": "POST",
"options": {},
"jsonBody": "={\n \"startDate\": \"{{ $now.minus(30, 'days').format('yyyy-MM-dd') }}\",\n \"endDate\": \"{{ $now.format('yyyy-MM-dd') }}\",\n \"dimensions\": [\"query\", \"page\"]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleOAuth2Api"
},
"executeOnce": true,
"typeVersion": 4.2
},
{
"id": "03e271fe-0bfd-4f75-85af-cfdf0bcb8ac0",
"name": "GSC에서 발견된 키워드 필터링",
"type": "n8n-nodes-base.if",
"notes": "Filters out keywords that were not found in GSC data, only passing through keywords that actually have ranking performance data. Prevents AI analysis of keywords with no GSC presence.",
"position": [
1424,
656
],
"parameters": {
"options": {
"ignoreCase": true
},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "ccec25a9-75f6-4c82-a93f-aafb28aa3633",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $json.status }}",
"rightValue": "not_found_in_gsc"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "df3c0be2-7000-4821-8eb2-bbd9fee541f6",
"name": "GSC 데이터 가져오기 (클라이언트 2)",
"type": "n8n-nodes-base.httpRequest",
"notes": "Retrieves Google Search Console data for Client 2 using domain property format. Collects 30 days of search analytics data including keywords, pages, positions, and engagement metrics.",
"onError": "continueRegularOutput",
"position": [
48,
464
],
"parameters": {
"url": "=https://www.googleapis.com/webmasters/v3/sites/sc-domain:{{ $json['Client Website'] }}/searchAnalytics/query",
"method": "POST",
"options": {},
"jsonBody": "={\n \"startDate\": \"{{ $now.minus(30, 'days').format('yyyy-MM-dd') }}\",\n \"endDate\": \"{{ $now.format('yyyy-MM-dd') }}\",\n \"dimensions\": [\"query\", \"page\"]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleOAuth2Api"
},
"executeOnce": true,
"typeVersion": 4.2
},
{
"id": "88d5018c-546f-4299-9ad9-d77065450cb6",
"name": "GSC 데이터 가져오기 (클라이언트 3)",
"type": "n8n-nodes-base.httpRequest",
"notes": "Connects to Google Search Console API for Client 3 to extract search performance data. Gathers keyword rankings, page URLs, positions, clicks, impressions, and CTR for the past 30 days.",
"onError": "continueRegularOutput",
"position": [
48,
848
],
"parameters": {
"url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($json['Client Website']) }}/searchAnalytics/query",
"method": "POST",
"options": {},
"jsonBody": "={\n \"startDate\": \"{{ $now.minus(30, 'days').format('yyyy-MM-dd') }}\",\n \"endDate\": \"{{ $now.format('yyyy-MM-dd') }}\",\n \"dimensions\": [\"query\", \"page\"]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleOAuth2Api"
},
"executeOnce": true,
"typeVersion": 4.2
},
{
"id": "a7f0a16a-ca27-4bb2-b5ba-5b74759f5f70",
"name": "GSC 데이터 가져오기 (클라이언트 4)",
"type": "n8n-nodes-base.httpRequest",
"notes": "Pulls Google Search Console analytics data for Client 4 covering the last 30 days. Retrieves comprehensive search performance metrics including keyword-page relationships and ranking positions.",
"onError": "continueRegularOutput",
"position": [
48,
1040
],
"parameters": {
"url": "=https://www.googleapis.com/webmasters/v3/sites/{{ encodeURIComponent($json['Client Website']) }}/searchAnalytics/query",
"method": "POST",
"options": {},
"jsonBody": "={\n \"startDate\": \"{{ $now.minus(30, 'days').format('yyyy-MM-dd') }}\",\n \"endDate\": \"{{ $now.format('yyyy-MM-dd') }}\",\n \"dimensions\": [\"query\", \"page\"]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "googleOAuth2Api"
},
"executeOnce": true,
"typeVersion": 4.2
},
{
"id": "9d911368-b656-4519-8a24-36220cb9f4e0",
"name": "스티키 노트",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1008,
416
],
"parameters": {
"width": 480,
"height": 416,
"content": "## Monitor Keywords Sheet for Changes\nMonitors the Keywords Google Sheet for any changes and triggers the workflow when modifications are detected. Polls every minute to ensure real-time processing of keyword updates.\n\n## Fetch Client Website URLs\nRetrieves the list of client website URLs from the Google Sheet. This data is used to determine which clients to process and route them to their respective GSC data collection paths.\n"
},
"typeVersion": 1
},
{
"id": "ddd27041-06de-475b-bb47-2d1a6032be6c",
"name": "스티키 노트2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-64,
-48
],
"parameters": {
"width": 656,
"height": 1248,
"content": "## Fetch GSC Data (Client 1-4)\nMakes API call to Google Search Console for Client [X] to retrieve the last 30 days of search performance data. Gets keyword-page combinations with position, clicks, impressions, and CTR metrics.\n\n## Group GSC Data by Keyword (Client 1-4)\nGroups Google Search Console data by keyword for Client [X]. Takes raw GSC response and organizes it by keyword, with each keyword containing an array of URLs that rank for it, including position, clicks, impressions, and CTR data.\n"
},
"typeVersion": 1
},
{
"id": "581173a3-79e0-415d-99d2-a971bb5318d1",
"name": "스티키 노트4",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
368
],
"parameters": {
"height": 448,
"content": "## Match Keywords from Sheet with GSC Data\nCross-references target keywords from the Google Sheet with actual GSC performance data. Identifies which target keywords are ranking in GSC and which are missing, adding status flags for tracking."
},
"typeVersion": 1
},
{
"id": "f6343388-a09f-433d-94dc-a6c7d9195eec",
"name": "스티키 노트5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1360,
64
],
"parameters": {
"width": 1104,
"height": 816,
"content": "## Filter Keywords Found in GSC\nFilters out keywords that were not found in GSC data, only passing through keywords that actually have ranking performance data. Prevents AI analysis of keywords with no GSC presence.\n## Analyze Keyword Cannibalization Risk\nUses AI to analyze keyword cannibalization risk by examining how many pages from the same domain rank for each keyword. Categorizes risk as High, Moderate, Low, or No risk based on page count and performance distribution.\n## Save Cannibalization Analysis Results\nWrites the final cannibalization analysis results back to Google Sheets. Updates or appends rows with risk levels, reasoning, observations, remediation steps, and all associated keyword data.\n"
},
"typeVersion": 1
},
{
"id": "2e15b4f3-f8ac-41f6-a50f-beb2af6f384d",
"name": "스티키 노트1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1664,
64
],
"parameters": {
"width": 432,
"height": 1040,
"content": "## Keyword Cannibalization Detection Workflow Summary\n### Overview\nThis n8n workflow is an automated keyword cannibalization detection system that monitors multiple client websites and analyzes their search performance using Google Search Console data and AI-powered risk assessment.\n\n### Workflow Process\n🔄 Automated Trigger: The workflow monitors a Google Sheets document for keyword changes, triggering execution every minute when modifications are detected to ensure real-time processing.\n\n📊 Multi-Client Data Collection: The system simultaneously processes up to 4 different client websites through intelligent routing nodes that direct each client's data to dedicated processing paths based on URL pattern matching.\n\n🔍 GSC Data Extraction: For each client, the workflow makes API calls to Google Search Console to retrieve 30 days of search performance data, collecting keyword rankings, page URLs, positions, clicks, impressions, and CTR metrics.\n\n⚙️ Data Transformation: Raw GSC API responses are processed through JavaScript code nodes that group flat data by keywords, creating structured datasets where each keyword contains all competing URLs with their complete performance metrics.\n\n🔗 Data Integration: A merge node combines GSC data from all clients with target keywords from the Google Sheet, then cross-references to identify which keywords are actually ranking versus those missing from search results.\n\n🤖 AI-Powered Analysis: The system uses GPT-4o to analyze keyword cannibalization risk by examining how many pages from the same domain compete for each keyword, automatically categorizing risk levels as High (5+ pages), Moderate (3+ pages), Low (2 pages with clear dominance), or No Risk (single page).\n\n💾 Automated Reporting: Final analysis results are written back to Google Sheets with comprehensive data including risk assessments, detailed reasoning, observations, actionable remediation steps, and complete performance metrics for client reporting."
},
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"f78e179c-dedd-4d79-9f96-ab40fbe40f3f": {
"main": [
[
{
"node": "5a72cf8f-cf73-4811-9b55-807f7966e826",
"type": "main",
"index": 0
}
]
]
},
"f44239f6-1831-4546-a829-2abf27d4eb2e": {
"main": [
[
{
"node": "df3c0be2-7000-4821-8eb2-bbd9fee541f6",
"type": "main",
"index": 0
}
]
]
},
"fc7efa81-5870-4bc7-bd21-2181214976ff": {
"main": [
[
{
"node": "88d5018c-546f-4299-9ad9-d77065450cb6",
"type": "main",
"index": 0
}
]
]
},
"c91f5ab4-29a1-4f83-b570-3fdf4b3e7c1a": {
"main": [
[
{
"node": "a7f0a16a-ca27-4bb2-b5ba-5b74759f5f70",
"type": "main",
"index": 0
}
]
]
},
"0957bb5f-6200-40a4-a61a-002c965b53ee": {
"ai_languageModel": [
[
{
"node": "2f8ae342-1586-4f0f-8cd5-d650c4a62b30",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"2c0ba70f-00df-4be9-98a2-7969653e10db": {
"main": [
[
{
"node": "f78e179c-dedd-4d79-9f96-ab40fbe40f3f",
"type": "main",
"index": 0
},
{
"node": "f44239f6-1831-4546-a829-2abf27d4eb2e",
"type": "main",
"index": 0
},
{
"node": "fc7efa81-5870-4bc7-bd21-2181214976ff",
"type": "main",
"index": 0
},
{
"node": "c91f5ab4-29a1-4f83-b570-3fdf4b3e7c1a",
"type": "main",
"index": 0
}
]
]
},
"5a72cf8f-cf73-4811-9b55-807f7966e826": {
"main": [
[
{
"node": "7f125793-0b03-4466-b25b-1ef3bb4a76d4",
"type": "main",
"index": 0
}
]
]
},
"df3c0be2-7000-4821-8eb2-bbd9fee541f6": {
"main": [
[
{
"node": "90168f30-8e88-4b5a-9a80-586ad28f5a8f",
"type": "main",
"index": 0
}
]
]
},
"88d5018c-546f-4299-9ad9-d77065450cb6": {
"main": [
[
{
"node": "866dd165-dd86-43b9-b24e-e54ade95c42c",
"type": "main",
"index": 0
}
]
]
},
"a7f0a16a-ca27-4bb2-b5ba-5b74759f5f70": {
"main": [
[
{
"node": "e679fffe-fb2b-4cff-b5a0-104179212ac0",
"type": "main",
"index": 0
}
]
]
},
"94c42a7f-50aa-48c9-9643-58eeaddd8ff8": {
"main": [
[
{
"node": "d79e5c22-ac7a-43f9-af04-0997fc7af3ce",
"type": "main",
"index": 0
}
]
]
},
"03e271fe-0bfd-4f75-85af-cfdf0bcb8ac0": {
"main": [
[
{
"node": "2f8ae342-1586-4f0f-8cd5-d650c4a62b30",
"type": "main",
"index": 0
}
],
[
{
"node": "6dbee777-42fc-4576-a27b-4650af47455e",
"type": "main",
"index": 0
}
]
]
},
"a6a60ba4-3b48-4750-af64-bf19c22f6b5c": {
"main": [
[
{
"node": "94c42a7f-50aa-48c9-9643-58eeaddd8ff8",
"type": "main",
"index": 2
}
]
]
},
"7ab77205-1560-45ae-8a94-fb088dcfa62e": {
"main": [
[
{
"node": "2c0ba70f-00df-4be9-98a2-7969653e10db",
"type": "main",
"index": 0
},
{
"node": "a6a60ba4-3b48-4750-af64-bf19c22f6b5c",
"type": "main",
"index": 0
}
]
]
},
"2f8ae342-1586-4f0f-8cd5-d650c4a62b30": {
"main": [
[
{
"node": "6dbee777-42fc-4576-a27b-4650af47455e",
"type": "main",
"index": 0
}
]
]
},
"7f125793-0b03-4466-b25b-1ef3bb4a76d4": {
"main": [
[
{
"node": "94c42a7f-50aa-48c9-9643-58eeaddd8ff8",
"type": "main",
"index": 0
}
]
]
},
"90168f30-8e88-4b5a-9a80-586ad28f5a8f": {
"main": [
[
{
"node": "94c42a7f-50aa-48c9-9643-58eeaddd8ff8",
"type": "main",
"index": 1
}
]
]
},
"866dd165-dd86-43b9-b24e-e54ade95c42c": {
"main": [
[
{
"node": "94c42a7f-50aa-48c9-9643-58eeaddd8ff8",
"type": "main",
"index": 3
}
]
]
},
"e679fffe-fb2b-4cff-b5a0-104179212ac0": {
"main": [
[
{
"node": "94c42a7f-50aa-48c9-9643-58eeaddd8ff8",
"type": "main",
"index": 4
}
]
]
},
"503e683a-53c1-4aed-a86d-49ccdecd8b84": {
"ai_outputParser": [
[
{
"node": "2f8ae342-1586-4f0f-8cd5-d650c4a62b30",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"d79e5c22-ac7a-43f9-af04-0997fc7af3ce": {
"main": [
[
{
"node": "03e271fe-0bfd-4f75-85af-cfdf0bcb8ac0",
"type": "main",
"index": 0
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - 콘텐츠 제작, 멀티모달 AI
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
블로그 게시자 – 완전한 AI 기반 콘텐츠 연구, 제작, 최적화 및 게시 자동화
Gemini, Ideogram AI 및 WordPress를 사용한 블로그 생성 및 게시 자동화
If
Set
Code
+
If
Set
Code
35 노드Incrementors
콘텐츠 제작
WordPress 블로그 자동화 프로페셔널 에디션(심층 연구) v2.1 마켓
GPT-4o, Perplexity AI 및 다국어 지원을 사용한 SEO 최적화 블로그 생성 자동화
If
Set
Xml
+
If
Set
Xml
125 노드Daniel Ng
콘텐츠 제작
1. 플레이리스트 상세 설정 로봇 복사본
Suno, GPT-4, Runway, Creatomate로 AI 생성 YouTube 음악 플레이리스트 생성
If
Set
Code
+
If
Set
Code
203 노드Joseph
콘텐츠 제작
제 작업 흐름9_구형
사용 MagicHour와 Gemini로 Google Sheets에서 매일 YouTube 비디오 생성
If
Set
Code
+
If
Set
Code
31 노드Divyansh Chauhan
콘텐츠 제작
💥 NanoBanana, Seedream 4, ChatGPT Image 및 Veo 3를 사용한 동영상 광고 자동화 - VIDE
AI(NanoBanana, Seedream, GPT-4o, Veo 3)를 사용하여 비디오 광고 캠페인 자동화 및 게시
Set
Code
Wait
+
Set
Code
Wait
63 노드Dr. Firas
콘텐츠 제작
OpenAI, RunwayML, ElevenLabs를 사용한 무면식 숏폼 비디오 자동화
OpenAI, RunwayML, ElevenLabs를 사용한 무면쇼트 비디오 자동화: 스크립트부터 소셜 미디어까지
Set
Code
Wait
+
Set
Code
Wait
56 노드LeeWei
콘텐츠 제작