github star gitee star atomgit star PyPI Downloads AI编程 AI交流群

大家好,这里是程序员晚枫,正在all in AI编程实战

第6讲:云端飞跃:将你的应用打造成网页版AI插件大全

大家好,我是程序员晚枫。在上一讲中,我们使用PySide6创建了专业的桌面应用程序。今天,我们要迈出更重要的一步:将你的工具转化为网页服务,打造一个属于你自己的"AI插件大全"网站!

为什么需要网页版?

在维护python-office项目的过程中,我发现了用户的一个普遍需求:

"晚枫,这些工具很好用,但每次都要安装Python环境,能不能直接在网页上使用?"

网页版的优势显而易见:

  • 零安装:用户无需任何环境配置
  • 跨平台:手机、电脑、平板都能访问
  • 易于分享:一个链接就能让全世界使用
  • 持续更新:功能更新对用户无感知

技术选型:为什么选择Streamlit?

在对比了多个Web框架后,我最终选择了Streamlit,原因如下:

  • 极简开发:用Python脚本就能创建Web应用
  • AI友好:专为机器学习和数据科学设计
  • 部署简单:一键部署到Streamlit Cloud
  • 完美兼容:与python-officepopdf无缝集成

环境准备

1
2
3
4
5
# 安装必要的库
pip install streamlit popdf python-office streamlit-file-browser

# 验证安装
streamlit hello

实战:30分钟创建AI工具网站

基础框架:创建你的第一个Web应用

创建一个名为ai_tools_website.py的文件:

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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
import streamlit as st
import popdf
import office
import os
from pathlib import Path
import tempfile
import base64

# 页面配置
st.set_page_config(
page_title="AI办公工具大全 - 程序员晚枫",
page_icon="🚀",
layout="wide",
initial_sidebar_state="expanded"
)

# 自定义CSS样式
st.markdown("""
<style>
.main-header {
font-size: 2.5rem;
color: #1f77b4;
text-align: center;
margin-bottom: 2rem;
}
.tool-card {
padding: 1.5rem;
border-radius: 10px;
border: 1px solid #ddd;
margin-bottom: 1rem;
background-color: #f9f9f9;
}
.success-msg {
padding: 1rem;
background-color: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 5px;
color: #155724;
}
.error-msg {
padding: 1rem;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 5px;
color: #721c24;
}
</style>
""", unsafe_allow_html=True)

def main():
# 网站标题
st.markdown('<div class="main-header">🚀 AI办公工具大全 - 基于python-office</div>', unsafe_allow_html=True)

# 作者信息
st.sidebar.markdown("## 👨‍💻 关于作者")
st.sidebar.info("**程序员晚枫** \n`python-office`开源项目创始人 \n专注于AI办公自动化开发")

# 网站统计
st.sidebar.markdown("## 📊 网站统计")
st.sidebar.metric("工具数量", "8")
st.sidebar.metric("今日使用", "127")
st.sidebar.metric("总使用次数", "2,843")

# 工具导航
st.sidebar.markdown("## 🛠️ 工具导航")
tool_choice = st.sidebar.radio(
"选择工具",
["PDF工具", "文档转换", "文件处理", "图片工具", "关于我们"]
)

# 根据选择显示不同工具
if tool_choice == "PDF工具":
show_pdf_tools()
elif tool_choice == "文档转换":
show_document_tools()
elif tool_choice == "文件处理":
show_file_tools()
elif tool_choice == "图片工具":
show_image_tools()
else:
show_about()

def show_pdf_tools():
"""PDF工具页面"""
st.header("📄 PDF工具集")

# 创建选项卡
tab1, tab2, tab3, tab4 = st.tabs(["PDF转Word", "Word转PDF", "PDF合并", "PDF分割"])

with tab1:
st.subheader("PDF转Word工具")
st.markdown("使用`popdf`库实现高质量的PDF到Word转换")

uploaded_file = st.file_uploader("上传PDF文件", type=["pdf"], key="pdf2word")

if uploaded_file is not None:
# 显示文件信息
file_details = {
"文件名": uploaded_file.name,
"文件类型": uploaded_file.type,
"文件大小": f"{uploaded_file.size / 1024:.2f} KB"
}
st.write(file_details)

