在飞书文档里写技术方案、记产品需求写的时候挺爽等要往外搬的时候就开始头疼。官方导出的格式是 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?fromfrom_copylink这种是 Wiki 节点的 token不是 docx 的 token。feishu2md 对纯 wiki 链接支持有限我的处理办法是在浏览器里打开该文档从地址栏里找到真正的 docx 链接再复制 token。如果是多维表格或者电子表格同样需要定位到表格自身的 token。确认 token 后跑一次导出feishu2md export ABCDEF123456abcdef正常的话当前目录会出现一个以文档标题命名的 .md 文件文档中所有图片也同时下载到 assets 目录。3. 揭开转换原理飞书文档块如何一步步变成 Markdown3.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-无序列表项ordered1.有序列表项序号根据顺序生成todo- [ ]或- [x]任务列表code三重反引号围栏并带上飞书代码块中记录的语言标识quote / callout引用块table标准 Markdown 表格包含表头行与分隔行image下载图片后输出![描述](assets/图片名.png)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 中生成类似这样的引用![图片](assets/1730000000000_xxx.png)如果图片较大或者数量较多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字段可能重复建议把飞书文档的标题作为站点页面的titleMarkdown 文件内部的第一级标题可以删掉。如果使用 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 调低到 1020。运行期间不要并发跑其他飞书 API 的脚本。使用官方 API 的配额查询接口观察用量。如果仍然失败可以为当前应用申请更高级别的速率配额。另外飞书图片下载接口有时会要求额外的 URL 参数如果工具版本依赖的接口路径发生变化也会导致全部图片下载失败。这时候升级 feishu2md 即可。5.3 表格转出来后列错位或内容缺失列错位几乎都是合并单元格导致的。飞书表格的合并单元格在 API 中表现为当前单元格没有独立内容而是继承另一个单元格的 span。Markdown 表格不支持跨行跨列所以转换后出现空白单元格是预期行为。我的临时对策是让飞书文档尽量不用合并单元格或者在文档中把需要展示的数据先拍平。如果合并无法避免导出后用脚本扫描表格行中|的数量不一致的地方重点修补。5.4 换行符全部丢失或段落挤在一行这是一个容易误判的问题。飞书 API 返回的段落文本是以块为单位的一个 paragraph 块内部的换行行为取决于飞书的编辑模型。如果你在飞书里用“Enter”分段那么会生成多个 paragraph 块如果你用“ShiftEnter”强制换行那么同一 paragraph 块内部会有text分段Markdown 输出可能会合并成一行或加入两个空格来保留软换行。GitHub 风格的 Markdown 对末尾两个空格的软换行支持并不一致Pandoc 可能忽略它。所以遇到多行内容挤在一起最可靠的方式是在原文档中检查是否误用了 ShiftEnter。如果文档里大量使用软换行建议在转出后做一次正则替换把\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 几乎不需要手工修补省下的时间足够你再去写几篇新文档。