利用 Python 将飞书文档转成博客

需求

由于个人平常用飞书文档比较多,而且飞书写文档确实也挺方便, 如果能将飞书的文档直接转换为我的博客文档,那写文档就方便多了,因此我利用 python 中 streamlit,实现了可视化将飞书文档转换为博客文档

展示效果

启动项目

1
streamlit run feishu2blog.py

)

界面展示

第一步:由于需要读取飞书文档,因此需要使用飞书应用进行读取,飞书应用的创建可以参考官方文档

)

第二步:填写好飞书应用的 appId 和 appSecret 后就会出现如下的界面

)

第三步:根据点击 “生成并发布”

)

第四步:完成发布之后,就可以在个人的博客网站上查看到了

)
)

)

具体实现代码如下

说明:

  1. 这里使用了 feishu2md 这个开源项目(github 可以搜索到),用来下载飞书的文档
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
'''
飞书文档转成博客文档
'''
import streamlit as st
import os
from datetime import datetime
import re

class Feishu2Blog():
def __init__(self):
self.base_path = "/cloudide/workspace/MyBlogRepository/MyBlogRepository/hexo-blog"
self.blog_file_path = f"{self.base_path}/source/_posts"
pass

# 配置feishu2md 的配置
def config_feishu2md(self , app_id , app_secret):
# 进入基础目录
os.chdir(self.base_path)
# 执行命令:feishu2md config -a <app_id> -s <app_secret>
os.system(f"./feishu2md config --appId {app_id} --appSecret {app_secret}")

# 读取指定目录下的文件。
def read_file_in_dir(self, dir_path):
file_list = []
for root, dirs, files in os.walk(dir_path):
for file in files:
file_list.append(os.path.join(root, file))
return file_list

# 提取出飞书文档url中的token
def extract_token_from_feishu_url(self, feishu_file_url):
# 提取出飞书文档url中的token
token = feishu_file_url.split('?')[0].split('/')[-1]
return token

# 下载飞书文档
def download_feishu_doc(self, doc_url, doc_name):
token = self.extract_token_from_feishu_url(doc_url)
# 进入基础目录
os.chdir(self.base_path)
# 执行命令:feishu2md dl <飞书文档地址> -o <保存路径>
# 如果下载失败需要报错
try:
os.system(f"./feishu2md dl -o {self.blog_file_path}/{token} {doc_url}")
except Exception as e:
# 报错信息
st.error(f"下载失败:{e}")
# 更换文件名,原名叫 file_name , 新文件名是 doc_name
src_blog_file_name_list = self.read_file_in_dir(f"{self.blog_file_path}/{token}")
# 移动文件
os.rename(src_blog_file_name_list[0], f"{self.blog_file_path}/{doc_name}.md")

# 图片目录更改为博客名称目录
image_static_file_name_list = self.read_file_in_dir(f"{self.blog_file_path}/{token}/static")
for image_static_file_name in image_static_file_name_list:
imgage_name = image_static_file_name.split('/')[-1]
os.rename(image_static_file_name, f"{self.blog_file_path}/{doc_name}/{imgage_name}")

# 删除临时目录
if os.path.exists(f"{self.blog_file_path}/{token}/static"):
os.rmdir(f"{self.blog_file_path}/{token}/static")
if os.path.exists(f"{self.blog_file_path}/{token}"):
os.rmdir(f"{self.blog_file_path}/{token}")

return f"{self.blog_file_path}/{doc_name}.md"

# 保存上传的图片到本地
def save_uploaded_file(self, uploaded_file, directory="uploaded_files"):
image_home_file_path = f'{self.blog_file_path}/{directory}'
# 如果目录不存在,则创建
if not os.path.exists(image_home_file_path):
os.makedirs(image_home_file_path)

# 获取当前时间戳
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")

# 拼接新的文件名
image_home_file_name = f"{uploaded_file.name.split('.')[0]}_{timestamp}.{uploaded_file.name.split('.')[-1]}"
file_path = os.path.join(image_home_file_path, image_home_file_name)

