写在前面
前两篇文章,咱们把 n8n 跑起来了,也做了一些简单的工作流。
从零搭建 n8n 开发环境:Docker 部署完全指南
但你可能会发现,遇到稍微复杂点的场景就不知道怎么处理了。
• 怎么从 API 返回的复杂 JSON 中提取想要的数据?
• 如何处理数组和循环?
• 表达式{{ }}里能写什么,不能写什么?
• 节点之间数据是怎么传递的?
这些问题,都是因为对 n8n 的核心概念理解不够深入。今天这篇文章,我会用大白话把这些概念讲透,让你彻底理解 n8n 的运行机制。
理解 n8n 的数据模型
先从最底层的数据结构说起。
n8n 的数据格式
n8n 内部用JSON 数组来传递数据,格式是这样的:
[
{
"json": {
"name": "张三",
"age": 25
},
"binary": {},
"pairedItem": 0
}
]
关键字段:
•json:实际的数据内容
•binary:二进制数据(文件、图片等)
•pairedItem:追踪数据来源
这个格式看起来有点啰嗦,但有它的道理:每个节点都能处理多条数据。
为什么是数组?
假设你要批量处理 100 个用户的数据,n8n 是这样处理的:
[
{ "json": { "name": "用户1", "age": 20 } },
{ "json": { "name": "用户2", "age": 30 } },
{ "json": { "name": "用户3", "age": 40 } },
...
]
一次性传递,一次性处理。这就是 n8n 高效的原因。
数据传递规则
节点A(输出3条数据) 节点B(输入3条,输出3条) 节点C(输入3条)
•一对一:每条输入对应一条输出
•多对一:多条输入合并成一条输出(如聚合)
•一对多:一条输入拆分成多条输出(如分割)
节点详解
1. 触发节点(Trigger Nodes)
触发节点是工作流的起点,监听事件或条件。
Schedule Trigger - 定时触发
# 每小时执行
Hours Interval: 1
# 每天特定时间
Cron Expression: 0 9 * * * # 每天 9 点
# 每周一执行
Cron Expression: 0 9 * * 1
Cron 表达式速查:
┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 日期 (1 - 31)
│ │ │ ┌───────────── 月份 (1 - 12)
│ │ │ │ ┌───────────── 星期 (0 - 6,0 是周日)
│ │ │ │ │
* * * * *
常用例子:
•0 * * * *- 每小时整点
•0 9,12,18 * * *- 每天 9 点、12 点、18 点
•*/15 * * * *- 每 15 分钟
•0 0 1 * *- 每月 1 号凌晨
Webhook Trigger - HTTP 触发
HTTP Method: POST
Path: my-webhook
Authentication: Header Auth
Header Name: X-API-Key
Header Value: {{ $credentials.apiKey }}
访问地址:https://n8n.yourdomain.com/webhook/my-webhook
应用场景:
• 接收第三方服务的回调(支付成功、订单状态变更)
• 前端调用触发工作流
• 其他系统集成
Manual Trigger - 手动触发
用于测试,点击按钮执行。生产环境要删掉,换成真正的触发器。
2. 常规节点(Regular Nodes)
HTTP Request - 最常用的节点
发起 HTTP 请求,调用任意 API。
GET 请求:
Method: GET
URL: https://api.example.com/users/{{ $json.userId }}
Authentication: Header Auth
Headers:
Authorization: Bearer {{ $credentials.token }}
POST 请求:
Method: POST
URL: https://api.example.com/users
Body Content Type: JSON
Body:
{
"name": "{{ $json.name }}",
"email": "{{ $json.email }}"
}
处理分页:
# 配合 Loop Over Items 节点
URL: https://api.example.com/users?page={{ $json.page }}&size=100Function - JavaScript 处理
最灵活的节点,可以写任意 JavaScript 代码。
基础用法:
// 处理单条数据
const item = $input.item.json;
return {
fullName: `${item.firstName} ${item.lastName}`,
age: 2025 - item.birthYear,
isAdult: (2025 - item.birthYear) >= 18
};
处理多条数据:
// 遍历所有输入数据
const items = $input.all();
return items.map(item => ({
json: {
userId: item.json.id,
userName: item.json.name.toUpperCase(),
createdAt: new Date().toISOString()
}
}));
引入 npm 包:
// 先在 Function 节点中安装
const moment = require('moment');
return {
formattedDate: moment().format('YYYY-MM-DD'),
timestamp: moment().unix()
};Code - Python 处理
n8n 也支持 Python(需要配置 Python 环境)。
# 处理数据
import json
from datetime import datetime
items = _input.all()
results = []
for item in items:
data = item.json
results.append({
'json': {
'name': data['name'],
'processed_at': datetime.now().isoformat(),
'length': len(data['content'])
}
})
return resultsIF - 条件判断
简单条件:
Value 1: {{ $json.age }}
Operation: Larger Than
Value 2: 18
多个条件:
Conditions:
- {{ $json.age }} > 18
- {{ $json.country }} = "China"
Combine: AND # 或者 OR
复杂判断:
// 用 Function 节点更灵活
const item = $input.item.json;
if (item.age >= 18 && item.status === 'active') {
return { route: 'true' };
} else {
return { route: 'false' };
}Switch - 多路分支
比 IF 节点更强大,支持多个分支。
Mode: Rules
Rules:
- {{ $json.status }} equals "pending" 输出1
- {{ $json.status }} equals "approved" 输出2
- {{ $json.status }} equals "rejected" 输出3
Fallback: 输出4(都不匹配)Merge - 合并数据
合并多个节点的输出。
模式一:Append(追加)
节点A: [1, 2, 3]
节点B: [4, 5, 6]
合并后: [1, 2, 3, 4, 5, 6]
模式二:Keep Matches(保留匹配)
Join: $json.userId # 根据 userId 匹配
Mode: Keep Matches
节点A: [{userId: 1, name: "张三"}]
节点B: [{userId: 1, age: 25}]
合并后: [{userId: 1, name: "张三", age: 25}]Loop Over Items - 循环处理
处理数组数据,逐个执行。
Input: {{ $json.userIds }} # [1, 2, 3, 4, 5]
Batch Size: 1
# 会循环5次,每次处理一个 userId
配合 HTTP Request 批量调用 API:
Loop HTTP Request (获取用户详情) 收集结果3. 数据转换节点Set - 设置数据
修改数据结构,最常用的节点之一。
保留模式(Keep):
Mode: Keep Only Set
Values to Set:
- Name: userId
Value: {{ $json.id }}
- Name: userName
Value: {{ $json.profile.name }}
输入:
{ "id": 123, "profile": { "name": "张三", "age": 25 }, "other": "..." }
输出:
{ "userId": 123, "userName": "张三" }
手动模式(Manual):
Mode: Manual Mapping
Mapping:
user:
id: {{ $json.id }}
name: {{ $json.name }}
createdAt: {{ $now.toISO() }}Item Lists - 数组操作
拆分数组:
Operation: Split Out Items
Field: {{ $json.users }} # 数组字段
输入:
{ "users": [{ "name": "张三" }, { "name": "李四" }] }
输出:
[
{ "name": "张三" },
{ "name": "李四" }
]
聚合数组:
Operation: Aggregate Items
Aggregate: All Items
Output Format: Array
Field Name: results
输入:
[
{ "score": 80 },
{ "score": 90 }
]
输出:
{ "results": [{ "score": 80 }, { "score": 90 }] }Edit Fields - 字段操作
Operations:
- Rename: oldName newName
- Remove: fieldToDelete
- Convert: age Number表达式深度解析
表达式是 n8n 的核心,用{{ }}包裹。
1. 基础语法
引用数据
// 上一个节点的数据
{{ $json.fieldName }}
// 指定节点的数据
{{ $('节点名称').item.json.fieldName }}
// 当前时间
{{ $now }}
// 当前工作流 ID
{{ $workflow.id }}
// 执行 ID
{{ $execution.id }}数组和对象
// 数组第一个元素
{{ $json.users[0].name }}
// 对象嵌套
{{ $json.data.user.profile.email }}
// 数组长度
{{ $json.users.length }}
// 遍历数组
{{ $json.users.map(u => u.name).join(', ') }}2. 内置函数字符串处理
// 转大写
{{ $json.name.toUpperCase() }}
// 截取
{{ $json.text.substring(0, 10) }}
// 替换
{{ $json.content.replace('旧词', '新词') }}
// 分割
{{ $json.tags.split(',') }}
// 去除空格
{{ $json.input.trim() }}时间处理
// 当前时间(ISO 格式)
{{ $now.toISO() }}
// 格式化时间(注意:n8n 使用 Luxon,用小写 yyyy)
{{ $now.toFormat('yyyy-MM-dd HH:mm:ss') }}
// 其他常用格式
{{ $now.toFormat('yyyy-MM-dd') }} // 只要日期
{{ $now.toFormat('HH:mm:ss') }} // 只要时间
{{ $now.toFormat('yyyy年MM月dd日') }} // 中文格式
// 时间计算
{{ $now.plus({ days: 7 }).toISO() }} // 7天后
{{ $now.minus({ hours: 1 }).toISO() }} // 1小时前
// 时间戳
{{ $now.toMillis() }} // 毫秒
{{ $now.toSeconds() }} // 秒数学计算
// 基本运算
{{ $json.price * 0.8 }} // 打8折
{{ $json.total + $json.tax }} // 求和
{{ Math.round($json.value) }} // 四舍五入
{{ Math.max($json.a, $json.b) }} // 最大值条件判断
// 三元运算
{{ $json.age >= 18 ? '成年' : '未成年' }}
// 多重判断
{{ $json.score >= 90 ? '优秀' : $json.score >= 60 ? '及格' : '不及格' }}
// 空值处理
{{ $json.name || '未命名' }}
{{ $json.value ?? 0 }} // null/undefined 时返回 0数组操作
// 映射
{{ $json.users.map(u => u.name) }}
// 过滤
{{ $json.users.filter(u => u.age > 18) }}
// 查找
{{ $json.users.find(u => u.id === 123) }}
// 累加
{{ $json.numbers.reduce((sum, n) => sum + n, 0) }}
// 排序
{{ $json.items.sort((a, b) => b.price - a.price) }}3. 高级技巧动态字段名
// 根据条件选择字段
{{ $json[$json.fieldType] }}
// 例如:fieldType = "name",则获取 $json.nameJSON 序列化
// 对象转字符串
{{ JSON.stringify($json) }}
// 字符串转对象
{{ JSON.parse($json.jsonString) }}正则表达式
// 提取邮箱
{{ $json.text.match(/[\w.-]+@[\w.-]+\.\w+/)[0] }}
// 提取数字
{{ $json.text.match(/\d+/g) }}
// 替换(正则)
{{ $json.text.replace(/\s+/g, '-') }} // 空格替换为横杠错误处理
// try-catch(在 Function 节点中)
try {
return JSON.parse($json.data);
} catch (error) {
return { error: error.message };
}数据流转实战案例1:处理复杂 JSON
API 返回:
{
"code": 0,
"data": {
"list": [
{ "id": 1, "name": "商品A", "price": 99.9 },
{ "id": 2, "name": "商品B", "price": 199.9 }
],
"total": 2
}
}
提取商品列表:
方式一:用 Set 节点
Mode: Keep Only Set
Values:
- Name: products
Value: {{ $json.data.list }}
方式二:用 Function 节点
return $input.item.json.data.list.map(item => ({
json: {
productId: item.id,
productName: item.name,
price: item.price
}
}));案例2:批量 API 调用
需求:有 100 个用户 ID,批量获取用户详情。
工作流设计:
1. [Set] 设置用户 ID 列表
2. [Split In Batches] 每次处理 10 个
3. [HTTP Request] 调用 API
4. [Wait] 等待 1 秒(避免限流)
5. [循环] 返回步骤 2
6. [Merge] 合并所有结果
Split In Batches 配置:
Batch Size: 10
Options:
- Reset: false
HTTP Request 配置:
Method: GET
URL: https://api.example.com/users/{{ $json.userId }}案例3:数据去重
输入:
[
{ "userId": 1, "name": "张三" },
{ "userId": 2, "name": "李四" },
{ "userId": 1, "name": "张三" } // 重复
]
用 Function 节点去重:
const items = $input.all();
const seen = new Set();
const unique = [];
for (const item of items) {
const key = item.json.userId;
if (!seen.has(key)) {
seen.add(key);
unique.push(item);
}
}
return unique;案例4:数据聚合统计
输入:订单列表
[
{ "userId": 1, "amount": 100 },
{ "userId": 1, "amount": 200 },
{ "userId": 2, "amount": 150 }
]
统计每个用户的总消费:
const items = $input.all();
const stats = {};
for (const item of items) {
const userId = item.json.userId;
const amount = item.json.amount;
if (!stats[userId]) {
stats[userId] = { userId, totalAmount: 0, orderCount: 0 };
}
stats[userId].totalAmount += amount;
stats[userId].orderCount += 1;
}
return Object.values(stats).map(stat => ({ json: stat }));
输出:
[
{ "userId": 1, "totalAmount": 300, "orderCount": 2 },
{ "userId": 2, "totalAmount": 150, "orderCount": 1 }
]调试技巧1. 查看中间数据
在关键节点后加Set节点,输出数据看看:
Mode: Keep Only Set
Values:
- Name: debug
Value: {{ $json }}2. 使用 Function 节点打印
console.log('当前数据:', $input.item.json);
return $input.item.json;
然后在执行历史里查看控制台输出。
3. 拆分复杂表达式
表达式太复杂,容易出错。拆成多个步骤:
// 不好:一行搞定
{{ $json.users.filter(u => u.age > 18).map(u => u.name).join(', ') }}
// 好:分步处理
// 步骤1:Set 节点过滤
{{ $json.users.filter(u => u.age > 18) }}
// 步骤2:Set 节点提取名字
{{ $json.map(u => u.name) }}
// 步骤3:Set 节点拼接
{{ $json.join(', ') }}4. 错误节点
在工作流末尾加一个Error Trigger节点,捕获错误并发送通知:
[任意节点出错] [Error Trigger] [发送告警]性能优化建议1. 避免不必要的节点
不好:
HTTP Request Set Set Set IF ...
好:
HTTP Request Function(一次性处理完) IF ...2. 批量处理
不好:逐个调用 API(100次请求)
Loop HTTP Request
好:批量调用(1次请求)
HTTP Request (Body: { "ids": [1,2,3,...] })3. 减少数据传输
不好:传递完整数据
{{ $json }} // 包含很多无用字段
好:只传递需要的字段
{{ { id: $json.id, name: $json.name } }}4. 合理使用缓存
对于不常变化的数据,可以缓存到全局变量:
// 读取缓存
const cache = $workflow.staticData.cache || {};
// 检查缓存
if (cache[key]) {
return cache[key];
}
// 调用 API 并缓存
const result = await fetchData();
$workflow.staticData.cache = cache;
cache[key] = result;
return result;总结
今天咱们深入学习了:
1.数据模型:n8n 的数据结构和传递机制
2.节点详解:触发节点、常规节点、数据转换节点
3.表达式:基础语法、内置函数、高级技巧
4.数据流转:复杂 JSON 处理、批量调用、去重、聚合
5.调试技巧:查看中间数据、拆分表达式、错误捕获
6.性能优化:减少节点、批量处理、合理缓存
理解了这些概念,你就能设计出高效、优雅的工作流。
下一篇,咱们进入实战阶段:HTTP 节点深度使用,打造你的数据采集器,学习各种 API 调用技巧和反爬虫应对策略。
延伸阅读
• n8n 表达式官方文档[1]
• JavaScript MDN 文档[2]
• Luxon 时间库文档[3]
引用链接
[1]n8n 表达式官方文档:https://docs.n8n.io/code/expressions/
[2]JavaScript MDN 文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
[3]Luxon 时间库文档:https://moment.github.io/luxon/