飞书文档创建(Feishu DocX API)
通过飞书 DocX API 创建富格式文档,支持文本格式、标题、列表、表格、代码块、Callout、公式等全部原生能力。
触发条件
- "写个飞书文档"、"创建飞书文档"
- "生成飞书doc"、"feishu doc"
- "写到飞书文档里"、"输出成飞书文档"
前置条件
- 飞书 App 需要
docx:document和drive:drive权限 - 环境变量已配置:
FEISHU_APP_ID_JARVIS/FEISHU_APP_SECRET_JARVIS(或对应 bot 的 env)
API 概览
1. 创建文档
import os, lark_oapi as lark
from lark_oapi.api.docx.v1 import *
client = lark.Client.builder() \
.app_id(os.environ['FEISHU_APP_ID_JARVIS']) \
.app_secret(os.environ['FEISHU_APP_SECRET_JARVIS']) \
.domain(lark.FEISHU_DOMAIN).build()
req = CreateDocumentRequest.builder() \
.request_body(CreateDocumentRequestBody.builder()
.title("文档标题")
.folder_token("") # 空字符串 = 根目录
.build()).build()
resp = client.docx.v1.document.create(req)
doc_id = resp.data.document.document_id
2. 添加内容块
body = CreateDocumentBlockChildrenRequestBody.builder() \
.children(blocks) # List[Block]
.index(-1) # -1 = 追加到末尾
.build()
req = CreateDocumentBlockChildrenRequest.builder() \
.document_id(doc_id) \
.block_id(parent_id) # doc_id = 根级,或容器 block_id
.document_revision_id(-1) # -1 = 最新版本
.request_body(body).build()
resp = client.docx.v1.document_block_children.create(req)
3. 获取子块(表格填充用)
req = GetDocumentBlockChildrenRequest.builder() \
.document_id(doc_id) \
.block_id(table_block_id) \
.build()
resp = client.docx.v1.document_block_children.get(req)
cell_ids = [item.block_id for item in resp.data.items]
Block Types 速查
| block_type | 类型 | Builder setter | 说明 |
|---|---|---|---|
| 2 | text | .text() |
普通文本段落 |
| 3-8 | heading1-6 | .heading1() ~ .heading6() |
标题 H1-H6 |
| 12 | bullet | .bullet() |
无序列表 |
| 13 | ordered | .ordered() |
有序列表 |
| 14 | code | .code() |
代码块 |
| 15 | quote | .quote() |
引用行 |
| 17 | todo | .todo() |
任务清单 |
| 19 | callout | .callout() |
高亮块(容器) |
| 22 | divider | .divider({}) |
分割线 |
| 25 | grid | .grid() |
多列布局(API 不支持创建) |
| 27 | iframe | .iframe() |
内嵌网页 |
| 28 | image | .image() |
图片(需先上传获取 token) |
| 31 | table | .table() |
表格 |
| 34 | quote_container | .quote_container({}) |
引用容器(可嵌套) |
构建 Block 的工具函数
以下函数可直接复制使用:
make_text_element — 创建带格式的文本片段
def make_text_element(content, bold=False, italic=False, underline=False,
strikethrough=False, inline_code=False,
text_color=None, bg_color=None, link=None):
style_builder = TextElementStyleBuilder()
if bold: style_builder.bold(True)
if italic: style_builder.italic(True)
if underline: style_builder.underline(True)
if strikethrough: style_builder.strikethrough(True)
if inline_code: style_builder.inline_code(True)
if text_color: style_builder.text_color(text_color) # 1=红 2=橙 3=黄 4=绿 5=蓝 6=紫
if bg_color: style_builder.background_color(bg_color) # 同上
if link: style_builder.link(LinkBuilder().url(link).build())
return TextElementBuilder() \
.text_run(TextRunBuilder().content(content)
.text_element_style(style_builder.build()).build()).build()
make_text_block — 创建文本类 Block
def make_text_block(elements, block_type=2, align=None, style_kwargs=None):
style_builder = TextStyleBuilder()
if align: style_builder.align(align) # 1=左 2=中 3=右
if style_kwargs:
if 'language' in style_kwargs: style_builder.language(style_kwargs['language'])
if 'done' in style_kwargs: style_builder.done(style_kwargs['done'])
text = TextBuilder().elements(elements).style(style_builder.build()).build()
bb = BlockBuilder().block_type(block_type)
type_map = {
2: 'text', 3: 'heading1', 4: 'heading2', 5: 'heading3',
6: 'heading4', 7: 'heading5', 8: 'heading6',
12: 'bullet', 13: 'ordered', 14: 'code', 15: 'quote', 17: 'todo',
}
getattr(bb, type_map.get(block_type, 'text'))(text)
return bb.build()
make_divider — 分割线
def make_divider():
return BlockBuilder().block_type(22).divider({}).build()
常用代码块语言 ID
| ID | 语言 | ID | 语言 |
|---|---|---|---|
| 1 | PlainText | 22 | JavaScript |
| 15 | Bash/Shell | 40 | TypeScript |
| 49 | Python | 18 | Java |
| 19 | JSON | 12 | Go |
| 7 | C++ | 56 | YAML |
| 29 | Markdown | 54 | SQL |
| 53 | Rust | 4 | C |
Callout 高亮块
Callout 是容器块,创建后需要往内部添加子块:
callout = BlockBuilder().block_type(19) \
.callout(CalloutBuilder()
.background_color(4) # 1=红 2=橙 3=黄 4=绿 5=蓝 6=紫
.border_color(4)
.emoji_id("bulb") # 英文 emoji 名称,非 unicode
.build()).build()
ids = add_blocks([callout])
add_blocks([make_text_block(...)], parent_id=ids[0]) # 填充内容
常用 emoji_id: bulb, check, warning, star, crystal_ball, rocket, fire, heart, memo, pushpin
表格
表格创建后自动生成空 cell,需要查询 cell_ids 再逐个填充:
table = BlockBuilder().block_type(31) \
.table(TableBuilder()
.property(TablePropertyBuilder().row_size(3).column_size(4).build())
.cells([]).build()).build()
table_ids = add_blocks([table])
# 获取 cell block_ids
req = GetDocumentBlockChildrenRequest.builder() \
.document_id(doc_id).block_id(table_ids[0]).build()
resp = client.docx.v1.document_block_children.get(req)
cell_ids = [item.block_id for item in resp.data.items]
# cells 按行优先排列: [row0col0, row0col1, ..., row1col0, ...]
for idx, cell_id in enumerate(cell_ids):
row, col = idx // num_cols, idx % num_cols
add_blocks([make_text_block([make_text_element(data[row][col])])], parent_id=cell_id)
行内公式(LaTeX)
make_text_block([
make_text_element("能量公式:"),
TextElementBuilder().equation(
EquationBuilder().content("E = mc^2").build()
).build(),
])
引用容器
容器块,可嵌套文本、列表等:
qc = BlockBuilder().block_type(34).quote_container({}).build()
qc_ids = add_blocks([qc])
add_blocks([make_text_block(...)], parent_id=qc_ids[0])
图片
需要先上传文件获取 image token,再创建 image block:
# 1. 上传图片到飞书(使用 drive API 的 upload_media)
# 2. 创建 image block
image_block = BlockBuilder().block_type(28) \
.image(ImageBuilder().token(image_token).width(800).height(400).build()).build()
注意事项
- Rate limit: 每秒约 5 次 API 调用,批量操作间加
time.sleep(0.2) - Grid 不支持 API 创建:
block_type=25返回block not support to create,只能在客户端手动添加 - 容器块填充: Callout (19)、Quote Container (34)、Table Cell (32) 都是容器,创建后需要往 children 添加内容
- document_revision_id=-1: 总是使用最新版本,避免并发冲突
- 文档 URL:
https://bytedance.feishu.cn/docx/{document_id}
工作流程
- 创建空文档 → 拿到
document_id - 规划内容结构 → 标题、段落、表格、Callout 等
- 批量添加块 → 一次 API 调用可加多个同级 block
- 填充容器 → 表格 cell、callout、quote_container 需要二次填充
- 返回文档链接 →
https://bytedance.feishu.cn/docx/{document_id}