背景:为什么要用飞书 CLI
我维护了一个公众号选题管理系统,用飞书多维表格存储选题信息。一开始只是手动在飞书表格里更新状态、添加上下文,但随着选题越来越多,手动操作的效率瓶颈越来越明显——每次写文章前要查状态、更新素材阶段、标记发布状态,这些重复操作完全可以自动化。
飞书官方提供了 lark-cli(命令行工具)和 API,可以通过脚本批量操作多维表格。这篇文章记录了我从零安装 lark-cli、编写统一操作脚本、到全流程测试的完整过程,重点讲测试中发现的 3 个 API 调用细节问题。
lark-cli 是什么
lark-cli 是飞书(Lark)官方提供的命令行工具,可以用来操作飞书的各种资源——文档、表格、通讯录、多维表格等。和直接调用 HTTP API 相比,它帮你处理了认证、token 刷新等底层细节,适合在脚本或 CI/CD 流水线中使用。
两种认证模式:
- user-default:以个人身份操作,拥有完整资源权限,适合自动化脚本
- bot-only:以机器人身份操作,权限范围受限
如果用脚本操作自己的表格,user-default 模式更合适。安装很简单:从飞书官网下载 CLI 二进制,配置认证后就能直接用。
编写统一操作脚本
核心思路是把 lark-cli 的多维表格操作封装成一个 Python 脚本,提供统一的增删改查接口。
import subprocess, json, sys
BASE_TOKEN = "你的 Base Token"
TABLE_ID = "你的表格 ID"
def lark(*args: str) -> dict:
cmd = ["lark-cli", "base", *args, "--as", "user",
"--base-token", BASE_TOKEN, "--table-id", TABLE_ID,
"--format", "json"]
r = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
result = json.loads(r.stdout)
if not result.get("ok"):
print(f"❌ 操作失败: {result.get('message', 'unknown error')}", file=sys.stderr)
sys.exit(1)
return result
脚本支持的功能:
list 列出所有记录
get <record_id> 查看单条记录
create "标题" 新建记录
update <record_id> 更新字段
delete <record_id> 删除记录
summary 统计摘要
命令设计上统一了参数格式、输出格式和错误处理,避免每条命令都重复写 lark-cli 的调用参数。
全流程测试:发现了 3 个坑
脚本写完之后,我用一个测试选题跑了一遍完整的 Create → Get → Update → Delete 流程。不出意外,5 个步骤里有 3 个出了意外。
修复 1:record_id 解析路径不对
现象:create 命令执行成功,但返回的 record_id 是空的。
✅ 已创建: 飞书多维表格自动化操作指南
record_id:
根因:创建记录时用的 API 是 +record-batch-create,它返回的 JSON 结构里,新记录的 ID 放在 data.record_id_list(一个数组),但代码写成了 data.records[0].record_id。这个字段压根不存在。
# ❌ 错误——假设返回结构有 records
records = result.get("data", {}).get("records", [])
rid = records[0].get("record_id", "") if records else ""
# ✅ 正确——实际返回的是 record_id_list
rid_list = result.get("data", {}).get("record_id_list", [])
rid = rid_list[0] if rid_list else ""

这个问题本质上是对 API 返回结构的预设错了。用任何命令行工具封装 API 时,最好先用 --format json 实际看一次返回值结构,而不是靠文档猜。如果你的场景用 HTTP API 直调,也会遇到类似的问题——POST /v1/base/records 返回的字段名可能和 GET 不同,需要在测试阶段先确认。
修复 2:非交互环境 delete 崩溃
现象:在自动化流水线或 cron 中执行 delete 命令时,脚本直接崩溃。
⚠️ 确认删除记录 recvlxm7PeC32j?(y/N)
EOFError: EOF when reading a line
根因:input() 函数在 stdin 不可用(cron、CI 等非交互环境)时会抛出 EOFError。大多数 CLI 脚本的删除操作默认要求用户交互确认,但在自动化场景中不存在可以交互的人。
def cmd_delete(args):
if args.force:
lark("+record-delete", "--record-id", args.record_id, "--yes")
print(f"✅ 已删除 {args.record_id}")
return
try:
confirm = input(f"⚠️ 确认删除记录 {args.record_id}?(y/N) ")
except EOFError:
print("⛔ 非交互环境需加 --force 跳过确认", file=sys.stderr)
sys.exit(1)
# 正常交互确认...

这个问题的通用性很强——任何在交互模式和自动化模式间切换的脚本都会遇到。两条经验:
- 所有需要确认的写操作(删除、覆盖、批量修改),都设计一个
--force参数 - 用
try/except EOFError而不是先检测 stdin 是否 TTY——后者在管道调用时仍然可能误判
修复 3:标签字段非法值校验
现象:创建记录时传了一个不在选项列表中的标签值,API 直接拒绝。
❌ 操作失败: not_found
Value: "测试" // 不在可用选项列表中
根因:飞书多维表格的 multi_select(多选)字段有固定的选项集,传非法值会报错误码 800030005。它不会自动过滤或忽略——只要有一个值不合法,整个请求就失败。
VALID_TAGS = ("AI", "工具", "教程", "生活", "技术")
def _validate_tags(tag_str: str) -> list[str]:
tags = [t.strip() for t in tag_str.split(",") if t.strip()]
valid = []
for t in tags:
if t in VALID_TAGS:
valid.append(t)
else:
print(f"⚠️ 忽略无效标签: '{t}'(可选: {', '.join(VALID_TAGS)})")
return valid

类似的场景在操作枚举型字段(单选、多选、下拉列表)时很常见。一个更激进的做法是在脚本中加一个 --list-options 命令,自动拉取字段的可选值列表,这样即便表格选项变了也不需要改脚本代码。
一些经验总结
3 个 bug 的根因各不相同,但可以归纳为同一个教训:封装 API 时,你对返回结构的假设、对运行环境的假设、对字段约束的假设,每一项都可能在真实场景中被推翻。
- 返回结构和文档/直觉不一致 → 先拿 JSON 看一次
- 交互模式在自动化中不可用 → 设
--force参数 + 异常处理 - 固定选项字段传非法值 → 预校验或自动适配
工具链方面,lark-cli 本身没有大问题,但它的 +record-batch-create 返回结构、high-risk-write 确认机制这些细节,实测之后才会发现。用容器部署 lark-cli 配合 cron 定时执行也是个可行的方向,能进一步减少手动操作。
脚本化不是为了取代管理,而是把重复劳动交给机器,把精力留给决策。这个过程本身就是这系列文章最好的注释。