在飞书文档里写技术方案、记产品需求,写的时候挺爽,等要往外搬的时候就开始头疼。官方导出的格式是 Word、PDF,最多加个 HTML,导出的 Word 里表格排版乱得没法看,PDF 没法直接喂给 AI 或进 Git 仓库。我试过不少工具,最后固定在 feishu2md 这条路上,它能把飞书文档干净利落地转成标准 Markdown,保留标题层级、代码块、表格、甚至公式和图片引用。这篇就把我实际使用的经验完整写下来,包括原理、配置、避坑和自动化思路,给同样被飞书导出折磨的朋友一个可参考的方案。
1. 飞书文档能否直接导出 Markdown?先看看官方设计的边界
1.1 飞书官方的导出能力到底给到了哪一步
飞书云文档是国内团队协作中普及度很高的工具,尤其是字节系公司内部,以及很多以文档驱动协作的团队,几乎把需求、周报、技术设计全写在飞书里。但飞书的导出能力一直停留在“兼容办公格式”的思路上:支持 docx、pdf、纯文本,以及 HTML。这套设计本身没有错,因为多数人要的是打印、归档、给不熟悉 Markdown 的同事看,Markdown 反而需要额外解释。问题在于开发者把 Markdown 当作一等公民,希望在本地编辑器和代码仓库里继续维护这些文档。
飞书导出的 docx 格式,实际使用时你会发现几个明显的坑:
- 标题层级虽然能识别,但多级列表经常退化成带缩进的普通段落,甚至出现序号错乱。
- 表格的合并单元格、宽度设置、背景色会丢失,导出后表格挤成一团,在 Word 里手动调整耗时远超预期。
- 代码块的换行丢失,代码里所有缩进可能被替换为连续空格,贴回编辑器还能用,但放进 Markdown 的独立代码块时,空行会全部消失。
- 图片变成相对路径引用而非嵌入,因为飞书导出 docx 时把图片放在了一个同级目录里,一旦你单独发那个 Word 文件,图片全裂。
纯文本导出就更原始了,几乎只保留文字内容。HTML 导出看似完整,实际会塞入大量飞书自带的 class 和 inline style,转成 Markdown 时需要清洗,字符编码偶尔还会出问题。这些痛点叠加起来,让我决定寻找专攻“飞书转 Markdown”的小工具,而不是自己去写脚本反复解析 HTML。
1.2 feishu2md 这个项目是怎么切入问题的
feishu2md 是一个开源命令行工具,在 GitHub 上能找到,名字直译就是“飞书转 Markdown”。它不是把飞书当成静态网页去爬取,而是走飞书开放平台的云文档 API,通过官方接口拿到文档块数据,再在本地把这些块结构重新映射成 Markdown 语法。这个思路从根本上绕开了 HTML 解析的种种脏活,也能保证内容更新后可以随时重新拉取。
它的核心能力覆盖了我日常的绝大多数场景:
- 支持 docx、sheetx、bitablex 等不同类型的飞书文档和表格,当然最常用的是 docx 文档。
- 能把标题、有序列表、无序列表、任务列表、代码块、引用块、表格、图片、公式、高亮块等常见块类型转成对应的 Markdown 表达。
- 图片默认下载到目标目录,并自动生成相对路径引用,符合 Git 仓库存放要求。
- 公式块可以转成 LaTeX 表达式,配合支持数学公式的 Markdown 渲染器正好匹配。
- 支持在 config.json 里配置多个应用凭证,方便同一个工具服务多个用户或团队。
我选择它而不是自己写脚本,是因为它已经把 API 分页、块类型递归、图片上传和下载这些琐碎工作处理好了。自己用飞书 API 写过文档读取的人会懂,docx 是一棵树,子块里还有嵌套子块,光处理递归就得写不少代码,更别提表格块里的单元格还有独立 ID。
1.3 与市面上其他转换方案的横向对比
除了 feishu2md,市面上还有几种常见做法:
- 复制飞书文档全部内容,粘贴到支持 Markdown 的编辑器里,让它自动把 HTML 转成 Markdown。这个方法对简单文档管用,但一旦文档里有代码块、复杂表格、嵌套引用,结果往往需要大量手工修复。
- 用浏览器扩展在页面上直接操作 DOM,提取内容后生成 Markdown。这个方式对登录态的依赖强,飞书前端结构调整就可能失效,而且大文档容易卡死。
- 基于飞书开放 API 的云文档 SDK 自行开发转换脚本。灵活度最高,但需要自己处理鉴权、分页、块类型映射、资源下载,前期开发成本和后期维护成本都偏高。
- feishu2md 这种命令行工具刚好处在平衡点:配置一次,之后就是一条命令的事。它处理过的文档块类型已覆盖绝大多数飞书写作场景,即使遇到个别不支持的类型,也能通过配置或后续更新兜住。
对大多数技术团队来说,没有理由去重造轮子。个人作者、文档工程师、经常把飞书内容同步到 Git 仓库或内部知识库的运维、开发、项目经理,用 feishu2md 是省力且可靠的选择。
2. 从零开始配置 feishu2md 的完整流程
2.1 本地环境检查与 Node.js 安装
feishu2md 是 Node.js 项目,首先需要确认本机有可用的 Node 环境。以我常用的 Linux 服务器和 Mac 本机为例,先看版本:
node -v npm -v如果输出 v16 或更高的版本,一般没问题。低于 v16 建议升一下,因为工具本身依赖较新的 JavaScript 特性,旧版本的 Node 会报语法错误或出现模块加载异常。Windows 用户如果不想碰 WSL,直接用官方安装包装 Node.js LTS 版本也行,cmd 或 PowerShell 里跑命令同样支持。
安装完成后,我习惯使用 npx 方式直接执行 feishu2md,避免全局污染。如果不熟悉 npx,可以先全局安装:
npm install -g feishu2md全局安装的好处是之后在任意目录都能执行 feishu2md,对偶尔用一次的人来说,用 npx 也能临时拉取。两种方式都不影响后续配置。
2.2 在飞书开放平台创建自定义应用
feishu2md 需要调用飞书开放 API,所以必须有一个飞书自建应用。打开飞书开放平台(open.feishu.cn),用自己的飞书账号登录,然后在开发者后台创建一个企业自建应用。这里的几个关键步骤:
- 应用名称随便起,比如“docs-md-export”。
- 应用范围选择企业,如果只是个人使用,可以选“仅自己”。
- 创建完成后进入应用详情页,左侧菜单里找到“凭证与基础信息”,这里有 App ID 和 App Secret。这两个值就是 feishu2md 要用的关键凭证。
- 然后到“权限管理”里开通云文档相关的 API 权限。
权限配置是很多人第一次配置时最容易漏掉的环节。要选择至少以下三项权限:查看云文档、查看图片或附件、查看文档内容。具体字段名,飞书后台的权限名称会随版本变化,核心是 docx 的读权限和 drive 的读权限。如果只读取自己的文档,可以勾选“通过手机号或邮箱获取用户 ID”这类基础权限,但主要别漏了云文档和云空间中文件内容的查看权限。
我实际踩过的坑是:只开了文档读权限,没开图片资源权限,导致转换结果里所有图片都无法下载,Markdown 里图片引用指向本地不存在的文件。后来把“查看云空间文件”的权限补上,才正常。
2.3 初始化 feishu2md 配置
在项目目录下运行初始化命令:
feishu2md config这个命令会在当前目录生成一个 config.json 模板文件。也可以用编辑器手动创建:
{ "app_id": "cli_xxxxxxxxxxxxxxxx", "app_secret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "page_size": 50, "image_dir": "assets", "trim_suffix": true }字段作用说明一下:
- app_id 和 app_secret,填上一步创建应用时拿到的值。
- page_size 是每页拉取块数据的数量,默认 50 就行,太大了单次请求数据量高,反而容易触发 API 限流。
- image_dir 指定下载图片存放的目录,我习惯用 assets,这样 Markdown 文件中引用路径是
assets/xxx.png,符合主流静态站点生成器的习惯。 - trim_suffix 表示是否去除文件名中的飞书文档标题后缀,比如“xx需求文档.docx”中的“.docx”,默认开启。
配置完成后,可以先使用命令行方式验证凭证是否有效:直接带 app_id 和 app_secret 跑一次转换,如果报 403,回到飞书开放平台检查权限范围。工具支持通过环境变量或者命令行参数覆盖配置,比如:
feishu2md export <doc_token> -a cli_xxx -s xxxx但每次敲参数容易手滑,建议直接写好 config.json。
2.4 获取目标文档的 doc token
飞书云文档的链接长得像这样:
https://xxx.feishu.cn/docx/ABCDEF123456abcdef末尾那串 ABCDEF123456abcdef 就是文档的 token,也叫 doc token。feishu2md 需要的输入就是它。可以直接把完整链接传给工具,也可以只传 token。我自己习惯只传 token,因为完整链接偶尔会因为域名不同导致解析出错,但工具本身做了兼容,两种方式都行。
如果文档在某个文件夹里,需要的是文档本身的 token,而不是文件夹 token。飞书分享链接有时长这样:
https://xxx.feishu.cn/wiki/WikiToken?from=from_copylink这种是 Wiki 节点的 token,不是 docx 的 token。feishu2md 对纯 wiki 链接支持有限,我的处理办法是在浏览器里打开该文档,从地址栏里找到真正的 docx 链接,再复制 token。如果是多维表格或者电子表格,同样需要定位到表格自身的 token。
确认 token 后,跑一次导出:
feishu2md export ABCDEF123456abcdef正常的话,当前目录会出现一个以文档标题命名的 .md 文件,文档中所有图片也同时下载到 assets 目录。
3. 揭开转换原理:飞书文档块如何一步步变成 Markdown
3.1 飞书云文档的数据模型:块(Block)与子树(Children)
飞书 docx 文档在结构上是一棵块树。每个块有多种类型:heading1、heading2、paragraph、code、quote、table、image、file、todo、bullet、ordered、callout、divider、equation 等。块之间有父子关系,比如列表项下面可以有嵌套列表,表格单元格内部可以有段落,这些都是子块。
feishu2md 拿到文档 token 后,通过 API 逐层拉取块列表。它的工作方式类似于遍历树,先取根基下的所有块,发现某个块还有 children 就继续往下拉。官方 API 有分页限制,所以工具会循环请求。理解这个模型,对排查“某块内容没转出来”很有帮助。
比如你在飞书文档里插入了一个“高亮块”(callout),如果工具版本较旧,可能只提取了高亮块内的文本,没有把高亮块本身的提示类型转化为 Markdown 的引用块格式。这类问题在后续版本中逐步被改进。我建议保持工具版本常更新,并用 feishu2md 的 issue 区跟踪,新块类型的适配经常在小版本发布。
3.2 块类型到 Markdown 语法的映射规则
每位开发者写这种转换工具时,都会定义一套映射表。feishu2md 的处理逻辑大致如下:
| 飞书块类型 | Markdown 输出 |
|---|---|
| heading1 ~ heading9 | #~#######对应级别 |
| paragraph | 普通文本段落 |
| bullet | -无序列表项 |
| ordered | 1.有序列表项,序号根据顺序生成 |
| todo | - [ ]或- [x]任务列表 |
| code | 三重反引号围栏,并带上飞书代码块中记录的语言标识 |
| quote / callout | >引用块 |
| table | 标准 Markdown 表格,包含表头行与分隔行 |
| image | 下载图片后,输出 |
| equation | $$或$...$包裹 LaTeX 公式 |
| divider | 输出---水平线 |
| file | 下载附件,输出链接文本 |
这个映射并不总是完美。飞书的“有序列表”支持自定义起始序号,但 Markdown 标准只支持从 1 开始自动编号,所以自定义序号会被丢失。飞书的 callout 块支持背景色、标题、图标,Markdown 引用块没有这些属性,只会保留文本内容。这些是格式转换天然存在的损耗,提前知道就能在转换后做针对性检查。
3.3 表格块的嵌套处理与行列合并问题
飞书表格块的存储方式比较特殊:一个 table 块包含多个 table_cell 子块,每个 table_cell 内部又包含 paragraph 块。转换工具需要先读取 table 块的第一行作为表头,再从第二行开始作为数据行,拼接成管道分隔的 Markdown 表格。
如果单元格有合并行列,飞出 API 会给被合并的单元格标记为与某个“主力单元格”相同。feishu2md 对合并单元格的处理策略是:把被合并的格子在 Markdown 表格中输出为空字符串,这会导致表格横向列数不一致。我遇到这种情况时,会回到飞书文档里尽量减少合并单元格的使用,或者在导出后手工在 Markdown 表格里补上缺失列的占位内容。
另一个容易出问题的细节是表格文本里的竖线字符 |。飞书表格单元格如果包含英文竖线,直接拼进 Markdown 表格会把列结构打乱。feishu2md 是否有转义处理取决于版本。我建议在转换前全局搜索文档中的竖线,如果是业务数据里的合法字符,可以在转换后统一用\|替换。
3.4 图片下载、重命名与本路径映射
图片是幂等转换中最容易翻车的点。飞书 API 返回的图片块包含一个 file_token,需要先调用获取图片资源的接口,拿到图片的二进制流,再写入本地文件。文件名默认采用图片块的唯一 ID 或时间戳。
feishu2md 的处理结果是在 Markdown 中生成类似这样的引用:
如果图片较大或者数量较多,API 请求量会很大,可能触发飞书 API 对单个应用的频控限制。我在转换一个 60 多张图片的大文档时,遇到过中途开始有几张图片拉取失败的情况。解决的措施是:
- 调低 page_size,降低单次并发压力。
- 如果工具支持并发参数,把并发数限制在 5 以下。
- 检查 config.json 中图片目录配置,确保目录可写。
3.5 数学公式与代码块的字符保留策略
很多技术文档会在飞书里用公式块写数学推导,feishu2md 会读取公式块的 LaTeX 表达式,原样写入 Markdown。只要渲染器支持数学公式(比如 Typora 打开行内公式和块级公式,或者静态站点接入 MathJax / KaTeX),这些公式就能直接显示。
有一点要注意:飞书公式块的源码可能包含\begin{aligned}、\tag{1}等环境命令,写入 Markdown 后如果在某些平台上渲染异常,可以先确认目标渲染器的 KaTeX 版本是否支持这些命令。我通常先用 Typora 预览一遍,有问题再在公式前后手动微调。
代码块则相对简单,飞书代码块的语言标记可以直接对应 Markdown 围栏语言。如果代码块中的内容含有三重反引号,feishu2md 会采用四个反引号作为围栏,这是符合 CommonMark 的写法,在 GitHub 上也能正常渲染。代码块内的缩进和空行会被原样保留,这一点比 Word 导出可靠得多。
4. 实操演示与批量自动化方案
4.1 单篇文档转换的标准操作与参数说明
先进入一个空的工作目录,写好 config.json。然后执行:
feishu2md export <doc_token>导出后查看目录结构:
. ├── config.json ├── assets │ └── 1730000000000_xxxx.png └── 飞书文档标题.md打开生成的 md 文件,检查标题、列表、代码块和表格是否完整。我习惯把导出后的文件放到 Git 仓库里,用git diff对比不同版本的飞书文档,比自己手动复制粘贴效率高很多。
feishu2md 还有其他几个实用参数:
-o, --output <dir>:指定输出目录,默认是当前目录。-i, --image-dir <dir>:覆盖配置里的 image_dir。--no-download:跳过图片下载,只生成 Markdown 文本,适合只需要文字内容或者图床另行处理的场景。--后面跟完整 url,支持直接粘贴飞书分享链接。
我的常用命令:
feishu2md export "https://xxx.feishu.cn/docx/ABCDEF" -o ./output_dir4.2 批量转换多个文档的脚本思路
单个文档转换只是第一步。平时的一个痛点是团队知识库里有上百篇飞书文档,需要整体同步到 Git 仓库。这时候一条条跑命令太慢,我用一个简单的 shell 脚本循环处理:
#!/bin/bash tokens=( "docx_token_1" "docx_token_2" "docx_token_3" ) for token in "${tokens[@]}"; do feishu2md export "$token" -o ./docs || echo "failed: $token" done更智能的方式是用飞书开放 API 获取某个知识空间或文件夹下的所有文档 token,再调用 feishu2md。大致流程是:先用 API 拉取 wikispace 或 folder 的子节点列表,提取所有 node_token,再对每个 node_token 调用 feishu2md。飞书的权限校验比较严格,需要开通“获取文档目录信息”的权限。
如果有 Jenkins 或 GitHub Actions,可以把这个脚本放进定时任务,每天自动拉取一次,然后提交到 Git 仓库,实现文档和代码同步更新。我在项目里就设置了一个每天凌晨两点运行的 cron job,至今稳定运行了大半年,没出现过权限失效。
4.3 与静态网站生成器的配合使用
生成的 Markdown 文件可以直接放进 Hugo、VitePress、Docusaurus 或 MkDocs 项目中。需要注意几个适配细节:
- 图片路径默认指向
assets目录,如果静态站点要求图片和 md 文件放在同一级或者启用 page bundles,需要调整 image_dir 配置。 - 文档中提到站内链接时,飞书链接无法直接用在另一个文档中,需要手动把飞书链接替换为目标站点的相对链接。
- 文档开头的标题和站点的
title字段可能重复,建议把飞书文档的标题作为站点页面的title,Markdown 文件内部的第一级标题可以删掉。
如果使用 Typora 或者 Obsidian 这类本地编辑器,直接打开生成的 md 文件即可,图片相对路径默认就能渲染。Obsidian 的默认附件设置是复制到仓库附件目录,这里不建议额外修改,保持 feishu2md 生成的路径更省事。
4.4 输出文档内容的后处理清单
转换不是终点,我每次都会执行一个后处理清单:
- 全文搜索
\u00a0不换行空格,飞书有时会把连续空格变成不间断空格,在普通文本里显示正常,在代码块里会多出不可见字符。 - 检查表格行列数,特别是在存在合并单元格时。
- 检查所有图片是否成功下载,通过统计 md 文件中
![]数量和 assets 目录文件数对比。 - 检查代码块语言标签是否准确,飞书默认代码块可能是 JavaScript,但实际是 TypeScript,需要批量替换围栏语言。
- 检查文档标题中是否包含反斜杠或特殊 Unicode 字符,这些在 Windows 文件系统上可能导致文件名非法。
这些动作看起来繁琐,但每次转换后花两分钟检查,能避免把坏文件直接提交进仓库。
5. 高频问题排查与解决方案记录
5.1 报错 403:权限不足或凭证过期
feishu2md 运行时最常见的就是 403。原因四个:
- App Secret 填错,重新复制检查,注意不要多复制空格。
- 应用权限没开通,回到开放平台把云文档、云空间相关权限都勾上。
- 用户身份过期,飞书自建应用获取 tenant_access_token 一般不会过期,但如果是 user_access_token,则需要重新走 OAuth 授权。
- 文档没有授权给这个应用,如果开启了“应用仅可用以下范围”限制,需要把文档所在空间或文档本身加入可用范围。
我的建议是:在飞书开放平台的“权限管理”页面,找到“API 权限”,确认“云文档”和“云空间”两类的只读权限是开通状态。如果换了文档,立刻在同目录下先跑一次,能快速定位是不是权限配置的问题。
5.2 图片无法下载或下载不完整
图片问题常见于大文档。现象是转换命令输出成功,但 assets 目录里图片数量少于文档中的图片块数量,或者部分图片文件大小是 0 字节。原因通常是 API 限流。
缓解思路:
- 将 page_size 调低到 10~20。
- 运行期间不要并发跑其他飞书 API 的脚本。
- 使用官方 API 的配额查询接口观察用量。
- 如果仍然失败,可以为当前应用申请更高级别的速率配额。
另外,飞书图片下载接口有时会要求额外的 URL 参数,如果工具版本依赖的接口路径发生变化,也会导致全部图片下载失败。这时候升级 feishu2md 即可。
5.3 表格转出来后列错位或内容缺失
列错位几乎都是合并单元格导致的。飞书表格的合并单元格在 API 中表现为当前单元格没有独立内容,而是继承另一个单元格的 span。Markdown 表格不支持跨行跨列,所以转换后出现空白单元格是预期行为。
我的临时对策是让飞书文档尽量不用合并单元格,或者在文档中把需要展示的数据先拍平。如果合并无法避免,导出后用脚本扫描表格行中|的数量,不一致的地方重点修补。
5.4 换行符全部丢失或段落挤在一行
这是一个容易误判的问题。飞书 API 返回的段落文本是以块为单位的,一个 paragraph 块内部的换行行为取决于飞书的编辑模型。如果你在飞书里用“Enter”分段,那么会生成多个 paragraph 块;如果你用“Shift+Enter”强制换行,那么同一 paragraph 块内部会有text分段,Markdown 输出可能会合并成一行,或加入两个空格来保留软换行。
GitHub 风格的 Markdown 对末尾两个空格的软换行支持并不一致,Pandoc 可能忽略它。所以遇到多行内容挤在一起,最可靠的方式是在原文档中检查是否误用了 Shift+Enter。如果文档里大量使用软换行,建议在转出后做一次正则替换:把\n替换成\n\n,人为制造段落分隔。
5.5 生成的 Markdown 文件名含特殊符号导致冲突
飞书文档标题如果包含/、:、*等字符,在 Windows 上直接创建文件会报错,在 Linux 和 Mac 上虽能创建,但会在 Git 仓库中造成路径歧义。feishu2md 可能做了清洗,但版本不同,清洗规则不一。我建议自己加上一层文件名替换,在脚本里用echo "filename" | sed 's/[\/\:*?"<>|]/-/g'统一处理。
6. 用我的体会收尾:feishu2md 只是开始,后续才是价值所在
feishu2md 帮我解决的不仅是“飞书文档转 Markdown”这一个动作,它把飞书里沉淀的内容重新拉回到我能自由控制的技术栈里。过去团队的知识都锁在飞书 servers 里,不利于外部协作,也无法进入代码评审、持续集成等流程。现在通过命令行转换,文档能跟着版本走,能 diff,能接入文档自动化流水线,能翻成站点、PDF、甚至训练语料。
但也不要指望它是完美的。飞书里一些高级块,比如思维笔记、多维表格的视图看板,转换后不会保留交互特征,只会输出结构化数据或者文字。对需要原样保留飞书交互效果的场景,还是要考虑其他思路。而对大多数文本型文档、技术教程、需求描述来讲,feishu2md 已经够用。
最后分享一个小技巧:配合 Git 仓库使用的时候,别把生成的 assets 目录排除在提交之外。很多人只在 .gitignore 里写 node_modules,却忘了 assets,结果其他人 clone 仓库后看到的 md 全是裂图。把文档和图片一起提交,整条链路才真正闭环。如果你也在维护自己的知识库,建议在飞书文档里定一个约定:对于需要长期归档的文档,固定使用标准标题层级、标准表格和非合并单元格,这样 export 出来的 Markdown 几乎不需要手工修补,省下的时间足够你再去写几篇新文档。