8
n8n 中文网amn8n.com

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
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "qfdeygimBULYjsqJ",
  "meta": {
    "instanceId": "c9b372d6af59e3f6d8835345664babb3bd2c029f9d3764b59df1829e2ae18ec7",
    "templateCredsSetupCompleted": true
  },
  "name": "Revolut支出手动处理",
  "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账单分析器"
      },
      "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": "## 从Supabase获取分类"
      },
      "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": "## 下载并转换账单"
      },
      "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分类器"
      },
      "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": "## 插入到supabase"
      },
      "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": {
    "Merge": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          },
          {
            "node": "Create a row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Aggregate": {
      "main": [
        [
          {
            "node": "Download extract",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Uniq Hash": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create a row": {
      "main": [
        [],
        []
      ]
    },
    "gpt-4.1-mini": {
      "ai_languageModel": [
        [
          {
            "node": "Category extraction",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Get many rows": {
      "main": [
        [
          {
            "node": "Aggregate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Category extraction",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [],
        [
          {
            "node": "Extract merchant",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download extract": {
      "main": [
        [
          {
            "node": "Extract from File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract merchant": {
      "main": [
        [
          {
            "node": "Category extraction",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Extract from File": {
      "main": [
        [
          {
            "node": "Normalize content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize content": {
      "main": [
        [
          {
            "node": "Uniq Hash",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Category extraction": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking ‘Execute workflow’": {
      "main": [
        [
          {
            "node": "Get many rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 内容创作, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量19
分类2
节点类型14
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 查看

分享此工作流