飞书多维表格接入OpenClaw:打造可自然语言调用的业务数据CRUD技能包
简介飞书多维表格 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_idURL 中?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(ftoken 获取失败: {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?tabletblXXXXviewvewXXXX/base/后到?table前的那段是 app_tokentable后到view前的是 table_id。这里有个常见的误解有的人以为 view_id 也要传其实记录 CRUD 可以不指定视图view_id 只影响视图层面的过滤和分组。还有一个容易漏的环节自建应用创建后和这张表没有任何关系必须在表格右上角「分享」里把应用添加为可编辑的协作者否则就算权限范围开了接口也拿不到这张表的数据。3.4 安装 OpenClaw 并确认 skills 目录不同系统安装方式差别不小Windows 上最常见的路径是 WSL2 Node.js LTSOpenClaw 对 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 fhttps://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records resp requests.get( url, headers{Authorization: fBearer {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、fieldssearch_records.py按条件查询table_id、filter、page_sizeupdate_record.py修改记录record_id、fieldsdelete_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 fhttps://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records headers {Authorization: fBearer {get_tenant_access_token()}} resp requests.post(url, headersheaders, 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 fhttps://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records/{record_id} headers {Authorization: fBearer {get_tenant_access_token()}} resp requests.get(url, headersheaders) 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 fhttps://open.feishu.cn/open-apis/bitable/v1/apps/{APP_TOKEN}/tables/{TABLE_ID}/records/{record_id} headers {Authorization: fBearer {get_tenant_access_token()}} resp requests.put(url, headersheaders, 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.postimport 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, encodingutf-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(ftoken 获取失败: {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 就能跑。希望帮到你。本文还有配套的精品资源点击获取