用 n8n 做自动化,几乎都会撞上同一个需求:上游一次性返回了几百条数据,你却希望一条一条处理。给用户挨个发邮件、按订单逐条查物流、把接口返回的列表分批写入第三方系统……这些场景里,Loop Over Items 就是那个“救火队员”。它做的事情不复杂:把 n8n 工作流里的一组 items 按你指定的批次大小逐轮送进循环体,每轮只处理一条或一小批数据,全部处理完后再触发后续流程。
如果你是刚接触 n8n 不久,可能对这个节点既熟悉又陌生——节点面板里看得到它,但不确定它和普通节点的区别;如果你已经用过一段时间,大概率也踩过“循环里字段丢了”“循环次数不对”“不知道怎么在循环里引用当前这一条数据”之类的坑。这篇文章我会从实际工作流搭建的角度,把 Loop Over Items 的原理、参数、真实案例和排障经验一次讲透,尽量让你看完就能直接上手。
1. Loop Over Items 是什么,解决什么问题
1.1 它在 n8n 流程中的位置
Loop Over Items 是 n8n 内置的 Flow(流程控制)类节点,不需要额外安装社区节点。你打开左侧节点面板,在 Flow 分类下就能看到它。从名字也能猜到,它专门负责“遍历”:把输入节点传来的 items 数组拆开,按照设定的批次大小,一轮一轮地发给后面的节点去处理。
这里有一个 n8n 的基础概念必须先说清楚:n8n 中几乎所有普通节点,处理的都是“一组数据”,而不是“一条数据”。比如一个数据库查询节点查出了 100 行用户记录,它向下游输出的不是 1 个包含 100 行的整体对象,而是 100 个 item,每个 item 是一条用户记录。下一个节点拿到这 100 个 item,会对每个 item 各执行一次逻辑。这听起来好像天然就已经是循环了,为什么还要 Loop Over Items?
区别在于:普通节点拿到 100 个 item 时,虽然内部会逐个处理,但对使用者来说是一个整体批次,中间没有“轮次”的概念,你没法控制每一轮之间的节奏,也没法暂停下来做点别的。Loop Over Items 则把这个过程显式化了,它像一个闸门,一次只放一批数据过去,让后面的节点处理完这一批,再放下一批。这种“显式的轮次控制”,正是批量自动化场景里最需要的东西。
1.2 没有它时你会遇到什么麻烦
我早期用 n8n 时不太理解这个节点,觉得“反正下游节点本来就会对每个 item 跑一遍逻辑,直接连过去不就得了”。后来接到一个需求:从 CRM 里取回 500 个待回访客户,需要逐个调用第三方接口查询客户的最近订单,再根据结果发送回访短信。第一次我图省事,把查询订单的 HTTP Request 节点直接接在 CRM 查询后面,结果第三方接口瞬间被打爆,返回一大堆 429 限流错误,工作流直接失败,而且失败后根本不知道是哪个客户、哪一批请求出的问题。
这就是没有循环控制时的典型痛点:大批量数据一次性涌入,外部系统扛不住;中间某一条数据异常,整个批次全部失败,排障像大海捞针;遇到需要“上一条处理完才能处理下一条”的业务逻辑,更没有合适的位置做等待和判断。Loop Over Items 解决的就是这一类问题——它让批量数据处理从“粗放式的一梭子”变成了“可控的一轮一轮”,每条数据在什么时间点处理、处理到第几条了、这一批有没有异常,都变得清晰可见。
1.3 它擅长和不擅长的场景
根据我的实际使用经验,Loop Over Items 适合下面这几类场景:
- 需要对上游返回的多条数据逐条执行同一套逻辑,比如批量发送邮件、短信、站内通知;
- 调用的外部 API 有明确的频率限制,需要控制请求节奏;
- 每条数据的处理结果会影响下一条数据的参数,必须串行执行;
- 希望某一条处理失败时,能快速定位到具体是哪条数据引起的。
但也要说句公道话,它并不是所有批量场景的最优解。如果只是想把一个数组拆成多个小数组继续分发,没有“逐轮等待”的需求,直接用普通节点处理可能更快;如果数据量达到几万条甚至更多,一个工作流从头到尾遍历完,执行时间和资源占用都很可观,这种时候更适合拆成多个子任务或者用外部队列来控制。简单说,Loop Over Items 是帮你把“批量任务变成可控任务”的节点,而不是帮你无脑塞下无限数据的节点。
2. 参数与数据流准备:把要遍历的数据理清楚
2.1 理解 n8n 的 items 数组是循环的基础
动手配置前,务必要确认清楚上游节点到底输出的是什么结构。Loop Over Items 遍历的对象,是输入节点传来的 items 数组。你可以把它理解成一列火车车厢:每节车厢就是一个 item,里面装着一个 JSON 对象,Loop Over Items 的作用就是决定一次放几节车厢出站。
以一个实际的数据库查询为例。假设你用 MySQL 节点执行了SELECT email, user_name FROM users WHERE status = 'pending',查询结果是 30 条未激活用户。在 n8n 的执行数据里,这个节点会输出 30 个 item,每个 item 大概是这样的结构:
{ "email": "user01@example.com", "user_name": "张三" }如果把这 30 个 item 直接接到 Loop Over Items 上,循环节点会把它当成“需要遍历的清单”。你在搭建流程之前,可以先在前面随便接一个 Set 节点或者直接查看上游节点的执行结果,确认字段名、字段数量、数据类型是否符合预期。不要嫌这一步麻烦,我见过太多人循环配了半天不生效,最后发现是上游查询的字段名和后续表达式里写的不一致。
2.2 Batch Size 到底该怎么调
Loop Over Items 最核心的参数是 Batch Size,也就是“每轮循环往后续节点送几条数据”。默认值是 1,含义是每次迭代只把一条 item 交给后面的节点处理。如果输入有 30 条数据,Batch Size 为 1,那工作流会执行 30 轮循环;Batch Size 改为 10,则 30 条数据会被分成 3 轮,每轮送 10 条过去。
这里有一个容易误解的地方:Batch Size 为 10 不是说“后面节点每轮只处理 10 条里的一条”,而是“后面节点这一轮收到的是一个包含 10 条 item 的数组”,它会对这 10 条 item 分别执行逻辑。如果你用的是 SMTP Send、HTTP Request 这类天然支持多 item 的节点,这一轮就会连续发送 10 封邮件或发起 10 个请求。
Batch Size 怎么选,取决于你的业务诉求:
| 对比项 | Batch Size = 1 | Batch Size = 10 |
|---|---|---|
| 迭代轮数 | 等于 items 总条数 | 等于总条数 / 10向上取整 |
| 单轮内下游处理量 | 每次只处理 1 条 | 每轮处理 10 条 |
| 请求节奏 | 最容易控制,逐条推进 | 吞吐量更高,但突发压力更大 |
| 异常排查难度 | 低,能精准定位到某一条 | 高,一条出错整轮都可能受影响 |
| 适合场景 | 对外部 API 限流敏感、需要逐条确认 | 批量导入、批量归档、内部系统调用 |
我个人在面对外部 API 时,默认从 Batch Size = 1 开始调。先把一条数据的完整链路跑通,确认识别率、参数、返回结构都正常,再根据接口的限流情况决定是否调大批次。调批次之前,不妨去读一下你调用的那个 API 的频率限制文档,不同服务商差异很大,有的允许每秒几十次,有的每分钟只给 60 次额度,用 Batch Size 硬怼一定会出事。
2.3 Loop 输出和 Done 输出千万别接反
Loop Over Items 节点在编辑器右侧通常有两个输出口:一个是 loop 输出,另一个是 done 输出。这两个输出的含义完全不同,很多新手第一次用都会在这里栽跟头。
loop 输出是“每一轮循环的主输出”,后面连接的业务逻辑节点,比如发邮件的 SMTP 节点、调接口的 HTTP Request 节点,都要接在这个输出上。每执行一轮循环,这些节点就会被触发一次。
done 输出则是“所有循环全部结束之后”才触发的完成信号,整个循环生命周期里它只会触发一次。如果你希望循环跑完以后再做一个统一的收尾动作,比如发送一条“全部处理完成”的群通知、把处理结果汇总写回数据库,就应该把收尾节点接在 done 输出上。
一个很常见的错误是,有人把“循环结束后发送通知”的节点也接到了 loop 输出上,结果每处理一条数据就收到一条通知。这不是 n8n 有 bug,而是输出接口选错了。接线的时候留意一下连接点提示,n8n 会明确问你要连接到哪个输出。
注意:如果循环内的业务节点执行失败,导致整个工作流中途退出,done 输出不会触发。所以不要把“必须执行”的清理动作只挂在 done 分支上,最好配合 n8n 的 Error Workflow 或额外的兜底机制。
2.4 和 Split In Batches 节点的关系
如果你用的 n8n 版本稍微老一点,可能还会在节点面板里看到 Split In Batches 这个节点。它和 Loop Over Items 的定位高度重叠,都是把输入数据按照批次拆分后逐轮输出。n8n 官方后来的建议是优先使用 Loop Over Items,因为它的语义更清晰,也能覆盖 Split In Batches 的主要使用场景。
我的建议是:新写工作流时直接用 Loop Over Items,不要再纠结 Split In Batches。迁移旧工作流时留意一下循环内部对数据的引用方式可能略有不同,但只要数据流不变,替换成本通常很低。这个“一个功能两代节点”的情况也提醒我们,看网上老教程时先确认一下对方的 n8n 版本,避免照着旧界面操作半天对不上。
3. 实操:循环逐条生成权益码并发送邮件
3.1 场景设定与整体流程
为了把用法讲透,我设计了一个真实感比较强的案例。假设你现在有一个 n8n 工作流,目标是:每天早上从会员表里找出 100 个本月还没领过优惠券的用户,逐个调用内部权益接口为每个用户生成一个专属优惠码,再给每个用户发送一封带优惠码的邮件。
这个案例里最核心的约束是:内部权益接口有调用频率限制,不能一次性把 100 个请求全打过去;邮件内容里的优惠码又必须和用户一一对应,不能张冠李戴。这正好是 Loop Over Items 发挥价值的标准场景。
整体流程可以这样组织:
- 上游节点查询待发用户列表,得到 N 个用户 item;
- Loop Over Items 节点接收这个列表,设置 Batch Size = 1;
- 循环体内先接一个 HTTP Request 节点,调用内部权益接口,为当前用户生成优惠码;
- 再接一个 Set(Edit Fields)节点,把邮件需要的字段组装到一起;
- 最后接一个 SMTP 节点,给当前用户发送邮件;
- 所有用户处理完后,通过 done 输出发送一条汇总通知。
3.2 把输入数据准备好
第一步是拿到待处理用户列表。你可以用 MySQL、Postgres、HTTP Request、Google Sheets 等任意方式作为数据源。这里关键不是用什么节点,而是保证输出的 item 结构足够清晰。
假设上游查询出来每个用户 item 长这样:
{ "user_id": "u_1001", "email": "member01@example.com", "name": "林晓", "member_level": "gold" }如果你第一次搭这个流程,我强烈建议先用一个数据量极小的输入来验证,比如在 SQL 查询里加LIMIT 3,或者先用测试数据。不要一上来就处理 100 条真实数据,否则一旦流程有问题,邮件可能已经发出去几十封了,很难挽回。测试通过后再把数据量放宽,这是批量任务的基本安全姿势。
3.3 接入 Loop Over Items 并配置批次大小
把用户列表节点连接到 Loop Over Items 节点。点击循环节点,在参数面板里找到 Batch Size,先填 1。前面解释过,Batch Size = 1 表示每轮循环只放行一个用户 item 进入循环体,整条链路串行执行,这样既能控制对内部权益接口的请求频率,也便于定位是哪条数据出了问题。
如果后续测试发现内部接口其实很扛得住,想提速,可以把 Batch Size 调到 5 或 10。但记得要重新评估一下“邮件发送”这步的节奏:批量发送邮件时如果频率太高,很容易被邮件服务商判定为垃圾邮件行为,所以我个人做邮件类场景时几乎永远保持 Batch Size = 1,最多在邮件服务商侧开通专门的批量发送通道。
3.4 在循环体内调用接口并保留原始字段
循环体里的第一个业务节点是 HTTP Request。
这里有个很容易踩的坑:默认情况下,HTTP Request 节点会把上游传进来的用户 item “替换”成接口响应的内容。也就是说,进入 HTTP Request 时你还有email、name这些字段,等它请求完权益接口,输出给下一个节点的可能就只剩{"code": "GOLD-2024-XXXX"}之类的接口返回值了。如果不做任何处理,下一步组装邮件时就会发现:优惠码拿到了,但用户邮箱和姓名不见了,邮件根本不知道发给谁。
为了解决这个问题,有两个常用手段。第一种是在 HTTP Request 节点上开启“包含输入数据”(Append Input Data 或类似选项),让输出里既保留原始用户字段,又带上接口返回的字段;第二种是在 HTTP Request 后面加一个 Set 节点,手动把需要继续传递的字段重新拼接出来。
我推荐第二种,因为它的表达最明确,也不依赖 HTTP Request 节点不同版本里选项名称的差异。操作上,在 HTTP Request 后面接一个 Set(Edit Fields)节点,把输出字段配置成邮件发送需要的完整结构:
to字段填{{ $('Loop Over Items').item.json.email }},用于取当前循环用户 item 里的邮箱;user_name字段填{{ $('Loop Over Items').item.json.name }};coupon_code字段填{{ $json.code }},这里$json是 HTTP Request 节点输出的接口响应内容。
这样组合之后,Set 节点输出的就是一条同时包含收件人信息和优惠码的完整 item,下游节点处理起来就非常干净了。
3.5 用 SMTP 节点完成逐封发送
邮件发送这一步,先说一个常见疑问:n8n 本身没有“内置邮箱”,它靠的是节点去连接外部邮件服务。最通用的方案是 SMTP 节点,你只要在 n8n 的 Credentials 里配置好 SMTP 服务地址、账号、密码(或授权码),就能用它发邮件。n8n 里也可以接入 Gmail、Outlook、SendGrid 等专门的邮件节点,本质都是靠对应的凭据把邮件交给第三方服务发送。
在我们的循环工作流中,Set 节点后面接 SMTP Send(或你选择的邮件发送节点)。在邮件的收件人、主题、正文里,直接用{{ $json.to }}、{{ $json.user_name }}、{{ $json.coupon_code }}这些变量引用。因为此时 Batch Size = 1,SMTP 节点每一轮只会收到一条 item,所以它每轮只发一封邮件。外部接口被“限流”的问题,也从根源上被循环节奏控制住了。
如果你的实际业务流程不需要调用接口,而是直接把数据库里查出来的一批用户逐封发邮件,那整个循环体甚至可以只由一个 SMTP 节点组成。Loop Over Items 会把每个用户 item 依次送进 SMTP 节点,每次发一封。这就是这个节点最朴素的用法,很多“通知类”工作流就是这么搭起来的。
3.6 查看执行日志和验证结果
配好之后,先跑一次测试。n8n 的执行日志是排查批量任务最有力的工具。
点击执行后,在 executoins 列表里打开这次工作流执行记录,你会看到 Loop Over Items 节点下面有很多次迭代记录,或者能看到它把输入列表切成了一条一条往下传。你可以展开任意一次迭代,确认当前的email、user_id是否正确,HTTP Request 返回的优惠码有没有正常赋值,SMTP 节点是否返回了发送成功状态。
这里分享一个我自己的验证习惯:即使 Batch Size = 1,第一次跑正式数据时也一定先选 5 个以内用户。跑完以后立刻去收件箱确认邮件内容,尤其是优惠码和用户名是否对得上。如果内容有误,马上停掉后续流程,回头检查 Set 节点里的字段映射;如果内容没问题,再放开完整的数据量。邮件这类“发出去就收不回”的操作,前置验证绝对不能省。
3.7 邮箱发送服务选择的补充
既然标题相关热词里反复提到 n8n 能通过什么邮箱发邮件,我顺带把这块也聊透一点。n8n 发邮件的常见方案大概是这三种:
- 自建或企业邮箱的 SMTP 服务,用 SMTP 节点发送,成本低,适合内部通知和中小量邮件,但要注意服务商对每日发送量和频率的限制;
- 专业邮件发送服务,比如 SendGrid、Postmark、Amazon SES,适合营销邮件、大规模事务邮件,有更好的送达率和反垃圾信誉;
- Gmail 等个人邮箱,适合测试和小流量场景,不适合生产环境大批量发送,很容易触发风控。
在 Loop Over Items 里逐封发送时,如果你用的是普通 SMTP 邮箱,千万不要把 Batch Size 调太高,也不要完全省略每条之间的节奏。即便 Batch Size = 1,n8n 的执行速度也可能比你想象中快很多,眨眼间就把几十封邮件发出去了。如果邮件服务商对发送频率敏感,可以考虑在循环体内加一个 Wait 节点做延时,或者退一步把任务拆分成多次触发。批量邮件的核心技术从来不是“能不能发”,而是“发多少、多快、会不会被当成垃圾邮件”。
4. 循环内部的数据引用与高级玩法
4.1 循环里到底怎么引用“当前这条”数据
循环场景里最出问题的就是数据引用。很多人以为,进入循环体之后,随便在哪个节点里用$json.email都能取到当前用户邮箱,其实这取决于你当前看到的这个节点的输入到底是什么。
n8n 的表达式里,$json代表的是“当前节点收到的那条输入数据”。在循环体里的第一个节点,它收到的确实就是当前迭代的那条 item,所以直接用{{ $json.email }}没问题。但如果在 HTTP Request 或某些转换节点之后再用$json,它代表的可能就是前一个节点的输出,不再是循环开始时那条用户数据了。
要引用循环开始时的那条原始数据,更稳妥的方式是指定“循环节点”本身。比如循环节点如果叫 Loop Over Items,那么在许多 n8n 版本里可以这样写:
{{ $('Loop Over Items').item.json.email }}旧版兼容写法也有人用:
{{ $node['Loop Over Items'].json.email }}两者的本质都是:“从 Loop Over Items 节点当前这一轮迭代的输出里,取 email 字段”。因为你的 Batch Size = 1,每一轮 Loop Over Items 输出给循环体的只有一条 item,所以这么取到的就是当前正在处理的这条用户数据。如果 Batch Size 调成了 10,那这个表达式取到的会是当前轮次里 Loop 节点输出的第一条 item,而不是随机一条,你需要理解这个语义再使用。
4.2 被上游节点覆盖的数据,怎么找回来
上一节提到的 HTTP Request 覆盖输入问题,不只出现在 HTTP 节点上。凡是输出结构会变化的节点,比如 Code、Merge、Transform 类节点,都有可能改变下游能看到的字段。
我的习惯是:进入循环后先放一个 Set 节点,把从这条数据开始就必须全程携带的关键字段复制一份,并加上固定前缀,比如send_email、send_name。后面即便数据流被接口响应覆盖,我还能用类似{{ $json.send_name }}或从 Set 节点回引的方式把字段找回来。这种“先存档再处理”的思路,在循环场景里特别好使。
如果你不确定当前节点到底有什么字段,最快的办法是直接看执行日志。在 n8n 的编辑界面里,点击任意节点左侧的小圆点,可以查看该节点这一步的输入输出数据。点开看一次,比反复猜表达式要高效得多。
4.3 嵌套循环怎么搭才不失控
Loop Over Items 是可以嵌套使用的。比如外层循环遍历所有客户,每个客户下面又有多个订单需要继续逐条处理,这时可以在外层循环体内再放一个 Loop Over Items 节点,让内层循环去遍历“当前客户的订单列表”。
但嵌套循环的复杂度是成倍增长的。你在内层循环里引用数据时,要格外小心:内层节点能直接看到的,往往是内层 Loop 节点送进来的订单数据;如果你还想同时引用外层客户信息,通常需要像 4.2 节说的那样,在进入内层循环前先通过一个 Set 节点把外层客户 ID、邮箱等字段合并到订单数据里,让内层循环的每个 item 都“自带上下文”。
嵌套层数最好不要超过两层。超过两层之后,执行日志的排查难度会非常大,每一层是哪一轮出了问题都不容易看清。如果你的业务流程真的需要三层以上遍历,我建议先把逻辑拆成多个工作流,用子工作流(Execute Workflow)串联,每层单独测试,这样维护起来会轻松很多。
4.4 循环结束后想汇总结果怎么办
循环处理完之后,最自然的收尾就是通过 done 输出接一个节点。但要注意,done 分支上的节点并不会自动收到“所有循环处理结果的汇总列表”,它只是收到一个“循环完成”的信号。想要汇总每次循环产生的数据,有一个常用思路:在循环体内的最后一个节点,把该次处理结果写入一个外部存储,比如追加到 Google Sheets 的行里、更新到数据库表中,或者通过 HTTP 请求写入数据仓库;循环结束后,再用一个查询节点去读取这份汇总数据。
如果不想引入外部存储,也可以用 n8n 的 Code 节点,在 done 分支后用$('Loop Over Items').all()这类表达式尝试收集循环节点的全部输出。但这类表达式需要结合你的 n8n 版本和数据流仔细验证,不是在所有情况下都能拿到你期望的那个列表。所以我更推荐“边循环边落库”的方式,它虽然多一次存储调用,但数据可靠,排查也方便。
5. 常见问题排查与避坑记录
5.1 高频问题速查表
把我在实际使用中碰到过、以及社群里高频出现的问题整理成了一张速查表,遇到对应现象可以直接对照:
| 问题现象 | 可能原因 | 排查与解决办法 |
|---|---|---|
| 循环没有逐条执行,像一次性全跑了 | Batch Size 配置比预期大;或上游节点本来就只有一个聚合 item | 检查循环节点参数里的 Batch Size,看看上游输出 item 数量 |
| 循环完全没有执行 | 上游输出的 items 数量为 0 | 在上游节点执行结果里确认数据是否为空 |
| 后续节点拿不到原始用户字段 | HTTP Request 或转换节点覆盖了输入 item | 用 Set 节点重新组装字段,或开启“包含输入数据”选项 |
| 循环内某一条报错,整个工作流中断 | n8n 对执行失败的默认行为就是中断 | 在上游尽量清洗数据,用 IF 节点把可疑数据分流,不要带病进循环 |
| done 分支没有触发 | 循环内某节点执行失败导致工作流提前退出 | 先修复循环内的报错,再检查 done 分支是否接对了输出口 |
| 外部接口报 429 限流错误 | 请求频率超过接口限制 | Batch Size 调小,或在循环中加 Wait 节点控制节奏 |
| 工作流执行特别慢 | items 数量巨大,每轮又有外部请求 | 拆分批次执行;考虑并发/队列或减少外部请求次数 |
| 连续多次执行同一工作流后,循环次数不对 | 循环状态可能残留到了下一次执行 | 检查节点参数里是否有 Reset 选项,结合文档开启重置 |
5.2 循环被中断时的定位思路
任何批量任务,最怕的不是出错,而是出错后不知道错在哪。Batch Size = 1 的时候,定位问题还相对容易:你只要看执行日志里最后一轮失败的那次迭代,就能确定是哪一条数据引发了异常。但如果你一上来就把 Batch Size 调到 50,某一条脏数据可能导致整轮 50 个请求全部失败,定位粒度就粗了。
我的建议是:新流程先用 Batch Size = 1 跑通,确认逻辑稳定后再放大批次。即便放大批次,循环体内的关键检查也不能少,比如调用外部接口之前,先把必填参数做一次校验。遇到 email 为空、user_id 不是数字这种情况,与其让它在接口层报错,不如提前用 IF 节点把这个 item 引导到一个“跳过并记录”的分支里,保证整个循环继续推进。
如果你确实需要“某一条失败但不要影响其他条继续处理”,靠单个循环很难做到优雅。n8n 没有节点级的 try/catch,社区里有人会用异常分支或者错误工作流来处理,但这类方案往往要引入额外的状态记录,复杂度不低。对大多数业务来说,提前清洗数据远比事后补救更可靠。
5.3 大批量循环时的性能与部署建议
随着热词里出现 n8n 本地部署、企业级部署,我想特别提醒一下:当循环的数据量上来以后,性能问题会逐渐暴露。
如果只是几百条数据,Loop Over Items 在默认配置下基本没什么压力。但如果一次要遍历几万条,情况就不同了。n8n 会把每次迭代的执行记录和中间数据都保留下来,几万条迭代的执行日志会占用大量存储和内存,编辑界面也可能卡顿。这个时候,更好的做法不是在单个工作流里硬跑,而是考虑拆批:用定时触发器或队列把几万条数据切成几百个批次,每个批次独立执行一个较小的工作流