# 转换选项
col1, col2 = st.columns(2)
with col1:
output_filename = st.text_input("输出文件名", value=uploaded_file.name.replace(".pdf", ".docx"))
with col2:
if st.button("开始转换", key="convert_pdf2word"):
with st.spinner("转换中,请稍候..."):
try:
# 保存上传的文件到临时目录
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
tmp_file.write(uploaded_file.getvalue())
tmp_path = tmp_file.name

# 执行转换
output_path = os.path.join(tempfile.gettempdir(), output_filename)
popdf.pdf2docx(input_file=tmp_path, output_file=output_path)

# 提供下载
with open(output_path, "rb") as file:
btn = st.download_button(
label="下载Word文档",
data=file,
file_name=output_filename,
mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)

st.markdown('<div class="success-msg">✅ 转换成功!</div>', unsafe_allow_html=True)

# 清理临时文件
os.unlink(tmp_path)
os.unlink(output_path)

except Exception as e:
st.markdown(f'<div class="error-msg">❌ 转换失败: {str(e)}</div>', unsafe_allow_html=True)

with tab2:
st.subheader("Word转PDF工具")
st.markdown("使用`python-office`库将Word文档转换为PDF")

uploaded_file = st.file_uploader("上传Word文档", type=["docx"], key="word2pdf")

if uploaded_file is not None:
file_details = {
"文件名": uploaded_file.name,
"文件类型": uploaded_file.type,
"文件大小": f"{uploaded_file.size / 1024:.2f} KB"
}
st.write(file_details)

if st.button("开始转换", key="convert_word2pdf"):
with st.spinner("转换中,请稍候..."):
try:
# 保存上传的文件
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp_file:
tmp_file.write(uploaded_file.getvalue())
tmp_path = tmp_file.name

# 执行转换
output_filename = uploaded_file.name.replace(".docx", ".pdf")
output_path = os.path.join(tempfile.gettempdir(), output_filename)
office.word2pdf(input_path=tmp_path, output_path=output_path)

# 提供下载
with open(output_path, "rb") as file:
btn = st.download_button(
label="下载PDF文档",
data=file,
file_name=output_filename,
mime="application/pdf"
)

st.markdown('<div class="success-msg">✅ 转换成功!</div>', unsafe_allow_html=True)

# 清理临时文件
os.unlink(tmp_path)
os.unlink(output_path)

except Exception as e:
st.markdown(f'<div class="error-msg">❌ 转换失败: {str(e)}</div>', unsafe_allow_html=True)

with tab3:
st.subheader("PDF合并工具")
st.markdown("合并多个PDF文件为一个")

uploaded_files = st.file_uploader(
"选择多个PDF文件",
type=["pdf"],
accept_multiple_files=True,
key="merge_pdf"
)

if uploaded_files and len(uploaded_files) > 1:
st.write(f"已选择 {len(uploaded_files)} 个PDF文件")

# 显示文件列表
for i, file in enumerate(uploaded_files):
st.write(f"{i+1}. {file.name} ({file.size / 1024:.2f} KB)")

output_filename = st.text_input("合并后文件名", value="合并文档.pdf", key="merge_output")

if st.button("开始合并", key="merge_pdfs"):
with st.spinner("合并中,请稍候..."):
try:
# 保存所有上传的文件
temp_files = []
for uploaded_file in uploaded_files:
tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
tmp_file.write(uploaded_file.getvalue())
temp_files.append(tmp_file.name)
tmp_file.close()

# 执行合并
output_path = os.path.join(tempfile.gettempdir(), output_filename)
popdf.merge_pdf(input_path=tempfile.gettempdir(), output_file=output_path)

# 提供下载
with open(output_path, "rb") as file:
btn = st.download_button(
label="下载合并后的PDF",
data=file,
file_name=output_filename,
mime="application/pdf"
)

st.markdown('<div class="success-msg">✅ 合并成功!</div>', unsafe_allow_html=True)

# 清理临时文件
for temp_file in temp_files:
os.unlink(temp_file)
os.unlink(output_path)

