ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

n8n教程:掌握内置方法与变量,轻松驾驭数据处理

n8n教程:掌握内置方法与变量,轻松驾驭数据处理 第一次用n8n搭一个数据清洗工作流时我差点在表达式这一关直接弃坑明明Webhook已经把一整包JSON丢进来了结果到下一节点想取某个字段不是undefined就是Cannot read properties of null。后来我才明白问题不在数据而在我自己对n8n内置方法和变量作用域的认知几乎等于零。这篇就把我踩出来的这些东西系统整理一遍。适合两类人一是正在用n8n写工作流、但每次碰到数据处理都在表达式和代码节点之间反复横跳的二是刚入坑、想弄清楚$json、$node、$env到底怎么用、为什么别人写的一行表达式能顶我好几个节点的。n8n教程掌握内置方法与变量轻松驾驭数据处理1. 数据在n8n里到底怎么流动先理解节点、item与数据类型1.1 每个节点其实都是一台小型数据处理器很多人把n8n的工作流想成一条线串好几个工具但更准确的理解是每个节点都是一台独立的数据处理器。你从Webhook节点收到一段JSON它会把这段JSON变成输出下一个节点拿到这份输出做一次变换再变成新的JSON传给下下个节点。每次传递发生时数据都会被完整地复制一份出来而不是像指针一样原地修改。这一点非常重要因为你在A节点改了某个字段完全不影响B节点收到的原始数据——除非你显式地把修改后的字段写回输出。n8n里的数据基本单位是item条目。默认情况下你的工作流可能会一次处理多条数据。比如你用HTTP Request节点拉了一个列表接口返回100条记录那这一节点的输出就是100个item后面连接的节点默认会依次处理这100个item每一条都走一遍整个流程。数据处理的核心就是搞清楚当前节点到底同时面对多少个item以及你希望操作的是单条还是批量。1.2$json与$node的区别一个管当前一个管全局这是n8n新手最容易混淆的两个内置变量。$json表示当前正在处理的这条item的完整JSON对象。在表达式里写{{ $json.email }}取到的就是当前这条数据的email字段。它的特点是跟着当前数据走如果工作流在一个循环里跑每轮循环中$json的内容都会变表达式会随着当前item重新计算。$node则提供的是图上任意节点的输出访问。写法是{{ $node[某个节点名].json.字段名 }}或者简写为$node.某个节点名.json.字段名。它等同于你去查看那个节点最后一次执行后留下来的数据。这个访问方式在你做跨节点数据合并时极其有用因为上游节点的数据可能不会自动往后传全靠你手动用$node把它捞回来。还有一个关键区别$json只在它所在的表达式求值的那一瞬间有效而$node即使在链路末尾也能回看起点Webhook接收到的原始报文。所以我经常跟人开玩笑说$json是眼前的数据$node是备忘录里的数据。1.3 数据类型藏着的大坑字符串和数字从来不会自动替你转换数据处理翻车的第二大头是类型不匹配。n8n表达式底层跑的是JavaScript所以你必须对JavaScript的类型转换心里有数。JSON里的数字是数字字符串是字符串。如果你用{{ $json.age 1 }}而age被Webhook解析成了字符串30你得到的不是31而是字符串301。反过来如果你在一个拼字符串的场景里忘了把数字转成字符串可能输出又是一堆莫名其妙的结果。我建议你在做关键字段的清洗前先花10秒钟确认类型。想快速看的话在表达式编辑器里试一下{{ typeof $json.age }}返回值能直接告诉你它是string还是number。数据类型决定了你后续所有内置方法能不能正常调用——hello.trim()没问题但42.trim()会直接报错。2. 内置方法不是越多越好按数据处理场景挑工具2.1 把内置方法当成表达式里的瑞士军刀n8n的表达式远比很多人以为的强。它不只是取字段、拼字符串它支持绝大多数JavaScript原生方法比如字符串的trim()、toLowerCase()、split()、replace()数组的map()、filter()、find()JSON.parse()Math.round()等等。再加上n8n自己封装好的内置方法和变量比如日期时间的$now、$today查询用的$jmespath()你几乎能在表达式里完成70%的常见数据变换。但能和该是两回事。我的经验是表达式适合做字段级的小变换比如把用户输入的邮箱统一转小写、给手机号做格式校验、在多个字段之间拼接出一个完整名称。一旦你开始写超过一行的嵌套逻辑表达式就会变得难读、难调试这时候应该考虑用可视化节点或代码节点。2.2 常用内置方法和节点选型对照表我按数据处理中最高频的几个场景整理了一张表帮你快速决定某个任务该用表达式、可视化节点还是代码节点处理场景推荐方式示例/说明字符串清洗去除空格、统一大小写表达式字符串方法{{ $json.name.trim().toLowerCase() }}字段重命名/新增字段Edit Fields新版或Set节点可视化配置最直观按条件过滤数据Filter节点或IF节点可视化配置条件即可不必写代码数组拆成多条itemSplit Out节点把一个数组字段拆成多个独立item多条item合并成数组Aggregate节点适用于把分散数据聚合成一个列表日期时间格式化DateTime节点可以指定时区和目标格式复杂查询/深层过滤$jmespath()表达式适合在JSON里做条件查询多步逻辑、循环处理、异常捕获Code节点写JavaScript拥有完整编程能力这张表的核心思路是能可视化配置的就别硬写表达式能写短表达式的就别开代码节点。这不是说谁更厉害而是为了让自己三个月后还能一眼看懂工作流在干什么。2.3 为什么复杂处理要果断上Code节点我见过不少人在表达式里硬堆三四个嵌套方法结果运行报错后根本分不清是哪一层出了问题。比如你想把一个数组字段里的每个元素都取出来做个聚合表达式写法很容易绕晕而换一个Code节点逻辑会清晰得多。Code节点的输入输出结构是接收items数组返回新的items数组。一个非常典型的JavaScript写法是return items.map((item) { const d item.json; return { json: { fullName: ${d.firstName} ${d.lastName}.trim(), ageNumber: Number(d.age), emailLower: (d.email || ).toLowerCase(), }, }; });这样做的优势不只是可读性还在于你可以自由地加console.log做调试、写try...catch做异常处理这是表达式很难替代的。判断标准很简单如果你发现自己在表达式里写了超过两次.map()或.filter()或者开始怀疑这段逻辑我下周还能看懂吗那就转Code节点。3. 变量的三种级别与重跑陷阱搞清作用域才能不丢数据3.1 节点级、工作流级、环境级变量的分层管理n8n里的变量其实分散在三层作用域里很多人只用过第一层导致数据流经常断。节点级变量就是$json以及节点输出中你能访问到的字段。它的生命周期只存在于当前工作流的执行过程中。当前item被处理完后续节点再往回看$node的时候看到的也仅仅是那个节点最后输出的快照不是实时变更。工作流级变量是我自己习惯的叫法指的是你通过Set节点、Edit Fields节点或Code节点主动保存的在节点间传递的中间数据。比如你可以在工作流起点放一个Code节点把一些全局配置项比如默认时区、默认货币符号塞进输出后面所有节点用$node[配置节点].json来引用。这样数据就能背着走不需要在每个节点里重复写死。环境级变量对应的是$env。你可以在n8n的配置文件或环境变量里定义好API密钥、数据库连接串、业务开关等然后在任意节点用{{ $env.DATABASE_URL }}引用。好处很明显工作流本身是干净的可复用的换环境你只要改配置不需要改每个表达式的硬编码值。这三个级别各有各的适用场景。我见过有人把所有密码直接写进表达式里虽然能跑通但工作流一旦导出给团队其他人用就是个巨大的安全隐患。数据处理的数据可不仅仅指业务数据还包括你工作流里的敏感配置。3.2 变量赋值的正确姿势Set节点与Code节点怎么选如果你想新增、修改或者重命名字段n8n老版本里常用Set节点新版本里更推荐Edit Fields节点。它们的本质是对当前item的JSON做一层可变映射你可以选择字段是静态值还是从表达式动态计算。实操中我强烈建议把赋值和计算分开看待。单字段、低逻辑的赋值用Edit Fields节点一旦涉及循环、条件判断、临时变量请直接放到Code节点里。因为Code节点的变量是真正的JavaScript变量你可以写const tempVar ...可以让它在代码块内部自由流转不会污染后续节点。这样从职责上划分工作流更清晰可视化节点负责看得见的字段调整Code节点负责看不见的逻辑计算。3.3 重跑工作流时变量会怎样影响结果数据处理场景经常会碰到工作流重跑比如你调接口失败后手动点了Execute node或者重跑了整个工作流。这里有个关键认知$node引用的是那个节点最近一次执行留下的输出。如果你在重跑时跳过了某个节点或者某个节点执行出错没产出数据后面引用它字段的表达式就会变成undefined或者直接报错。我在实际调试中遇到过非常典型的场景工作流第一步用Webhook接收数据第二步我用Code节点把数据清洗后存到一个临时变量第三步引用了$node[清洗节点].json.xxx。后来因为测试方便我单独重跑了第二步结果第三步引用的数据就变成了新执行的输出整个结果和线上完全对不上。所以我的建议是在生产工作流里尽量保持整体执行不要频繁单独重跑中间节点如果确实需要调试记得最终以完整执行为准。另外一个值得注意的点是n8n的执行数据面板里会记录每次执行时每个节点的状态。你在看为什么这个变量是这个值时不要只盯着表达式本身要去点开上游节点看它实际输出里的字段名和结构。很多时候不是表达式写错了而是上游节点输出结构的字段命名跟你预期不一样。4. 实战演练用一个Webhook清洗工作流把前面知识串起来4.1 场景设定杂乱无章的报名数据假设我们做活动报名系统前端表单通过Webhook把报名记录发到n8n。原始数据长这样[ { name: 张三 , contact_email: ZHANGSANEXAMPLE.COM, age: 28, note: , source: 官网-北京 }, { name: 李四, contact_email: null, age: 42, note: 老朋友介绍, source: 老用户 转介绍 } ]问题很明显姓名和来源字段有多余空格邮箱大小写不统一年龄有的是字符串有的是数字并有人超范围note字段可能为空source里混进了无意义的-分隔符。我们最终希望输出一份干净的数据写入数据库姓名去除首尾空格、邮箱统一小写、年龄转为数字且限定在18-60之间、note为空时填默认值、source按规则拆成渠道和地区两个字段。4.2 第一步用Edit Fields节点做字段级标准化我先放一个Edit Fields节点把明显能通过表达式一行搞定的字段处理掉。以邮箱为例表达式是{{ ($json.contact_email || ).toString().trim().toLowerCase() }}这里有一个关键细节contact_email可能为null所以先用|| 把它兜底成空字符串再调用字符串方法这样不会报错。年龄字段我直接用{{ Number($json.age) }}把它转成数字。姓名和note字段类似姓名用{{ $json.name.trim() }}note用{{ ($json.note || 无备注).trim() }}。这一步做完工作流里已经有一部分字段被清洗干净了但它还只是字段级处理解决不了过滤和拆分的问题。4.3 第二步用Code节点做逻辑拆分与过滤审核年龄范围和拆分source字段属于逻辑判断我宁可放在Code节点里一次讲清楚。我把上一节点的输出接进Code节点核心代码大致是这样return items.map((item) { const d item.json; const age Number(d.age); if (!d.emailLower || !d.emailLower.includes()) { return null; // 邮箱为空或格式不对直接丢弃 } if (age 18 || age 60) { return null; // 年龄不在范围内丢弃 } const [channel, region] (d.source || ) .split(-) .map(s s.trim()); return { json: { name: d.name, email: d.emailLower, age: age, note: d.note, channel: channel || 未知渠道, region: region || 未知地区, sourceRaw: d.source, }, }; }).filter(item item ! null);这段代码把判断并返回新对象集中在一个地方比在表达式里拆成五六个节点要直观得多。而且我把sourceRaw原始字段也保留下来方便后续审计或排查问题时看到底是哪条原始数据产生了问题。这里我的经验是清洗工作流里尽量保留一份原始快照哪怕之后写入数据库时不用它排查问题时它就是你的救命稻草。4.4 第三步与数据库节点对接的变量映射清洗完成后的数据后面接一个PostgreSQL或Google Sheets节点。以PostgreSQL为例你需要把字段映射到数据库表的列。这个映射就能直接用内置变量$json了每一条传入数据库节点的item都对应一个$json里的字段。如果你希望把多条数据批量写入注意数据库节点一般有两种操作模式逐条插入和批量插入。逐条插入适合数据量小、每条都可能独立失败的场景批量插入性能更好但一条失败可能影响整批。数据处理场景里我一般选择逐条插入配合下文的错误处理更稳。映射时记得核对好类型数据库里的整数列千万别传字符串进去。4.5 第四步加上错误处理链路清洗数据不是只有成功这条路实际跑起来基本每天都能遇到奇怪的数据。我会在Code节点后面接一个If节点或分支判断用表达式检查清洗结果是否为空数组。如果为空直接把一条今日无有效数据的通知发到群聊如果非空才继续进入数据库写入。同时所有节点统一接一个Error Trigger错误触发分支遇到异常时把错误信息连同原始数据打包发到Telegram或企业微信群方便第一时间处理。这样整条链路的最终意义不是展示一个能跑通的小demo而是告诉你数据处理流程一定包含校验、清洗、过滤、容错、审计这五个部分缺任何一个线上都会还给你一个不定时炸弹。5. 表达式返回undefined时我是怎么一步步定位的5.1 先看执行数据面板别靠猜遇到表达式返回undefined第一反应不要是再改改看而是打开对应节点下方的执行输出面板把上游节点实际产出的JSON结构完整看一遍。我会重点关注三件事字段名跟表达式里写的一不一样、字段值是不是null、字段类型是不是我预期的。80%的undefined都死于这三件事。比如我在真实项目里遇到过Webhook返回的字段叫contact.email但我表达式里写的是$json.contact.email看着没问题吧结果Webhook实际返回的结构是{ contact: { email: ... } }这里没问题但如果你写$json[contact.email]那取到的就是空——因为字段名包含点号你必须用中括号加引号的方式访问。这就是看结构和想当然之间的差距。5.2 善用表达式编辑器的实时预览n8n的表达式输入框通常自带一个实时预览区。你把表达式写下去下方会立刻显示在当前item上计算出的值。学会利用这个小窗口能省掉大量执行整个工作流的时间。我在测试新字段时会很自然地在预览区打一行{{ JSON.stringify($json) }}先把整个对象打印出来看清楚全貌再逐步取字段。这里有个小技巧如果你在表达式中使用JSON.stringify要注意输出的可能是一个字符串预览区里会带引号包裹。这是正常的不代表字段值是字符串。同理如果你要对数组做长度判断可以试试{{ $json.arr.length }}但要确保arr真的是数组而不是对象或字符串。5.3 常见类型转换失败与兜底写法我列几个出现频率最高的类型问题以及我的应对写法字段可能为null用($json.field || 默认值)做兜底但注意0和false也会被||忽略所以如果明确要保留0和false就得用($json.field ?? 默认值)。需要数字判断先Number($json.age)再比较别直接拿字符串和数字比。需要字符串拼接用模板字符串${a}${b}JS会自动把变量转成字符串比更直观。需要数组查找先确认字段是数组并用Array.isArray()校验再调用.find()或.includes()。需要日期比较统一用DateTime节点先格式化再比较字符串或时间戳别在表达式里直接做时间戳加减。5.4 我的调试铁律先打印、后处理、再联调最后分享我Debug数据工作流时自己反复用的一条流程。第一在每个清洗节点后面临时放一个Code节点里面只写一行console.log(JSON.stringify(items));把输出打印到日志里。第二先单独执行这个节点确认输入输出都对再往后接后续节点。第三全部节点联调时从执行数据的时间线视角看每个节点的耗时和输出大小这一步有时能意外发现某些节点因为数据量太大被拖慢的问题。等你确认某段表达式逻辑完全稳定再把这些临时调试节点删掉替换成正式的字段映射。这个习惯帮我少踩了至少一半的无谓的坑。数据处理的本质是减少不确定性而调试方法论的核心恰恰也是先消除变量、逐步缩小范围把问题锁死在最小闭环里。
返回列表