# 保存文件
with open(file_path, "wb") as f:
f.write(uploaded_file.getbuffer())

return image_home_file_name

# 编写博客文件的头部和内容
def write_blog_header(self, blog_title, blog_tags, blog_category, home_image_file_name):
# 如果 home_image_file_name 为None,则不需要 配置 cover
# date 格式为 yyyy-MM-dd HH:mm:ss
date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if home_image_file_name is None:
blog_header = f"---\ntitle: {blog_title}\ndate: {date}\ntags: {blog_tags}\ncategories: {blog_category}\n---\n"
else:
blog_header = f"---\ntitle: {blog_title}\ndate: {date}\ntags: {blog_tags}\ncategories: {blog_category}\ncover: {home_image_file_name}\n---\n"
return blog_header

# 构建博客正文,需要替换飞书文档中的图片链接
def build_blog_content(self, doc_content):
# 提取文档中所有图片
image_static_file_name_list = re.findall(r'!\[\]\(.*static/.*?\)', doc_content)
for image_static_file_name in image_static_file_name_list:
doc_content = doc_content.replace(image_static_file_name, f"![]({image_static_file_name.split('/')[-1]})")
return doc_content

# 读取下载的飞书文档
def read_feishu_doc(self, doc_path):
with open(doc_path, 'r', encoding='utf-8') as f:
doc_content = f.read()
return doc_content

# 生成博客文件
def generate_blog_file(self, blog_title, blog_tags, blog_category, home_image_file_name):
blog_file = f"{self.blog_file_path}/{blog_title}.md"
doc_content = self.read_feishu_doc(doc_path=blog_file)
# 构建博客文件内容
blog_content = self.write_blog_header(blog_title, blog_tags, blog_category, home_image_file_name) + self.build_blog_content(doc_content)
# 写入博客文件
with open(blog_file, 'w', encoding='utf-8') as f:
f.write(blog_content)

# 使用Hexo创建博客
def create_blog(self, blog_title):
# 进入到博客目录
os.chdir(self.base_path)
# 执行创建命令
os.system(f"hexo new {blog_title}")

# 运行发布流程
def delopy(self):
# 进入到博客目录
os.chdir(self.base_path)
# 执行发布命令
os.system("hexo clean && hexo generate && hexo deploy")

def main(self):
# 设置页面标题
st.title("飞书文档到博客发布工具")

# 在侧边栏添加输入框来输入飞书应用的 App ID 和 App Secret
app_id = st.sidebar.text_input("飞书应用的 App ID", help="请输入飞书应用的 App ID")
app_secret = st.sidebar.text_input("飞书应用的 App Secret", type="password", help="请输入飞书应用的 App Secret")

# 判断是否已提供正确的 App ID 和 App Secret
if app_id and app_secret:
# 在主界面添加输入框
doc_url = st.text_input("请输入飞书文档 URL")
blog_title = st.text_input("博客标题")
home_image = st.file_uploader("主页图片", type=["png", "jpg", "jpeg"], accept_multiple_files=False)
blog_tags = st.text_input("博客标签(用逗号分隔)")
blog_category = st.text_input("博客分类(用逗号分隔)")

# 在主界面添加按钮
if st.button("生成并发布"):
# 检查必填项是否已填写
if not doc_url or not blog_title:
st.error("请填写所有必填项:飞书文档 URL 和 博客标题")
else:
home_image_file_name = None
if home_image:
# 保存上传的图片文件
home_image_file_name = self.save_uploaded_file(home_image , blog_title)
with st.spinner('生成并发布博客中...'):
self.config_feishu2md(app_id, app_secret)
self.create_blog(blog_title)
# 下载
self.download_feishu_doc(doc_url, blog_title)
# 生成博客
self.generate_blog_file(blog_title , blog_tags, blog_category, home_image_file_name)
# 发布
self.delopy()
st.success("博客发布成功!")
else:
st.error("请输入飞书应用的 App ID 和 App Secret")

if __name__ == "__main__":
feishu2blog = Feishu2Blog()
feishu2blog.main()