except Exception as e:
st.markdown(f'<div class="error-msg">❌ 合并失败: {str(e)}</div>', unsafe_allow_html=True)

with tab4:
st.subheader("PDF分割工具")
st.markdown("将PDF文件按页面分割")

st.info("PDF分割功能正在开发中,敬请期待!")
st.markdown("""
**计划功能:**
- 按页面范围分割
- 按书签分割
- 按文件大小分割
- 批量分割功能
""")

def show_document_tools():
"""文档转换工具页面"""
st.header("📝 文档转换工具")

col1, col2 = st.columns(2)

with col1:
st.subheader("Excel转PDF")
uploaded_file = st.file_uploader("上传Excel文件", type=["xlsx", "xls"], key="excel2pdf")

if uploaded_file is not None:
if st.button("Excel转PDF", key="convert_excel2pdf"):
with st.spinner("转换中..."):
try:
# 保存上传的文件
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp_file:
tmp_file.write(uploaded_file.getvalue())
tmp_path = tmp_file.name

# 执行转换
output_filename = uploaded_file.name.replace(".xlsx", ".pdf").replace(".xls", ".pdf")
output_path = os.path.join(tempfile.gettempdir(), output_filename)
office.excel2pdf(input_path=tmp_path, output_path=output_path)

# 提供下载
with open(output_path, "rb") as file:
btn = st.download_button(
label="下载PDF",
data=file,
file_name=output_filename,
mime="application/pdf"
)

st.success("转换成功!")

# 清理临时文件
os.unlink(tmp_path)
os.unlink(output_path)

except Exception as e:
st.error(f"转换失败: {str(e)}")

with col2:
st.subheader("PPT转PDF")
uploaded_file = st.file_uploader("上传PPT文件", type=["pptx", "ppt"], key="ppt2pdf")

if uploaded_file is not None:
if st.button("PPT转PDF", key="convert_ppt2pdf"):
with st.spinner("转换中..."):
try:
# 保存上传的文件
with tempfile.NamedTemporaryFile(delete=False, suffix=".pptx") as tmp_file:
tmp_file.write(uploaded_file.getvalue())
tmp_path = tmp_file.name

# 执行转换
output_filename = uploaded_file.name.replace(".pptx", ".pdf").replace(".ppt", ".pdf")
output_path = os.path.join(tempfile.gettempdir(), output_filename)
office.ppt2pdf(input_path=tmp_path, output_path=output_path)

# 提供下载
with open(output_path, "rb") as file:
btn = st.download_button(
label="下载PDF",
data=file,
file_name=output_filename,
mime="application/pdf"
)

st.success("转换成功!")

# 清理临时文件
os.unlink(tmp_path)
os.unlink(output_path)

except Exception as e:
st.error(f"转换失败: {str(e)}")

def show_file_tools():
"""文件处理工具页面"""
st.header("📁 文件处理工具")

tab1, tab2 = st.tabs(["批量重命名", "文件整理"])

with tab1:
st.subheader("文件批量重命名")
st.markdown("对文件夹中的文件进行批量重命名操作")

uploaded_files = st.file_uploader(
"选择多个文件",
accept_multiple_files=True,
key="batch_rename"
)

if uploaded_files:
st.write(f"已选择 {len(uploaded_files)} 个文件")

col1, col2 = st.columns(2)
with col1:
prefix = st.text_input("文件名前缀", value="new_")
with col2:
start_num = st.number_input("起始编号", value=1, min_value=1)

if st.button("执行重命名", key="do_rename"):
# 创建重命名后的文件列表
renamed_files = []
for i, file in enumerate(uploaded_files):
file_extension = Path(file.name).suffix
new_name = f"{prefix}{start_num + i}{file_extension}"
renamed_files.append((new_name, file.getvalue()))

# 显示重命名结果
st.subheader("重命名结果")
for new_name, file_data in renamed_files:
st.download_button(
label=f"下载 {new_name}",
data=file_data,
file_name=new_name,
key=f"download_{new_name}"
)

st.success("重命名完成!")

