n8n-mcp 实战:Python Code 节点五大高频错误模式与系统化排查指南
【免费下载链接】n8n-mcpA MCP for Claude Desktop / Claude Code / Windsurf / Cursor to build n8n workflows for you项目地址: https://gitcode.com/GitHub_Trending/n8/n8n-mcp
导读
本文是 n8n-mcp 项目中 Python Code 节点(n8n Code node Python 模式)的权威错误排查指南,源自 ERROR_PATTERNS.md,并融合了同目录 SKILL.md、DATA_ACCESS.md、STANDARD_LIBRARY.md 及仓库源码实现。读完本文,你将掌握 n8n Python Code 节点最常见的五大错误(外加一个 Bonus 错误)的成因、报错形态、正确修复写法,以及一套可落地的错误预防检查清单与测试模式,能够直接排查并修复真实工作流中的 Python 节点故障。
一、错误全景:Top 5 高频错误总览
在 n8n 的 Code 节点中,Python 模式与 JavaScript 模式共享同一套数据与返回契约,但 Python 因其标准库限制和语法特性,产生了特有的高频错误。根据 ERROR_PATTERNS.md 的统计,以下 5 类错误覆盖了 Python Code 节点失败的大多数场景:
| # | 错误 | 根因 |
|---|---|---|
| 1 | ModuleNotFoundError | 导入外部库(Python 特有) |
| 2 | 空代码 / 缺少 return | 没有代码或没有返回语句 |
| 3 | KeyError | 未使用.get()直接访问字典键 |
| 4 | IndexError | 未做边界检查直接按下标访问列表 |
| 5 | 返回格式错误 | 返回了错误的数据结构 |
这五类错误是 n8n Python Code 节点失败的主要来源,下文逐一拆解。在动手排查前,请先建立一条核心认知:n8n 官方与本文所在技能体系都建议 95% 的场景优先使用 JavaScript,Python 仅在你明确需要标准库能力(正则、哈希、统计等)时使用,详见 README.md。
二、Error #1:ModuleNotFoundError —— 最致命的 Python 特有错误
频率:Python Code 节点中非常常见。
成因:尝试导入 n8n Python 运行环境中不可用的外部库。n8n 默认只提供 Python 标准库,没有 pip 包管理能力。
2.1 错误现场
# ❌ 错误:外部库不可用 import requests # ModuleNotFoundError: No module named 'requests' import pandas # ModuleNotFoundError: No module named 'pandas' import numpy # ModuleNotFoundError: No module named 'numpy' import bs4 # ModuleNotFoundError: No module named 'bs4' import pymongo # ModuleNotFoundError: No module named 'pymongo' import psycopg2 # ModuleNotFoundError: No module named 'psycopg2' # 以下代码必然失败——这些库并未安装! response = requests.get("https://api.example.com/data")2.2 解决方案
方案一:改用 JavaScript(推荐覆盖约 95% 的场景)
// ✅ JavaScript Code 节点中使用 this.helpers.httpRequest() const response = await this.helpers.httpRequest({ method: 'GET', url: 'https://api.example.com/data' }); return [{json: response}];方案二:用 n8n HTTP Request 节点替代
在 Python Code 节点之前串联一个 HTTP Request 节点,然后在前置节点的输出上继续处理:
# ✅ 在 Python Code 节点中读取上游 HTTP Request 节点的响应 response = _input.first()["json"] return [{ "json": { "status": response.get("status"), "data": response.get("body"), "processed": True } }]方案三:仅使用标准库
# ✅ 使用标准库 urllib(功能有限:无自定义 headers、无鉴权) from urllib.request import urlopen from urllib.parse import urlencode import json url = "https://api.example.com/data" with urlopen(url) as response: data = json.loads(response.read()) return [{"json": data}]2.3 常见库替换对照表
| 需求 | ❌ 外部库 | ✅ 替代方案 |
|---|---|---|
| HTTP 请求 | requests | HTTP Request 节点或 JavaScript |
| 数据分析 | pandas | Python 列表推导式 |
| 数据库 | psycopg2、pymongo | n8n 数据库节点(Postgres/MySQL/MongoDB) |
| 网页抓取 | beautifulsoup4 | HTML Extract 节点 |
| Excel | openpyxl | Spreadsheet File 节点 |
| 图片处理 | pillow | 外部 API 或专用节点 |
2.4 可用的标准库模块清单
# ✅ 以下均可使用——标准库 import json # JSON 解析 import datetime # 日期/时间操作 import re # 正则表达式 import base64 # Base64 编码 import hashlib # 哈希(MD5、SHA256) import urllib.parse # URL 解析与编码 import math # 数学函数 import random # 随机数 import statistics # 统计函数 import collections # defaultdict、Counter 等完整的可用模块与不可用模块清单(含itertools、functools、os.path等分级说明),见 STANDARD_LIBRARY.md。
自托管例外:外部包是否可用完全取决于实例的 Python runner 配置。若你的自托管实例明确声明了可用的额外包,可以按实例实际情况使用(详见 SKILL.md 中的说明)。
2.5 源码佐证:官方 Python 示例同样遵守标准库约束
仓库中的示例生成器 example-generator.ts 内置了nodes-base.code.pythonExample示例,其实现完全遵循“仅标准库 +_input.all()数据访问”的约束:
# Python data processing - use underscore prefix for built-in variables import json from datetime import datetime import re results = [] # Use _input.all() to get items in Python for item in _input.all(): # Convert JsProxy to Python dict to avoid issues with null values item_data = item.json.to_py() # Clean email addresses email = item_data.get('email', '') if email and re.match(r'^[\w\.-]+@[\w\.-]+\.\w+$', email): cleaned_data = { 'email': email.lower(), 'name': item_data.get('name', '').title(), 'validated': True, 'timestamp': datetime.now().isoformat() } else: cleaned_data = dict(item_data) cleaned_data['validated'] = False cleaned_data['error'] = 'Invalid email format' results.append({'json': cleaned_data}) return results这段代码印证了三条关键实现事实:只用json/datetime/re标准库;用item.json.to_py()将 JsProxy 转为 Python dict(避免空值问题);统一以{'json': ...}结构返回。同时,仓库的表达式格式校验器 expression-format-validator.ts 明确将jsCode、pythonCode、functionCode视为“原始代码字段”跳过表达式检查——说明这些字段在项目中被当作不可外部校验的代码主体,写好它们只能靠开发者遵守标准库与返回格式约束。
三、Error #2:空代码 / 缺少 Return
频率:所有 Code 节点均常见。
成因:代码节点内容为空,或代码执行路径上没有return语句。
3.1 错误现场
# ❌ 错误:空代码 # (什么都没有) # ❌ 错误:有代码但没有 return items = _input.all() processed = [item for item in items if item["json"].get("active")] # 忘了 return! # ❌ 错误:return 作用域错误 if _input.all(): return [{"json": {"result": "success"}}] # return 在 if 块内部——可能不会执行!3.2 正确写法
# ✅ 正确:始终 return all_items = _input.all() if not all_items: # 返回空数组或错误信息 return [{"json": {"error": "No items"}}] # 处理数据 processed = [item for item in all_items if item["json"].get("active")] # 末尾必须 return return processed if processed else [{"json": {"message": "No active items"}}]3.3 最佳实践:无条件返回
# ✅ 良好:函数末尾无条件 return def process_items(): items = _input.all() if not items: return [{"json": {"error": "Empty input"}}] # 处理 result = [] for item in items: result.append({"json": item["json"]}) return result # 调用函数并返回结果 return process_items()将业务逻辑封装进函数、由主流程return process_items()兜底,可以保证无论内部分支如何,节点出口始终有返回值。
四、Error #3:KeyError —— 字典访问未用 .get()
频率:Python Code 节点中非常常见。
成因:直接以dict["key"]形式访问不存在的字典键。
4.1 错误现场
# ❌ 错误:直接按键访问 item = _input.first()["json"] name = item["name"] # 若 "name" 不存在则 KeyError! email = item["email"] # 若 "email" 不存在则 KeyError! age = item["age"] # 若 "age" 不存在则 KeyError! return [{ "json": { "name": name, "email": email, "age": age } }]4.2 报错形态
KeyError: 'name'4.3 解决方案:.get() + 默认值
# ✅ 正确:使用带默认值的 .get() item = _input.first()["json"] name = item.get("name", "Unknown") email = item.get("email", "no-email@example.com") age = item.get("age", 0) return [{ "json": { "name": name, "email": email, "age": age } }]4.4 嵌套字典访问
# ❌ 错误:多层键直接访问 webhook = _input.first()["json"] name = webhook["body"]["user"]["name"] # 可能产生多个 KeyError! # ✅ 正确:逐层安全访问 webhook = _input.first()["json"] body = webhook.get("body", {}) user = body.get("user", {}) name = user.get("name", "Unknown") # ✅ 同样正确:链式 .get() name = ( webhook .get("body", {}) .get("user", {}) .get("name", "Unknown") ) return [{"json": {"name": name}}]4.5 Webhook Body 访问(关键!)
n8n Python Code 节点最常见的单一错误是忘记 webhook 数据被嵌套在["body"]之下。Webhook 节点会把 POST 数据、查询参数、JSON 载荷统一包装在body属性内:
# ❌ 错误:忘记 webhook 数据位于 "body" 下 webhook = _input.first()["json"] name = webhook["name"] # KeyError! email = webhook["email"] # KeyError! # ✅ 正确:通过 ["body"] 访问 webhook = _input.first()["json"] body = webhook.get("body", {}) name = body.get("name", "Unknown") email = body.get("email", "no-email") return [{ "json": { "name": name, "email": email } }]关于 webhook 完整结构(headers、params、query、body、method、url)以及_input.all()/_input.first()/_input.item/_node["Name"]的选型决策树,详见 DATA_ACCESS.md。
五、Error #4:IndexError —— 列表访问未做边界检查
频率:处理数组/列表时常见。
成因:直接按下标访问不存在的列表位置。
5.1 错误现场
# ❌ 错误:假设元素必然存在 all_items = _input.all() first_item = all_items[0] # 列表为空则 IndexError! second_item = all_items[1] # 只有 1 个元素则 IndexError! return [{ "json": { "first": first_item["json"], "second": second_item["json"] } }]5.2 报错形态
IndexError: list index out of range5.3 解决方案:先检查长度
# ✅ 正确:先检查长度 all_items = _input.all() if len(all_items) >= 2: first_item = all_items[0]["json"] second_item = all_items[1]["json"] return [{ "json": { "first": first_item, "second": second_item } }] else: return [{ "json": { "error": f"Expected 2+ items, got {len(all_items)}" } }]5.4 安全获取首元素
# ✅ 正确:用 _input.first() 代替 [0](内置安全保护) first_item = _input.first()["json"] return [{"json": first_item}] # ✅ 同样正确:访问前先判断 all_items = _input.all() if all_items: first_item = all_items[0]["json"] else: first_item = {} return [{"json": first_item}]5.5 用切片代替下标
# ✅ 正确:切片永远不会抛出 IndexError all_items = _input.all() # 取前 5 个(不足 5 个也不会失败) first_five = all_items[:5] # 取第一个之后的全部(为空也不会失败) rest = all_items[1:] return [{"json": item["json"]} for item in first_five]六、Error #5:返回格式错误
频率:新手用户常见。
成因:n8n 要求 Code 节点返回带"json"键的对象数组,返回其他结构会导致下游节点无法解析。
6.1 错误现场
# ❌ 错误:返回普通字典 return {"name": "Alice", "age": 30} # ❌ 错误:返回没有 "json" 包装的数组 return [{"name": "Alice"}, {"name": "Bob"}] # ❌ 错误:返回 None return None # ❌ 错误:返回字符串 return "success" # ❌ 错误:返回单个对象(而非数组) return {"json": {"name": "Alice"}}6.2 正确格式
# ✅ 正确:带 "json" 键的对象数组 return [{"json": {"name": "Alice", "age": 30}}] # ✅ 正确:多条数据 return [ {"json": {"name": "Alice"}}, {"json": {"name": "Bob"}} ] # ✅ 正确:批量转换 all_items = _input.all() return [ {"json": item["json"]} for item in all_items ] # ✅ 正确:空数组(合法) return [] # ✅ 正确:单条结果也要数组包装 return [{"json": {"result": "success"}}]为什么必须这样:下游节点期望的是列表格式。格式错误会导致整个工作流执行失败(详见 SKILL.md 的 Return Format Requirements 章节)。
6.3 常见场景
场景一:聚合(返回单一结果)
# 计算总和 all_items = _input.all() total = sum(item["json"].get("amount", 0) for item in all_items) # ✅ 正确:用数组 + "json" 包装 return [{ "json": { "total": total, "count": len(all_items) } }]场景二:过滤(返回多条结果)
# 过滤活跃条目 all_items = _input.all() active = [item for item in all_items if item["json"].get("active")] # ✅ 正确:原样返回(已是正确格式) return active # ✅ 同样正确:若需转换 return [ {"json": {**item["json"], "filtered": True}} for item in active ]场景三:无结果
# ✅ 正确:返回空数组 return [] # ✅ 同样正确:返回错误信息 return [{"json": {"error": "No results found"}}]七、Bonus 错误:AttributeError —— 模式使用不当
成因:在错误的运行模式下使用了_input.item。
7.1 错误现场
# ❌ 错误:在 "All Items" 模式使用 _input.item current = _input.item # 在 "All Items" 模式下为 None data = current["json"] # AttributeError: 'NoneType' object has no attribute '__getitem__'7.2 解决方案
# ✅ 正确:根据模式选择合适的方法 # "All Items" 模式使用: all_items = _input.all() # "Each Item" 模式使用: current_item = _input.item # ✅ 安全:先判断 item 是否存在 current = _input.item if current: data = current["json"] return [{"json": data}] else: # 当前运行在 "All Items" 模式 return _input.all()_input.item仅在Run Once for Each Item模式下可用;在默认的Run Once for All Items模式下为None。两种模式的选择依据、性能差异与示例代码见 SKILL.md。
八、错误预防检查清单
运行 Python Code 节点前,逐项核验:
- 无外部导入:仅使用标准库(json、datetime、re 等)
- 代码返回数据:每条执行路径都以
return结尾 - 格式正确:返回
[{"json": {...}}](带 "json" 键的数组) - 字典安全访问:字典用
.get()而非[] - 列表安全访问:下标访问前检查长度,或改用切片
- Webhook body 访问:通过
_json["body"]访问 webhook 数据 - 不返回 None:用空数组
[]代替None - 模式意识:按运行模式正确使用
_input.all()、_input.first()、_input.item
九、快速修复参考表
| 错误 | 快速修复 |
|---|---|
ModuleNotFoundError | 改用 JavaScript 或 HTTP Request 节点 |
KeyError: 'field' | 将data["field"]改为data.get("field", default) |
IndexError: list index out of range | 访问items[0]前先if len(items) > 0: |
| 输出为空 | 在末尾添加return [{"json": {...}}] |
AttributeError: 'NoneType' | 检查模式设置,或确认_input.item是否存在 |
| 格式错误 | 包装结果:return [{"json": result}] |
| Webhook KeyError | 通过_json.get("body", {})访问 |
十、测试你的代码:三种验证模式
测试模式一:处理空输入
# ✅ 始终用空输入测试 all_items = _input.all() if not all_items: return [{"json": {"message": "No items to process"}}] # 继续处理 # ...测试模式二:测试缺失字段
# ✅ 用 .get() + 默认值,字段缺失也不报错 item = _input.first()["json"] name = item.get("name", "Unknown") email = item.get("email", "no-email") age = item.get("age", 0) return [{"json": {"name": name, "email": email, "age": age}}]测试模式三:兼容两种运行模式
# ✅ 两种模式下都能运行的代码 try: # 先尝试 "Each Item" 模式 current = _input.item if current: return [{"json": current["json"]}] except: pass # 回退到 "All Items" 模式 all_items = _input.all() return all_items if all_items else [{"json": {"message": "No data"}}]十一、总结与黄金法则
需避开的 Top 5 错误:
- ModuleNotFoundError—— 改用 JavaScript 或 n8n 节点
- 缺少 return—— 始终以
return [{"json": {...}}]结尾 - KeyError—— 字典访问一律使用
.get() - IndexError—— 下标访问前先检查长度
- 格式错误—— 返回
[{"json": {...}}],而非普通对象
黄金法则:
- 不导入外部库(需要时改用 JavaScript)
- 字典访问始终使用
.get() - 始终返回
[{"json": {...}}]格式 - 列表访问前检查长度
- 通过
["body"]访问 webhook 数据
最后提醒:JavaScript 适用于约 95% 的场景;Python 有明确限制(无 requests、pandas、numpy);复杂操作优先选用 n8n 专用节点。
延伸阅读(同一技能包内的配套文档):
- SKILL.md —— Python Code 节点总览与快速上手
- DATA_ACCESS.md —— 数据访问模式与决策树
- STANDARD_LIBRARY.md —— 可用标准库模块全参考
- COMMON_PATTERNS.md —— 10 个生产级 Python 模式
- README.md —— 技能总览、何时用 Python 而非 JavaScript
【免费下载链接】n8n-mcpA MCP for Claude Desktop / Claude Code / Windsurf / Cursor to build n8n workflows for you项目地址: https://gitcode.com/GitHub_Trending/n8/n8n-mcp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考