关键词:Python桌面应用开发、Pywebview教程、Python GUI、Web技术桌面化、跨平台应用开发
还在为Python的桌面应用开发而头疼吗?Tkinter太丑,PyQt太复杂?今天给大家介绍一个宝藏库——Pywebview,让你用熟悉的HTML、CSS、JavaScript就能做出专业级的桌面应用!
🚀 什么是Pywebview?为什么这么香?
说白了,Pywebview就是把网页装进一个独立的桌面窗口里,然后让Python和网页可以互相"聊天"。
想象一下这个场景:
你已经会写网页了(HTML、CSS、JS)
你也会Python编程
但是做桌面应用总是卡壳
Pywebview就是来拯救你的!它让你可以:
✅ 用Web技术做界面(再也不用跟Tkinter的丑UI斗争)
✅ 用Python处理业务逻辑(发挥Python的强大生态)
✅ 一套代码跨三个平台(Windows、Mac、Linux)
✅ 打包后就是原生应用(用户根本看不出是网页)
📦 快速上手:第一个Hello World
安装Pywebview
# 基础安装
pip install pywebview
# 如果你想要更好的性能,可以选择安装额外组件
# Windows用户推荐:
pip install pywebview[cef]
# Linux用户可能需要:
pip install pywebview[gtk]
最简单的示例:打开一个网页
# hello_world.pyimport webview
def main(): # 创建一个窗口,显示百度首页 webview.create_window('我的第一个应用', 'https://www.baidu.com') webview.start()
if __name__ == '__main__': main()
运行这段代码,你就会看到一个独立的窗口打开了百度首页。是不是很简单?
🎨 进阶玩法:用本地HTML做界面
创建本地HTML文件
先创建一个简单的HTML文件:
html><html><head> <meta charset="UTF-8"> <title>计算器应用title> <style> body { font-family: 'Microsoft YaHei', Arial, sans-serif; margin: 0; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; display: flex; justify-content: center; align-items: center; min-height: 100vh; } .calculator { background: rgba(255, 255, 255, 0.1); padding: 30px; border-radius: 15px; box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3); text-align: center; backdrop-filter: blur(10px); } input { width: 200px; padding: 15px; margin: 10px; border: none; border-radius: 8px; font-size: 16px;
text-align: center; } button { background: #ff6b6b; color: white; border: none; padding: 15px 30px; margin: 10px; border-radius: 8px; cursor: pointer; font-size: 16px; transition: background 0.3s; } button:hover { background: #ff5252; } #result { font-size: 24px; margin: 20px 0; padding: 15px; background: rgba(255, 255, 255, 0.2); border-radius: 8px; } style>head><body> <div class="calculator"> <h1>🧮 超级计算器h1> <input type="number" id="num1" placeholder="输入第一个数字"> <input type="number" id="num2" placeholder="输入第二个数字"> <br> <button onclick="calculate('add')">➕ 加法button> <button onclick="calculate('subtract')">➖ 减法button> <button onclick="calculate('multiply')">✖️ 乘法button> <button onclick="calculate('divide')">➗ 除法button> <div id="result">
结果将在这里显示div> div>
<script> function calculate(operation) { const num1 = parseFloat(document.getElementById('num1').value); const num2 = parseFloat(document.getElementById('num2').value);
if (isNaN(num1) || isNaN(num2)) { document.getElementById('result').innerText = '请输入有效的数字!'; return; }
pywebview.api.calculate(num1, num2, operation).then(result => { document.getElementById('result').innerText = `结果:${result}`; }); } script>body>html>
Python后端逻辑
import webviewimport os
class Api: def calculate(self, num1, num2, operation): """处理计算逻辑""" try: if operation == 'add': result = num1 + num2 elif operation == 'subtract': result = num1 - num2 elif operation == 'multiply': result = num1 * num2 elif operation == 'divide': if num2 == 0: return "错误:除数不能为0" result = num1 / num2 else: return "未知操作"
return round(result, 2) except Exception as e: return f"计算错误:{str(e)}"
def main(): api = Api()
html_path = os.path.abspath('index.html')
webview.create_window( title='超级计算器', url=html_path, js_api=api, width=500, height=600, resizable=False )
webview.start(debug=True)
if __name__ == '__main__': main()
运行这个程序,你就有了一个带炫酷界面的计算器应用!
💡 实战项目:文件管理器
让我们做一个更有用的应用——简单的文件管理器。
HTML界面(file_manager.html)
html><html><head> <meta charset="UTF-8"> <title>文件管理器title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f5f5f5; } .header { background: #2c3e50; color: white; padding: 15px; display: flex; align-items: center; gap: 10px; } .current-path { background: #34495e; padding: 8px 12px; border-radius: 4px; font-family: monospace; } .toolbar { background: white; padding: 10px; border-bottom: 1px solid #ddd; display: flex; gap: 10px; } button { background: #3498db; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } button:hover { background: #2980b9; } .file-list { padding: 20px; display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 15px; } .file-item {
background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); cursor: pointer; transition: all 0.3s; } .file-item:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.2); } .file-icon { font-size: 40px; text-align: center; margin-bottom: 10px; } .file-name { text-align: center; font-weight: bold; word-break: break-all; } style>head><body> <div class="header"> <h1>📁 文件管理器h1> <div class="current-path" id="currentPath">加载中...div> div>
<div class="toolbar"> <button onclick="goBack()">⬅️ 返回上级button> <button onclick="refreshFiles()">🔄 刷新button> <button onclick="openFileDialog()">📂 选择文件夹button> div>
<div class="file-list" id="fileList"> 正在加载文件列表... div>
<script> let currentPath = '';
window.addEventListener('DOMContentLoaded', function() { refreshFiles(); });
function refreshFiles() { pywebview.api.get_current_directory().then(path => { currentPath = path; document.getElementById('currentPath').textContent = path; loadFiles(path); }); }
function loadFiles(path) { pywebview.api.list_files(path).then(files => { displayFiles(files); }); }
function displayFiles(files) { const fileList = document.getElementById('fileList'); fileList.innerHTML = '';
files.forEach(file => { const fileItem = document.createElement('div'); fileItem.className = 'file-item'; fileItem.onclick = () => openItem(file);
const icon = file.is_directory ? '📁' : getFileIcon(file.name);
fileItem.innerHTML = ` ${icon}
${file.name}
`;
fileList.appendChild(fileItem); }); }
function getFileIcon(filename) { const ext = filename.split('.').pop().toLowerCase(); const iconMap = { 'txt': '📄', 'doc': '📄', 'docx': '📄', 'pdf': '📕', 'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️', 'mp3': '🎵', 'mp4': '🎬', 'zip': '📦', 'rar': '📦', 'py': '🐍', 'js': '💛', 'html': '🌐' }; return iconMap[ext] || '📄'; }
function openItem(file) { if (file.is_directory) { loadFiles(file.path); currentPath = file.path; document.getElementById('currentPath').textContent = file.path; } else { pywebview.api.open_file(file.path); } }
function goBack() { pywebview.api.get_parent_directory(currentPath).then(
parentPath => { if (parentPath) { loadFiles(parentPath); currentPath = parentPath; document.getElementById('currentPath').textContent = parentPath; } }); }
function openFileDialog() { pywebview.api.choose_directory().then(path => { if (path) { loadFiles(path); currentPath = path; document.getElementById('currentPath').textContent = path; } }); } script>body>html>
Python后端(file_manager.py)
import webviewimport osimport subprocessimport platformfrom pathlib import Path
class FileManagerApi: def get_current_directory(self): """获取当前工作目录""" return str(Path.cwd())
def list_files(self, path): """列出指定目录下的文件和文件夹""" try: items = [] path_obj = Path(path)
for item in path_obj.iterdir(): items.append({ 'name': item.name, 'path': str(item.absolute()), 'is_directory': item.is_dir() })
items.sort(key=lambda x: (not x['is_directory'], x['name'].lower())) return items
except Exception as e: return [{'name': f'错误:{str(e)}', 'path': '', 'is_directory': False}]
def get_parent_directory(self, path): """获取父目录""" try: parent = Path(path).parent if parent != Path(path): return str(parent) return None except: return None
def open_file(self, file_path): """用系统默认程序打开文件""" try: system = platform.system() if system == 'Windows': os.startfile(file_path) elif system == 'Darwin': subprocess.run(['open'
, file_path]) else: subprocess.run(['xdg-open', file_path]) return True except Exception as e: print(f"打开文件失败:{str(e)}") return False
def choose_directory(self): """打开文件夹选择对话框""" try: result = webview.windows[0].create_file_dialog( webview.FOLDER_DIALOG ) if result: return result[0] return None except: return None
def main(): api = FileManagerApi()
webview.create_window( title='文件管理器 v1.0', url='file_manager.html', js_api=api, width=900, height=700, min_size=(600, 400) )
webview.start(debug=True)
if __name__ == '__main__': main()
🔥 高级技巧:与前端框架结合
如果你熟悉Vue.js或React,你完全可以用它们来构建更复杂的界面!
简单的Vue.js集成示例
html><html><head> <meta charset="UTF-8"> <title>Vue + Pywebviewtitle> <script src="https://unpkg.com/vue@3/dist/vue.global.js">script> <style> body { font-family: Arial, sans-serif; padding: 20px; } .todo-item { padding: 10px; margin: 5px 0; background: #f0f0f0; border-radius: 5px; } .completed { text-decoration: line-through; opacity: 0.6; } input, button { padding: 10px; margin: 5px; border: 1px solid #ddd; border-radius: 4px; }
button { background: #007bff; color: white; cursor: pointer; } style>head><body> <div id="app"> <h1>📝 我的待办清单h1>
<div> <input v-model="newTodo" @keyup.enter="addTodo" placeholder="输入新的待办事项"> <button @click="addTodo">添加button> div>
<div v-for="(todo, index) in todos" :key="index" class="todo-item" :class="{completed: todo.completed}"> <input type="checkbox" v-model="todo.completed" @change="updateTodo(index)"> {{ todo.text }} <button @click="deleteTodo(index)" style="float: right; background: #dc3545;">删除button> div>
<p>总计:{{ todos.length }} 项,已完成:{{ completedCount }} 项p> div>
<script> const { createApp } = Vue;
createApp({ data() { return { newTodo: '', todos: [] } }, computed: { completedCount() { return this.todos.filter(todo => todo.completed
).length; } }, methods: { async addTodo() { if (this.newTodo.trim()) { const todo = { text: this.newTodo, completed: false }; this.todos.push(todo); await pywebview.api.save_todos(this.todos); this.newTodo = ''; } }, async deleteTodo(index) { this.todos.splice(index, 1); await pywebview.api.save_todos(this.todos); }, async updateTodo(index) { await pywebview.api.save_todos(this.todos); }, async loadTodos() { this.todos = await pywebview.api.load_todos(); } }, async mounted() { await this.loadTodos(); } }).mount('#app'); script>body>html>
对应的Python代码
import webviewimport jsonimport os
class TodoApi: def __init__(self): self.data_file = 'todos.json'
def load_todos(self): """从文件加载待办事项""" if os.path.exists(self.data_file): try: with open(self.data_file, 'r', encoding='utf-8') as f: return json.load(f) except: return [] return []
def save_todos(self, todos): """保存待办事项到文件""" try: with open(self.data_file, 'w', encoding='utf-8') as f: json.dump(todos, f, ensure_ascii=False, indent=2) return True except Exception as e: print(f"保存失败:{str(e)}") return False
def main(): api = TodoApi()
webview.create_window( title='Vue + Python 待办清单', url='vue_app.html', js_api=api, width=600, height=500 )
webview.start(debug=True)
if __name__ == '__main__': main()
📱 打包发布:让你的应用变成真正的软件
开发完成后,你肯定想把应用打包成exe文件分享给别人。推荐使用PyInstaller:
# 安装PyInstaller
pip install pyinstaller
# 打包成单个exe文件
pyinstaller --onefile--windowed calculator_app.py
# 如果有额外的HTML文件,需要添加到打包中
pyinstaller --onefile--windowed--add-data"index.html;." calculator_app.py
🛠️ 常见问题与解决方案
1. 中文显示乱码
# 在HTML的head中添加
<metacharset="UTF-8">
# Python文件保存为UTF-8编码
2. JavaScript调用Python函数失败
# 确保在create_window时传入了js_api参数
webview.create_window(
title='我的应用',
url='index.html',
js_api=api_instance # 这个很重要!
)
3. 文件路径问题
import os
# 获取当前脚本所在目录current_dir = os.path.dirname(os.path.abspath(__file__))html_path = os.path.join(current_dir, 'index.html')
webview.create_window('应用', html_path)
🎯 为什么选择Pywebview?
经过这么多实例,相信你已经感受到了Pywebview的魅力:
学习成本低:会写网页就能做桌面应用
开发效率高:不用学复杂的GUI框架
界面现代化:告别Tkinter的上世纪风格
跨平台无压力:一套代码到处跑
生态丰富:可以用任何前端框架和库
适合哪些场景?
数据可视化工具
个人效率工具
企业内部管理系统
简单的桌面小应用
原型快速开发
对Python,AI,自动化办公提效,副业发展等感兴趣的伙伴们,扫码添加逍遥,限免交流群
备注【成长交流】