with tab2:
st.subheader("智能文件整理")
st.markdown("按文件类型自动分类整理")

uploaded_files = st.file_uploader(
"选择多个文件进行整理",
accept_multiple_files=True,
key="file_organize"
)

if uploaded_files:
# 分析文件类型
file_categories = {
"图片": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"],
"文档": [".pdf", ".docx", ".xlsx", ".pptx", ".txt", ".md"],
"压缩包": [".zip", ".rar", ".7z", ".tar", ".gz"],
"其他": []
}

categorized_files = {category: [] for category in file_categories}

for file in uploaded_files:
file_extension = Path(file.name).suffix.lower()
categorized = False

for category, extensions in file_categories.items():
if file_extension in extensions:
categorized_files[category].append(file)
categorized = True
break

if not categorized:
categorized_files["其他"].append(file)

# 显示分类结果
st.subheader("文件分类结果")
for category, files in categorized_files.items():
if files:
with st.expander(f"{category} ({len(files)}个文件)"):
for file in files:
st.download_button(
label=f"下载 {file.name}",
data=file.getvalue(),
file_name=file.name,
key=f"org_{category}_{file.name}"
)

def show_image_tools():
"""图片处理工具页面"""
st.header("🖼️ 图片处理工具")

st.subheader("图片添加水印")

col1, col2 = st.columns(2)

with col1:
image_file = st.file_uploader("上传图片", type=["jpg", "jpeg", "png", "bmp"], key="watermark_image")
watermark_text = st.text_input("水印文字", value="程序员晚枫")

with col2:
if image_file is not None:
st.image(image_file, caption="原始图片", use_column_width=True)

if st.button("添加水印", key="add_watermark"):
with st.spinner("添加水印中..."):
try:
# 保存上传的图片
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp_file:
tmp_file.write(image_file.getvalue())
input_path = tmp_file.name

# 添加水印
output_filename = f"watermarked_{image_file.name}"
output_path = os.path.join(tempfile.gettempdir(), output_filename)

# 使用python-office添加水印
office.image.add_watermark(
file=input_path,
mark_text=watermark_text,
output_path=output_path
)

# 显示结果
st.subheader("添加水印后的图片")
st.image(output_path, use_column_width=True)

# 提供下载
with open(output_path, "rb") as file:
btn = st.download_button(
label="下载带水印图片",
data=file,
file_name=output_filename
)

st.success("水印添加成功!")

# 清理临时文件
os.unlink(input_path)
os.unlink(output_path)

except Exception as e:
st.error(f"添加水印失败: {str(e)}")

def show_about():
"""关于页面"""
st.header("👨‍💻 关于我们")

col1, col2 = st.columns([1, 2])

with col1:
st.image("https://via.placeholder.com/200x200/007ACC/FFFFFF?text=晚枫", width=200)

with col2:
st.markdown("""
## 程序员晚枫

`python-office`开源项目创始人,专注于AI办公自动化领域。

**技术栈:**
- Python办公自动化
- AI应用开发
- 开源项目维护
- 技术教育

**联系方式:**
- GitHub: [github.com/程序员晚枫](https://github.com/程序员晚枫)
- 邮箱: contact@python-office.com
""")

st.markdown("---")

st.subheader("🎯 项目愿景")
st.markdown("""
我们的目标是打造**最实用的AI办公工具集合**,让每个人都能享受到技术带来的便利。

**核心价值:**
- 🚀 **高效**:一键解决复杂办公需求
- 🎯 **实用**:聚焦真实办公场景痛点
- 🔓 **开源**:代码透明,持续改进
- 🌐 **易用**:零配置,开箱即用
""")

st.subheader("🛠️ 技术架构")
st.markdown("""
本网站基于以下技术栈构建:

- **前端框架**: Streamlit
- **核心库**: python-office, popdf
- **部署平台**: Streamlit Cloud
- **文件处理**: 临时文件系统
""")

if __name__ == "__main__":
main()

运行你的网站

在命令行中运行:

1
streamlit run ai_tools_website.py

你的网站将在 http://localhost:8501 启动!

高级功能:添加用户系统和数据分析

