Revolut 지출 자동 분류
고급
이것은Content Creation, Multimodal AI분야의자동화 워크플로우로, 19개의 노드를 포함합니다.주로 Set, Code, Merge, Crypto, Supabase 등의 노드를 사용하며. GPT-4와 Supabase를 사용하여 Revolut 거래를 자동으로 분류합니다.
사전 요구사항
- •Supabase URL과 API Key
- •Google Drive API 인증 정보
- •OpenAI API Key
사용된 노드 (19)
워크플로우 미리보기
노드 연결 관계를 시각적으로 표시하며, 확대/축소 및 이동을 지원합니다
워크플로우 내보내기
다음 JSON 구성을 복사하여 n8n에 가져오면 이 워크플로우를 사용할 수 있습니다
{
"id": "qfdeygimBULYjsqJ",
"meta": {
"instanceId": "c9b372d6af59e3f6d8835345664babb3bd2c029f9d3764b59df1829e2ae18ec7",
"templateCredsSetupCompleted": true
},
"name": "Revolut Expenses Manual",
"tags": [
{
"id": "ea4Bb1csGvuESWWb",
"name": "Revolut",
"createdAt": "2025-08-11T13:32:47.326Z",
"updatedAt": "2025-08-11T13:32:47.326Z"
}
],
"nodes": [
{
"id": "44cf1317-7bac-45f6-a139-6979e1923496",
"name": "워크플로우 실행 시",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-2352,
16
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2ee0180a-e65f-4cf9-9d94-045844873105",
"name": "스티키 노트",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2960,
-352
],
"parameters": {
"width": 496,
"height": 800,
"content": "## Revolut Extracts Analyzer\n\n### This n8n template processes Revolut statements, normalizes transactions, and uses AI to categorize expenses automatically.\n### Use cases include detecting subscriptions, separating internal transfers, and building dashboards to track spending.\n---\n\n## How it works\n* **Get Categories from Supabase**\n* **Download & Transform**\n* **Loop Over Items**\n* **LLM Categorizer** \n* **Insert into Supabase**\n\n---\n\n## How to use\n* Start with the **manual trigger node** or replace it with a schedule/webhook. \n* Connect **Google Drive** to provide Revolut CSV files. \n* Ensure **Supabase** has tables for `transactions` and `categories`. \n* Extend with notifications, reports, or BI tools. \n\n---\n\n## Requirements\n* Google Drive for CSV files \n* Supabase tables for categories & transactions \n* LLM provider (OpenAI/Gemini)"
},
"typeVersion": 1
},
{
"id": "6070fcd4-1cd9-4ffe-a3d3-f7cd582ffc9d",
"name": "추출 파일 다운로드",
"type": "n8n-nodes-base.googleDrive",
"position": [
-1664,
16
],
"parameters": {
"fileId": {
"__rl": true,
"mode": "list",
"value": "1JAb-5Y4Pi2A7LePJ9wvEWVrg6iqN7_5l",
"cachedResultUrl": "https://drive.google.com/file/d/1JAb-5Y4Pi2A7LePJ9wvEWVrg6iqN7_5l/view?usp=drivesdk",
"cachedResultName": "agosto.csv"
},
"options": {},
"operation": "download"
},
"credentials": {
"googleDriveOAuth2Api": {
"id": "ehEt6oTyXIIzKB1E",
"name": "Google Drive - kermitdev9"
}
},
"typeVersion": 3
},
{
"id": "01c41ea4-df02-41ec-965f-43a75cb018b4",
"name": "행 생성",
"type": "n8n-nodes-base.supabase",
"onError": "continueErrorOutput",
"position": [
112,
16
],
"parameters": {
"tableId": "transactions",
"fieldsUi": {
"fieldValues": [
{
"fieldId": "completed_date",
"fieldValue": "={{ $json.completed_date }}"
},
{
"fieldId": "started_date",
"fieldValue": "={{ $json.started_date }}"
},
{
"fieldId": "description_original",
"fieldValue": "={{ $json.description_original }}"
},
{
"fieldId": "description_clean",
"fieldValue": "={{ $json.description_clean }}"
},
{
"fieldId": "merchant_name",
"fieldValue": "={{ $json.output.merchant_name }}"
},
{
"fieldId": "category_name",
"fieldValue": "={{ $json.output.category }}"
},
{
"fieldId": "type",
"fieldValue": "={{ $json.type }}"
},
{
"fieldId": "state",
"fieldValue": "={{ $json.state }}"
},
{
"fieldId": "fee",
"fieldValue": "={{ $json.fee }}"
},
{
"fieldId": "currency",
"fieldValue": "={{ $json.currency }}"
},
{
"fieldId": "balance",
"fieldValue": "={{ $json.balance }}"
},
{
"fieldId": "is_subscription",
"fieldValue": "={{ $json.output.is_subscription }}"
},
{
"fieldId": "is_internal",
"fieldValue": "={{ $json.output.is_internal }}"
},
{
"fieldId": "amount",
"fieldValue": "={{ $json.amount }}"
},
{
"fieldId": "uniq_hash",
"fieldValue": "={{ $json.uniq_hash }}"
},
{
"fieldId": "user_id",
"fieldValue": "={{ $json.body?.userId || null }}"
}
]
}
},
"credentials": {
"supabaseApi": {
"id": "bzLzwSBr9xr18RGW",
"name": "Kermitdev9 Supabase"
}
},
"typeVersion": 1
},
{
"id": "45650b46-adeb-4b19-a28e-166ca063f42a",
"name": "내용 정규화",
"type": "n8n-nodes-base.set",
"position": [
-1280,
16
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "42e6f2d4-7530-41ed-95a9-4f8b672e974a",
"name": "type",
"type": "string",
"value": "={{ $json[Object.keys($json)[0]] }}"
},
{
"id": "44447d5c-23b7-470f-982a-b6366035b1eb",
"name": "product",
"type": "string",
"value": "={{ $json[Object.keys($json)[1]].trim() }}"
},
{
"id": "44af0831-d9c6-4ec7-a729-58368b38fbb7",
"name": "started_date",
"type": "string",
"value": "={{ (($json[Object.keys($json)[2]]).replace(\" \", \"T\")) + \"Z\" }}"
},
{
"id": "63e4b1f0-a3b4-438c-b21d-347a8e0702c6",
"name": "completed_date",
"type": "string",
"value": "={{ (( $json[Object.keys($json)[3]]).replace(\" \", \"T\")) + \"Z\" }}"
},
{
"id": "842a5afe-7837-46e0-84cd-a894bbafa58c",
"name": "description_original",
"type": "string",
"value": "={{$json[Object.keys($json)[4]]}}"
},
{
"id": "0cdd36f6-cb48-4512-bc6a-3ce285b09aea",
"name": "description_clean",
"type": "string",
"value": "={{ ($json[Object.keys($json)[4]] || \"\").toLowerCase().replace(/\\s+/g, \" \").trim() }}"
},
{
"id": "9fbfbac5-2d02-4db6-8cb7-9177a66b1e37",
"name": "amount",
"type": "string",
"value": "={{ $json[Object.keys($json)[5]] }}"
},
{
"id": "6a6f1bf9-85ae-47fc-a25e-8c5433e6ac0d",
"name": "fee",
"type": "string",
"value": "={{ $json[Object.keys($json)[6]] }}"
},
{
"id": "8585ec37-33a1-4d4c-9b71-456809ab10da",
"name": "currency",
"type": "string",
"value": "={{ $json[Object.keys($json)[7]].toUpperCase() }}"
},
{
"id": "9b3a2caf-3548-4149-952e-86821f17361b",
"name": "state",
"type": "string",
"value": "={{ $json[Object.keys($json)[8]].toUpperCase() }}"
},
{
"id": "f2a8a409-9f3a-477c-8fe5-0eed9570071c",
"name": "balance",
"type": "string",
"value": "={{ $json[Object.keys($json)[9]] }}"
},
{
"id": "8737fc90-2cdf-4ce1-a793-308ee738bdbf",
"name": "raw",
"type": "string",
"value": "={{ $json }}"
},
{
"id": "c617cc25-10f6-47c0-84c8-06081bfe6672",
"name": "uniq_hash",
"type": "string",
"value": "={{ \n ( $json[Object.keys($json)[3]] || \"\") + \"|\" +\n Number($json[Object.keys($json)[5]]).toFixed(2) + \"|\" +\n ( $json[Object.keys($json)[7]] || \"\").toUpperCase() + \"|\" +\n ( $json[Object.keys($json)[0]] || \"\").toUpperCase()\n}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "21ec8fe0-b1b6-404e-8e73-0501de474bb8",
"name": "가맹점 추출",
"type": "n8n-nodes-base.code",
"position": [
-672,
32
],
"parameters": {
"jsCode": "// n8n Function Item\n// Entrada: $json.description_clean (minúsculas, sin espacios dobles)\n// Salida: merchant_candidate, merchant_candidate_normalized\n\nfunction norm(s = \"\") {\n return s\n .toLowerCase()\n .normalize(\"NFD\").replace(/[\\u0300-\\u036f]/g, \"\")\n .replace(/[^a-z0-9\\s]/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nlet desc = String($json.description_clean || \"\").trim();\nif (!desc) return [{ ...$json, merchant_candidate: \"unknown\", merchant_candidate_normalized: \"unknown\" }];\n\n// ruido común\nconst STOP_PHRASES = [\n \"payment from\",\"payment to\",\"transfer from\",\"transfer to\",\"bank transfer\",\n \"card payment\",\"pos purchase\",\"card purchase\",\"transaction at\",\"merchant\",\n \"bill\",\"charge\",\"revolut\",\"sas\",\"sarl\",\"sa\",\"ag\",\"gmbh\",\"spa\",\"srl\",\"ltd\",\n \"limited\",\"inc\",\"corp\",\"co\",\"store\",\"shop\"\n];\n\nconst NOISE_REGEX = [\n /\\b(lu|be|fr|de|nl|es|it|uk|us)\\b/g,\n /\\bcom\\b/g,\n /\\bwww\\b/g,\n /https?:\\/\\/\\S+/g,\n /[0-9]{2,}/g\n];\n\ndesc = norm(desc);\nfor (const rx of NOISE_REGEX) desc = desc.replace(rx, \" \");\nfor (const p of STOP_PHRASES) {\n const rx = new RegExp(`\\\\b${p.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")}\\\\b`, \"g\");\n desc = desc.replace(rx, \" \");\n}\ndesc = desc.replace(/\\s+/g, \" \").trim();\n\n// canónicos rápidos\nconst CANONICAL = [\n { re: /\\bamzn|amazon|prime\\s+video|amazon\\s+prime\\b/, name: \"Amazon\" },\n { re: /\\bapple|itunes|apple\\.com\\b/, name: \"Apple\" },\n { re: /\\bspotify\\b/, name: \"Spotify\" },\n { re: /\\bnetflix\\b/, name: \"Netflix\" },\n { re: /\\buber\\b/, name: \"Uber\" },\n { re: /\\bbolt\\b/, name: \"Bolt\" },\n { re: /\\bcarrefour\\b/, name: \"Carrefour\" },\n { re: /\\blidl\\b/, name: \"Lidl\" },\n { re: /\\baldi\\b/, name: \"Aldi\" },\n { re: /\\bdelhaize\\b/, name: \"Delhaize\" }\n];\n\nlet canonical = null;\nfor (const { re, name } of CANONICAL) { if (re.test(desc)) { canonical = name; break; } }\n\n// candidato por primeras palabras relevantes\nconst CONNECTORS = new Set([\"the\",\"and\",\"at\",\"for\",\"from\",\"to\",\"on\",\"in\",\"of\"]);\nlet cand = desc.split(\" \").filter(w => w && !CONNECTORS.has(w)).slice(0, 3).join(\" \").trim();\n\nconst merchant_candidate = canonical || cand || (desc || \"unknown\");\nconst merchant_candidate_normalized = norm(merchant_candidate);\n\nreturn [{\n ...$json,\n merchant_candidate: merchant_candidate_normalized\n}];\n"
},
"typeVersion": 2
},
{
"id": "51b8f681-4242-4692-8873-ea326a3dc312",
"name": "항목 루프",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-896,
16
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "48f035d1-2193-4ef1-86bf-9a385ddddadb",
"name": "카테고리 추출",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
-496,
-64
],
"parameters": {
"text": "={{ $('Normalize content').item.json.raw }}",
"batching": {},
"messages": {
"messageValues": [
{
"message": "=You are a financial transaction classifier and merchant extractor.\nYou will receive a raw financial transaction in JSON format.\nYou must return a single valid JSON object following exactly the required schema.\n\nAllowed categories:\n{{ $('Aggregate').item.json.normalized_name }}\n\nInstructions:\n1. Read the raw transaction.\n2. Extract a merchant_name: a clean, standardized version of the business or entity involved.\n - If the description clearly contains a brand or business name, return it cleaned (no codes, extra spaces, or special characters).\n - If there is no clear merchant, return \"Unknown\".\n3. Choose exactly ONE category from the allowed list based on the description, merchant, type, and amount.\n4. Set is_internal=true if the movement is between the user’s own accounts (top up, vault, transfer to self).\n5. Set is_subscription=true if the transaction appears to be a recurring service charge (streaming, memberships, etc.).\n6. Set confidence between 0.0 and 1.0 based on how certain you are about the classification.\n7. Set rule_reason as a short, clear explanation of why you chose that category and merchant.\n8. Respond only with JSON. No extra text, no explanations outside the JSON.\n\nExample output:\n{\n \"merchant_name\": \"Spotify\",\n \"category\": \"Subscriptions\",\n \"is_subscription\": true,\n \"is_internal\": false,\n \"confidence\": 0.98,\n \"rule_reason\": \"The description contains 'Spotify', which is a well-known recurring music subscription service.\"\n}\n"
}
]
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 1.7
},
{
"id": "dbd5bb41-b6df-4f41-a15c-356d55121ccd",
"name": "병합",
"type": "n8n-nodes-base.merge",
"position": [
-144,
16
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineAll"
},
"typeVersion": 3.2
},
{
"id": "41f70e3c-ab40-4695-af06-cc1be6672331",
"name": "출력 파서",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
-304,
112
],
"parameters": {
"jsonSchemaExample": "{\n \"merchant_name\": \"Spotify\",\n \"category\": \"Subscriptions\",\n \"is_subscription\": true,\n \"is_internal\": false,\n \"confidence\": 0.98,\n \"rule_reason\": \"The description contains 'Spotify', which is a well-known recurring music subscription service.\"\n}"
},
"typeVersion": 1.3
},
{
"id": "fd6174c8-3790-447b-9223-ec575ed9d0c7",
"name": "여러 행 가져오기",
"type": "n8n-nodes-base.supabase",
"position": [
-2144,
16
],
"parameters": {
"tableId": "categories",
"operation": "getAll",
"returnAll": true
},
"credentials": {
"supabaseApi": {
"id": "bzLzwSBr9xr18RGW",
"name": "Kermitdev9 Supabase"
}
},
"typeVersion": 1
},
{
"id": "4ab626a6-ceb7-4893-b400-5fff770536c1",
"name": "집계",
"type": "n8n-nodes-base.aggregate",
"position": [
-1984,
16
],
"parameters": {
"options": {},
"fieldsToAggregate": {
"fieldToAggregate": [
{
"fieldToAggregate": "normalized_name"
}
]
}
},
"typeVersion": 1
},
{
"id": "5e9d2e5d-39ef-4de6-bc7a-fc093ae63a9e",
"name": "스티키 노트1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2192,
-192
],
"parameters": {
"color": 2,
"width": 368,
"height": 464,
"content": "## Get Categories from Supabase\nRetrieve categories from Supabase to feed into later LLM categorization phases."
},
"typeVersion": 1
},
{
"id": "01a5bac3-91ad-4d67-be6c-0379cd2b1ccd",
"name": "스티키 노트2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1776,
-192
],
"parameters": {
"color": 6,
"width": 832,
"height": 464,
"content": "## Download and Transform extraction\nDownload the file from Google Drive, extract and parse the CSV data, normalize and standardize its content, then generate a unique hash to ensure consistency and prevent duplicates."
},
"typeVersion": 1
},
{
"id": "c76a64ba-e9ae-4716-b11e-de03c87ee6ee",
"name": "스티키 노트3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-720,
-192
],
"parameters": {
"color": 2,
"width": 736,
"height": 464,
"content": "## LLM Categorizer\nIterate over all transactions, send each to the LLM together with the previously retrieved category list, and extract the assigned category plus flags for whether it is a subscription, an internal transfer, or other relevant markers."
},
"typeVersion": 1
},
{
"id": "1343af82-305f-488d-9de4-b878eaf95e95",
"name": "스티키 노트4",
"type": "n8n-nodes-base.stickyNote",
"position": [
96,
-192
],
"parameters": {
"color": 2,
"width": 336,
"height": 464,
"content": "## Insert into supabase\nThen insert into supabase avoiding duplicates"
},
"typeVersion": 1
},
{
"id": "af13cecf-f7a9-4ac3-aa81-420de0b26a3f",
"name": "고유 해시",
"type": "n8n-nodes-base.crypto",
"position": [
-1104,
16
],
"parameters": {
"value": "={{ $json.uniq_hash }}",
"dataPropertyName": "uniq_hash"
},
"typeVersion": 1
},
{
"id": "50638b77-608d-46be-a5fc-11983a3da235",
"name": "gpt-4.1-mini",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
-496,
112
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "gpt-4.1-mini"
},
"options": {
"temperature": 0.3
}
},
"credentials": {
"openAiApi": {
"id": "3X2EvLOOJbnKaXSS",
"name": "OpenAi account"
}
},
"typeVersion": 1.2
},
{
"id": "5ecee4f9-7527-48ed-bbbb-176c19f5ef84",
"name": "파일에서 추출",
"type": "n8n-nodes-base.extractFromFile",
"position": [
-1440,
16
],
"parameters": {
"options": {}
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "363d0f72-9959-4964-b2b5-e00ff29f4690",
"connections": {
"dbd5bb41-b6df-4f41-a15c-356d55121ccd": {
"main": [
[
{
"node": "51b8f681-4242-4692-8873-ea326a3dc312",
"type": "main",
"index": 0
},
{
"node": "01c41ea4-df02-41ec-965f-43a75cb018b4",
"type": "main",
"index": 0
}
]
]
},
"4ab626a6-ceb7-4893-b400-5fff770536c1": {
"main": [
[
{
"node": "6070fcd4-1cd9-4ffe-a3d3-f7cd582ffc9d",
"type": "main",
"index": 0
}
]
]
},
"af13cecf-f7a9-4ac3-aa81-420de0b26a3f": {
"main": [
[
{
"node": "51b8f681-4242-4692-8873-ea326a3dc312",
"type": "main",
"index": 0
}
]
]
},
"01c41ea4-df02-41ec-965f-43a75cb018b4": {
"main": [
[],
[]
]
},
"50638b77-608d-46be-a5fc-11983a3da235": {
"ai_languageModel": [
[
{
"node": "48f035d1-2193-4ef1-86bf-9a385ddddadb",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"fd6174c8-3790-447b-9223-ec575ed9d0c7": {
"main": [
[
{
"node": "4ab626a6-ceb7-4893-b400-5fff770536c1",
"type": "main",
"index": 0
}
]
]
},
"41f70e3c-ab40-4695-af06-cc1be6672331": {
"ai_outputParser": [
[
{
"node": "48f035d1-2193-4ef1-86bf-9a385ddddadb",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"51b8f681-4242-4692-8873-ea326a3dc312": {
"main": [
[],
[
{
"node": "21ec8fe0-b1b6-404e-8e73-0501de474bb8",
"type": "main",
"index": 0
}
]
]
},
"6070fcd4-1cd9-4ffe-a3d3-f7cd582ffc9d": {
"main": [
[
{
"node": "5ecee4f9-7527-48ed-bbbb-176c19f5ef84",
"type": "main",
"index": 0
}
]
]
},
"21ec8fe0-b1b6-404e-8e73-0501de474bb8": {
"main": [
[
{
"node": "48f035d1-2193-4ef1-86bf-9a385ddddadb",
"type": "main",
"index": 0
},
{
"node": "dbd5bb41-b6df-4f41-a15c-356d55121ccd",
"type": "main",
"index": 1
}
]
]
},
"5ecee4f9-7527-48ed-bbbb-176c19f5ef84": {
"main": [
[
{
"node": "45650b46-adeb-4b19-a28e-166ca063f42a",
"type": "main",
"index": 0
}
]
]
},
"45650b46-adeb-4b19-a28e-166ca063f42a": {
"main": [
[
{
"node": "af13cecf-f7a9-4ac3-aa81-420de0b26a3f",
"type": "main",
"index": 0
}
]
]
},
"48f035d1-2193-4ef1-86bf-9a385ddddadb": {
"main": [
[
{
"node": "dbd5bb41-b6df-4f41-a15c-356d55121ccd",
"type": "main",
"index": 0
}
]
]
},
"44cf1317-7bac-45f6-a139-6979e1923496": {
"main": [
[
{
"node": "fd6174c8-3790-447b-9223-ec575ed9d0c7",
"type": "main",
"index": 0
}
]
]
}
}
}자주 묻는 질문
이 워크플로우를 어떻게 사용하나요?
위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.
이 워크플로우는 어떤 시나리오에 적합한가요?
고급 - 콘텐츠 제작, 멀티모달 AI
유료인가요?
이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.
관련 워크플로우 추천
컨텍스트 혼합 RAG AI 콘텐츠
Google Drive에서 Supabase 상황 벡터 데이터베이스로 동기화, RAG 애플리케이션 사용
If
Set
Code
+
If
Set
Code
76 노드Michael Taleb
AI RAG
콘텐츠생성기 v3
AI驱动블로그자동화:사용GPT-4생성并게시SEO기사至WordPress및Twitter
If
Set
Code
+
If
Set
Code
144 노드Jay Emp0
콘텐츠 제작
WordPress 블로그 자동화 프로페셔널 에디션(심층 연구) v2.1 마켓
GPT-4o, Perplexity AI 및 다국어 지원을 사용한 SEO 최적화 블로그 생성 자동화
If
Set
Xml
+
If
Set
Xml
125 노드Daniel Ng
콘텐츠 제작
OpenAI, ElevenLabs 및 Fal.ai를 사용한 비디오, 팟캐스트 및 ASMR용 바이럴 콘텐츠 제작 자동화
OpenAI, ElevenLabs 및 Fal.ai를 사용한 비디오, 팟캐스트 및 ASMR용 바이럴 콘텐츠 제작 자동화
Set
Code
Wait
+
Set
Code
Wait
97 노드Adam Crafts
콘텐츠 제작
Perplexity와 GPT를 사용하여 WordPress에 SEO 최적화 블로그 생성, 키워드와 미디어 포함
Perplexity와 GPT를 사용하여 WordPress에 SEO 최적화 블로그를 만들어 키워드와 미디어 포함
Set
Code
Limit
+
Set
Code
Limit
124 노드Paul
콘텐츠 제작
특정 도구를 사용하여 WordPress에 SEO 최적화 블로그 생성
특정 도구를 사용하여 WordPress에 SEO 최적화 블로그 생성
Set
Code
Limit
+
Set
Code
Limit
124 노드Paul
콘텐츠 제작