简介:飞书多维表格 OpenClaw 技能包面向运营、项目管理者等非技术背景用户,提供从零搭建业务应用并完成日常创建、读取、更新、删除操作的一站式方案。包内共 13 个文件,包含七份 Markdown 文档,用于技能说明、权限配置、字段映射、自动化流程与公式参考;两份 Python 脚本分别实现 Bitable 模板创建与常用数据操作;另有安装脚本、版本管理配置与元数据文件。整体包体仅 55KB,轻量且便于分发,已有 90 人学习浏览。通过一键安装即可将模板部署到飞书多维表格,快速覆盖项目管理、客户关系维护、库存跟踪等场景,使用者无需编写复杂代码,就能按需调整字段与流程,降低企业应用构建门槛。同时,开放式的技能结构便于结合社区经验持续扩展,适合希望提升团队协作与数据处理效率的个人或小组直接使用。
1. 把飞书多维表格做成 OpenClaw 技能:先想清楚这包东西解决什么
OpenClaw 这名字念快了很像龙虾,但它在做的事一点不儿戏:用 Skills 把能力拆成一个个可以被自然语言触发的技能包。跑过一阵子的人应该都有同感——它能处理文件、能写代码、能接本地模型,可一旦牵扯到真实业务数据就抓瞎,记录全散在 Markdown 和日志里,根本没法拿来当业务系统用。飞书多维表格恰恰是很多团队数据最集中的地方。这份资源要解决的就是这个缺口:把多维表格封装成一个可一键安装的 OpenClaw 技能包,装好后你不需要手动调 API,跟 OpenClaw 说「把这周需求按状态分组查出来」,它就真去查;说「新增一条客户记录」,它就真去写。适合已经跑起 OpenClaw、又不想每次手撕接口的人,也适合准备把 agent 接进真实业务数据流的团队。
2. SKILL.md 与飞书 API 对接:技能包内部的三个关键约定
2.1 SKILL.md 是入口:模型靠 frontmatter 决定要不要调用
OpenClaw 的 skill 机制直接兼容 Claude 的 SKILL.md 规范,一个技能的本质就是一个目录,目录里放一个 SKILL.md 和若干脚本。SKILL.md 的 YAML frontmatter 里,name 是技能的身份证,description 决定了模型什么时候应该调用它。OpenClaw 沿用了这套路由逻辑,每轮对话会根据已安装技能的 description 做语义匹配,匹配到才加载对应的脚本。
--- name: feishu_bitable_crud description: 当用户需要把业务数据写入飞书多维表格、按条件查询、修改或删除已有记录时使用本技能。适用于任务跟踪、客户登记、巡检记录、日报汇总等场景。 version: 1.0.0 ---description 写得越具体,模型命中率越高。比如这里写明「任务跟踪、客户登记」这种典型场景,比写「操作用户数据」要好得多。它本质上不是文档,是路由表,决定模型在哪句话之后把手伸进这个技能目录。
2.2 飞书开放 API 的三件套:app_token、table_id、record_id
不管增删改查,所有多维表格记录操作都绕不开三个 ID。app_token 是多维表格本身的唯一标识,table_id 是表格内某个数据表的标识,record_id 是单条记录的标识。它们的来源分别是表格 URL 和创建记录时的返回值。
| 参数 | 来源 | 用途 |
|---|---|---|
| app_token | 表格 URL 中/base/后的一串字符 | 定位多维表格 |
| table_id | URL 中?table=后的字符串 | 定位数据表 |
| record_id | 创建/搜索记录时 API 返回 | 定位单条记录 |
对应到 HTTP 接口,记录类的核心 endpoints 就四个:
POST /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records GET /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records PUT /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id} DELETE /open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}注意更新用的是 PUT 不是 PATCH,而且 PUT 是全量更新——传过去的 fields 会整体替换,漏传的字段会被清空。这一点后面避坑章还会单独说,现在先记住:更新前要么先读一次现状,要么在脚本里做字段合并。
2.3 tenant_access_token:应用身份和用户身份要分清
飞书自建应用的鉴权分两种:tenant_access_token 以应用身份访问,user_access_token 以用户身份访问。日常 CRUD 用 tenant 就够了,用户身份还要走 OAuth 授权流程,在 agent 场景里反而碍事。获取 tenant token 的接口是 internal 的,用 app_id + app_secret 直接换,有效期 2 小时,同一个 token 在有效期内重复申请不会换新的。官方建议的做法是缓存到过期前几分钟再刷新。如果你的技能脚本是每次被调用时独立执行的,那每次重新申请也没问题,代价只是多发一次请求;如果做成常驻服务,就必须做缓存,不然高峰期并发刷新容易触发限流。
2.4 什么时候适合封装成 skill,什么时候该写独立脚本
这是个选型问题。只是临时把一张表导出来分析,写个一次性脚本更快,没必要套 skill。但如果「查多维表格」「改记录」这些操作要在多轮对话里反复出现,或者要同时服务多个模型(比如本地 qwen 和云端模型混用),那就值得封装成 skill。还有一个容易翻车的误用:有人把整段数据同步逻辑全写进 SKILL.md,让模型自己照着步骤现写脚本调用。结果模型每次生成的代码风格都不一样,报错也各不相同。正确做法是把可复用的逻辑沉淀成 scripts 下的 .py 文件,SKILL.md 只做路由和参数说明。
3. 从零搭建:飞书应用、多维表格与 OpenClaw 环境的四步准备
3.1 创建自建应用并开通 bitable 权限
先去飞书开放平台的开发者后台,「创建企业自建应用」,名称随意,比如「OpenClaw 数据助手」。创建完进入「权限管理」,搜索并开通以下权限范围:
| 权限 | 用途 |
|---|---|
| bitable:app:readonly | 读取多维表格元信息和记录 |
| bitable:app | 读写多维表格记录 |
如果只做查询,开 readonly 就够;要做 CRUD,直接开bitable:app。权限开通后必须「创建版本」并发布,权限才会真正生效。这一步很多人漏掉,在开发者后台改了权限但没发布版本,结果调接口永远报权限不足。
3.2 获取 app_id 与 app_secret,写一个拿 token 的脚本
在「凭证与基础信息」页面能看到 App ID 和 App Secret。Secret 只在首次创建时完整显示,之后只能重置,拿到后先存到安全的地方。下面是最小可用的取 token 脚本:
import requests def get_tenant_access_token(app_id: str, app_secret: str) -> str: url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" resp = requests.post(url, json={ "app_id": app_id, "app_secret": app_secret }) data = resp.json() if data.get("code") != 0: raise RuntimeError(f"token 获取失败: {data}") return data["tenant_access_token"] if __name__ == "__main__": # 替换成你自己的凭证,注意别把 secret 提交到 git print(get_tenant_access_token("cli_xxx", "your_app_secret"))接口地址是 internal 结尾,对应自建应用的内部匿名换 token 方式。返回的 JSON 里 code 为 0 才算成功,expire 字段是 7200 秒。这个版本没做缓存,日常脚本建议直接复用第 5.1 节改进后的 common.py。
3.3 创建多维表格,从 URL 里读出 app_token 与 table_id
在飞书云文档里新建一张多维表格,任意加一列测试数据。建好后看浏览器地址栏,URL 结构一般是:
https://xxx.feishu.cn/base/bascnXXXX?table=tblXXXX&view=vewXXXX/base/后到?table=前的那段是 app_token,table=后到&view=前的是 table_id。这里有个常见的误解:有的人以为 view_id 也要传,其实记录 CRUD 可以不指定视图,view_id 只影响视图层面的过滤和分组。还有一个容易漏的环节:自建应用创建后和这张表没有任何关系,必须在表格右上角「分享」里把应用添加为可编辑的协作者,否则就算权限范围开了,接口也拿不到这张表的数据。
3.4 安装 OpenClaw 并确认 skills 目录
不同系统安装方式差别不小,Windows 上最常见的路径是 WSL2 + Node.js LTS,OpenClaw 对 WSL2 环境有依赖,很多报错都出在 WSL 没初始化好。装好后先确认两个东西:一是 OpenClaw 能正常启动,二是 skills 目录存在。常见位置是用户目录下:
ls ~/.openclaw/skills如果目录不存在,手动建一个:
mkdir -p ~/.openclaw/skills把技能目录解压进去后,每个子目录就是一个技能,目录名即技能名,目录内必须有 SKILL.md。改完目录结构后要重启 OpenClaw 让技能被重新扫描加载。
3.5 第一个验证:用脚本列出表格里所有记录
环境就绪后,先不急着写技能,用最原始的方式验证链路通不通。下面这段代码直接查第一页记录:
import requests APP_TOKEN = "bascnXXXX" TABLE_ID = "tblXXXX" TOKEN = "你的 tenant_access_token" def list_records(page_size: int = 20): url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records" resp = requests.get( url, headers={"Authorization": f"Bearer {TOKEN}"}, params={"page_size": page_size} ) data = resp.json() if data.get("code") != 0: raise RuntimeError(f"查询失败: {data}") return data["data"]["items"] for item in list_records(): print(item["fields"])page_size 最大 100,默认 20。items 里每条记录都有一个 record_id 和 fields 字段,fields 里每个 key 是列名,value 是字段值。能看到数据说明应用权限、文档授权、token 三条链路都通了;如果是空表或者报 91402,直接去检查第 3.1 和第 3.3 那两步。
4. 日常 CRUD 落地:把增删改查写成 OpenClaw 技能命令
4.1 命令面怎么设计:四个动词加一个查询
技能的本质是把 API 包一层给模型用,命令设计得越贴近自然语言越好。我一般把技能脚本拆成四个入口文件,每个文件对应一个操作:
| 脚本 | 对应操作 | 核心参数 |
|---|---|---|
| add_record.py | 新增记录 | table_id、fields |
| search_records.py | 按条件查询 | table_id、filter、page_size |
| update_record.py | 修改记录 | record_id、fields |
| delete_record.py | 删除记录 | record_id |
这样模型在规划动作时不需要理解 API 细节,它只需要从 SKILL.md 的描述里知道「这个脚本负责新增,那个脚本负责修改」。命令拆细的好处是排错简单,坏处是文件多,但技能目录结构本来就鼓励这种多文件组织。
4.2 fields 字段的 JSON 结构:多行文本、数字、日期、人员各是什么样子
这是最容易出错的地方。多维表格的字段类型和 JSON 类型不是一一对应的,尤其多行文本,很多人第一次写都以为是普通字符串。多行文本字段的值必须是一组 text 段的数组:
{ "任务描述": [{"text": "完成 OpenClaw 技能安装文档"}], "优先级": "高", "预计工时": 4 }单选字段直接传字符串,数字字段传数字,日期字段传毫秒时间戳,人员字段传一个包含 open_id 的数组。日期字段如果传了字符串,接口不会报错但会静默写入失败,这是最坑的,具体现象放到避坑章讲。
4.3 写 SKILL.md:让模型知道什么时候用、参数怎么填
--- name: feishu_bitable_crud description: 当用户需要将数据写入、查询、修改或删除飞书多维表格时使用本技能。典型场景包括任务跟踪、客户登记、巡检记录、日报汇总。查询结果会返回记录 ID 和字段内容。 --- # 飞书多维表格 CRUD 通过四个命令脚本操作指定多维表格: - `python3 scripts/add_record.py '{"任务名称": "..."}'`:新增记录,第一个参数是 JSON 格式的字段值。 - `python3 scripts/search_records.py --keyword "关键词"`:按关键词搜索记录,返回记录 ID 和全部字段。 - `python3 scripts/update_record.py <record_id> '{"字段": "新值"}'`:按记录 ID 更新字段。 - `python3 scripts/delete_record.py <record_id>`:按记录 ID 删除记录。 所有脚本依赖同目录下 common.py 提供的 token 缓存,控制台输出中文说明,方便模型直接解析结果。SKILL.md 里的命令示例要保证参数顺序和脚本实现完全一致,模型会照着这个示例拼命令。示例里{"任务名称": "..."}这种 JSON 参数建议用单引号包住整个 JSON 而不是双引号,因为 shell 里双引号会做变量展开,一个 $ 符号就能让整条命令翻车。
4.4 add_record 与 update_record 的 Python 实现
common.py 从同目录的 config.json 里读 APP_TOKEN、TABLE_ID 和凭证,并提供带缓存的 get_tenant_access_token,下面脚本直接复用。add_record 的核心就是把字段 JSON 原样塞进请求体:
import json import sys import requests from common import get_tenant_access_token, APP_TOKEN, TABLE_ID def add_record(fields: dict) -> str: url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records" headers = {"Authorization": f"Bearer {get_tenant_access_token()}"} resp = requests.post(url, headers=headers, json={"fields": fields}) data = resp.json() if data.get("code") != 0: raise RuntimeError(f"新增失败: {data}") record_id = data["data"]["record"]["record_id"] print(f"已创建记录,ID: {record_id}") return record_id if __name__ == "__main__": fields = json.loads(sys.argv[1]) add_record(fields)sys.argv[1] 是从 SKILL.md 透传过来的第一个参数,也就是那段 JSON。requests.post 的 json 参数会自动把 dict 序列化,不需要手动 json.dumps。返回的 record_id 一定要打印出来,模型后续可能要拿它做 update 或 delete,没有这个 ID,更新和删除就无从下手。
update_record 比 add_record 多一个变化:URL 里要拼 record_id,而且建议先做字段合并。上面 2.2 说过 PUT 是全量替换,所以脚本里先 GET 一次现状再合并新字段,能避免把一个不小心漏传的列清空:
import json import sys import requests from common import get_tenant_access_token, APP_TOKEN, TABLE_ID def get_record(record_id: str) -> dict: url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records/{record_id}" headers = {"Authorization": f"Bearer {get_tenant_access_token()}"} resp = requests.get(url, headers=headers) return resp.json()["data"]["record"]["fields"] def update_record(record_id: str, new_fields: dict) -> None: old_fields = get_record(record_id) old_fields.update(new_fields) url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records/{record_id}" headers = {"Authorization": f"Bearer {get_tenant_access_token()}"} resp = requests.put(url, headers=headers, json={"fields": old_fields}) if resp.json().get("code") != 0: raise RuntimeError(f"更新失败: {resp.json()}") print(f"记录 {record_id} 已更新") if __name__ == "__main__": update_record(sys.argv[1], json.loads(sys.argv[2]))这里先读旧字段再合并,代价是多一次 GET 请求,换来的是一份后悔药——更新字段时漏传的列不会被悄悄清掉。delete_record 的代码类似,只是把 PUT 换成 DELETE,这里不重复贴了。
5. 避坑指南:权限、字段格式与 Windows 环境的五类踩坑记录
5.1 tenant_access_token 过期:明明刚换的 token,请求却报 99991672
现象:连续跑几个 CRUD 脚本,前两个成功,第三个突然报token invalid,代码逻辑没改过,重新执行又好了。
原因:tenant_access_token 的过期时间是 7200 秒,但 OpenClaw 调用多个脚本时,如果每个脚本都在顶部重新申请 token,而飞书对同一 app_id 的 token 有「同 token 续期」机制,一旦某个请求在过期边缘拿到旧 token 就会失效。更隐蔽的是,token 过期后旧 token 不会立即报错,而是等下一次真正的 HTTP 请求才暴露。
解决:把 token 获取逻辑收敛到 common.py,做内存级缓存,记录过期时间,剩余 300 秒内才重新申请。脚本每次调用都走这个函数,而不是各自直接 requests.post:
import json import time import requests _cache = {"token": None, "expire_at": 0} def get_tenant_access_token(): if _cache["token"] and time.time() < _cache["expire_at"] - 300: return _cache["token"] with open("config.json", encoding="utf-8") as f: cfg = json.load(f) url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" resp = requests.post(url, json={"app_id": cfg["app_id"], "app_secret": cfg["app_secret"]}) data = resp.json() if data.get("code") != 0: raise RuntimeError(f"token 获取失败: {data}") _cache["token"] = data["tenant_access_token"] _cache["expire_at"] = time.time() + data["expire"] return _cache["token"]_cache["expire_at"] - 300表示提前 5 分钟就当过期处理,给网络抖动留余量。另外注意,如果你把 token 打印到日志里方便调试,调试完记得关掉,token 属于敏感凭证。
5.2 多行文本字段更新后被悄悄清空:fields 里的类型写错了
现象:用 update_record 更新一条记录,只传了「优先级」字段,结果这条记录原来的「任务描述」没了,表格里只剩新传的列。
原因:多维表格的 PUT 是全量更新,fields 里没传的字段一律清空。更麻烦的是,多行文本的正确格式是[{"text": "内容"}]数组,如果图省事直接传字符串,接口不报错但写入结果不符合预期,看起来就像「数据丢了」。
解决:update_record 脚本里先 GET 再 merge,把新旧字段合并后再 PUT,逻辑已经在 4.4 的代码里。多行文本字段必须用数组结构包裹,这个可以在 common.py 里加一个 normalize 函数,检测到纯字符串就自动包一层 text 段落,避免每次手写。
5.3 报错 91402:权限范围开了,接口还是说 no permission
现象:文档里说开通了bitable:app,代码也完全照着文档写,但请求记录列表返回 code 91402,意思是权限不足。
原因:两层问题叠加。第一层,开通权限后没发布新版本,开发者后台的权限变更没生效;第二层,多维表格的文档所有者没有把自建应用添加为协作者,权限范围只代表应用有「能力」,不代表对某张具体表有「访问权」。
解决:先确认开发者后台里权限状态是「已发布」而不是「草稿」;再去表格右上角分享菜单里输入应用名字,把应用身份加为可编辑。加完协作者后不需要重新发布版本,等一两分钟即可。
5.4 Windows 上安装时报「无法安全验证 WSL2 环境」
现象:在 PowerShell 里跑 OpenClaw 的安装脚本,提示无法安全验证 WSL2 环境,让你在 PowerShell 里运行wsl --status,但跑了之后又看不到有效的分发版信息。
原因:常见两种情况:一是 WSL2 内核组件没更新,wsl --status显示的内核版本偏旧;二是 OpenClaw 安装脚本从 Windows 侧检查 WSL 时,依赖的环境变量 PATH 里没有 wsl.exe 所在目录。这类问题报错信息很吓人,但本质上不是 OpenClaw 的问题,是 WSL 环境本身没准备好。
解决:先在 PowerShell 里跑wsl --status确认版本,再执行wsl --update升级内核。确保 WSL 里至少有一个已安装的发行版(比如 Ubuntu),并用wsl -l -v确认版本是 2。最后重新打开 PowerShell 再跑安装脚本。从那以后我凡是看到「无法安全验证」类报错,第一反应永远是先查底层环境而不是怀疑项目本身。
5.5 日期字段写入和读取差了 8 小时
现象:往日期字段写入毫秒时间戳,表格里显示的时间和预期差了 8 小时;或者从表格读日期再写回其他系统,解析出来永远是 UTC。
原因:多维表格的日期字段存的是 UTC 毫秒时间戳,表格界面按服务器时区渲染。如果脚本里用datetime.now()生成时间戳,本地时区如果是东八区,写入后界面显示就会正确;但如果用datetime.utcnow()生成,界面显示就比实际少 8 小时。问题出在脚本里用错了时间生成函数。
解决:统一用本地时区生成时间戳:int(datetime.now().timestamp() * 1000)。反过来,读取后要展示到 Web 页面时,用datetime.fromtimestamp(ts / 1000)转成本地时间,而不是datetime.utcfromtimestamp。这个坑平时不显眼,一旦你的 OpenClaw 技能要跨时区协作,比如多个地区的任务跟踪,就会变成定时任务里最常见的翻车点。
6. 一键安装 zip 的组装思路与端到端验证
6.1 zip 里装了什么:目录结构与安装脚本做的事
整个技能包解压后应该是这样一个结构:
feishu_bitable_crud/ ├── SKILL.md ├── config.example.json ├── scripts/ │ ├── common.py │ ├── add_record.py │ ├── search_records.py │ ├── update_record.py │ └── delete_record.py └── install.shconfig.example.json 里放 app_id、app_secret、app_token、table_id 的占位符。install.sh 做的事很朴素:检查 python3 是否存在、把 config.example.json 复制成 config.json 并提示用户填入真实凭证、把整个目录复制到~/.openclaw/skills/下、最后打印出重启 OpenClaw 的提醒。Windows 用户可以用同逻辑的 install.bat,内部调 wsl 执行同一套脚本。
6.2 端到端验证:自然语言建一条记录再读出来
装好后不要在 IDE 里测 Python 脚本,直接在 OpenClaw 对话里说:「在飞书多维表格的任务表里新增一条记录,任务名称是『测试端到端链路』,优先级是高,预计工时写 2。」然后再说:「把刚才那条任务查出来,字段全列出来。」
如果模型正确调用了 add_record 再调 search_records,说明 SKILL.md 的 description 路由、脚本参数解析、字段格式三个环节全部正常。这一步是验收动作,不是可选动作。我每次装完新技能都强制走一遍这个闭环,缺了任何一环,后面调模型怎么调都是玄学。
6.3 进阶:让技能从「工具」变成「数据入口」
等 CRUD 跑通,可以加两个小改造:一是把 search_records.py 支持按 view_id 读取视图过滤结果,配合视图的分组统计,让模型直接回答「每个状态下有几条任务」这类聚合问题;二是加一个定时入口,利用系统 crontab 每天固定时间调用 search_records,把结果汇总成 Markdown 推给本地模型做日报生成。这样 OpenClaw 的技能就不再是偶尔敲一下的查询工具,而是团队数据流的固定入口。
这份压缩包已经把上面所有脚本和说明文件按目录结构整理好,拿到后改完 config.json 就能跑。希望帮到你。
本文还有配套的精品资源,点击获取