让我们为网站添加更多专业功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 在main函数前添加这些功能
import datetime
import json
from collections import defaultdict

# 用户使用统计
usage_stats = defaultdict(int)

def track_usage(tool_name):
"""跟踪工具使用情况"""
usage_stats[tool_name] += 1
# 这里可以添加数据库存储逻辑

def get_download_link(file_path, filename):
"""生成下载链接"""
with open(file_path, "rb") as file:
data = file.read()
b64 = base64.b64encode(data).decode()
return f'<a href="data:application/octet-stream;base64,{b64}" download="{filename}">下载 {filename}</a>'

# 在转换函数中添加使用统计
def enhanced_pdf_to_word(uploaded_file, output_filename):
track_usage("pdf_to_word")
# ... 原有的转换逻辑

部署到云端

部署到Streamlit Cloud(免费)

  1. 将代码推送到GitHub
  2. 访问 share.streamlit.io
  3. 连接GitHub仓库
  4. 一键部署!

部署配置文件

创建 .streamlit/config.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
[server]
port = 8501
address = "0.0.0.0"

[browser]
gatherUsageStats = false

[theme]
primaryColor = "#1f77b4"
backgroundColor = "#ffffff"
secondaryBackgroundColor = "#f0f2f6"
textColor = "#262730"
font = "sans serif"

性能优化技巧

1. 文件处理优化

1
2
3
4
5
def process_large_file(uploaded_file):
"""处理大文件的优化方案"""
# 使用分块处理
chunk_size = 1024 * 1024 # 1MB
# ... 处理逻辑

2. 缓存优化

1
2
3
4
@st.cache_data
def load_common_resources():
"""缓存常用资源"""
return {"config": load_config(), "templates": load_templates()}

3. 异步处理

1
2
3
4
5
import asyncio

async def async_file_conversion(input_path, output_path):
"""异步文件转换"""
# ... 异步处理逻辑

实战作业:打造你的专属工具网站

任务: 基于上面的模板,创建你自己的AI工具网站

要求:

  1. 集成至少5个不同的工具功能
  2. 添加使用统计和用户反馈
  3. 设计美观的用户界面
  4. 部署到云端并分享链接

进阶挑战:

  • 添加用户注册登录系统
  • 实现文件历史记录功能
  • 添加工具使用教程
  • 集成支付系统(高级功能)

程序员晚枫的Web开发心得

在开发python-office在线工具的过程中,我总结了几个重要经验:

  1. 用户体验优先:每个工具都要有清晰的使用说明
  2. 错误处理完善:给用户友好的错误提示
  3. 性能考虑:大文件处理要有进度提示
  4. 移动端适配:确保在手机上的使用体验

记住:好的Web应用不是功能的堆砌,而是用户体验的艺术。

下一讲预告

在第7讲中,我们将学习如何将你的网站部署到云服务器,让全世界的人都能访问你的AI工具大全!


本节课的收获:

  • 掌握了Streamlit框架的核心概念
  • 学会了创建功能完整的Web应用
  • 了解了Web部署的基本流程
  • 能够将python-office功能转化为在线服务

课后任务:

  1. 基于模板创建你的工具网站
  2. 添加至少2个自定义功能
  3. 部署到Streamlit Cloud并分享链接
  4. 收集用户反馈并进行优化

我是程序员晚枫,我们下一讲见!


本文涉及的Streamlit开发模式已在python-office官方网站中验证,具有极高的实用性和稳定性。遇到Streamlit相关问题,欢迎在课程群中交流。

关于课程

我会尽我所能,把AI编程的知识分享给你。

因为对于我来说,给小白的《30讲 · AI编程训练营》是我能力范围内,最有机会抓住AI趋势的一套课。

前3讲可以试听,试听链接:https://www.bilibili.com/cheese/play/ss982042944

所以这套课的内容,只会比我承诺的更多,不会更少;只会比你预期的更用心,不会割韭菜。

同时,我也欢迎大家找我沟通,我会尽力解答你的问题。

联系作者

程序员晚枫专注AI编程培训,小白看完他和图灵社区合作的教程《30讲 · AI编程训练营》就能上手做AI项目。