之前开发中遇到数据迁移的需求,但是华为OBS对象存储不支持批到导出某个通的文件,于是自己用python写了这个工具,通过GUI界面简化了OBS文件下载流程,特别适合需要批量操作的技术人员使用。
一、工具核心功能亮点
- 可视化操作界面:使用Tkinter构建的直观GUI
- 多线程下载:后台线程处理避免界面卡顿
- 实时进度监控:下载速度、当前文件、进度条三重反馈
- 智能路径处理:自动根据华为云通路径创建本地目录结构
- 安全凭证管理:Secret Key以掩码形式显示
三、使用到的关键库
库名称 | 作用 | 版本要求 |
---|---|---|
tkinter | GUI界面构建 | Python标准库 |
obs | 华为云OBS官方SDK | pip install obs |
requests | HTTP请求处理 | pip install requests |
pathlib | 路径操作 | Python标准库 |
四、使用示例
- 填写AK/SK凭证
- 设置区域端点(默认西南区)
- 输入桶名称
- 选择桶内文件夹路径
- 保存到本地目录路径
- 点击"获取并下载"启动任务
总结
通过Python的Tkinter与华为OBS SDK结合,实现了:
- ✅ 图形化批量下载操作
- ✅ 实时进度反馈
- ✅ 智能路径处理
- ✅ 安全凭证管理
欢迎在评论区交流使用体验和改进建议!对于需要处理华为云OBS文件的技术人员,这个工具能极大提升工作效率。
附上完整源码,可自行编译运行。
如需直接获取可执行文件,欢迎私信联系获取.exe安装包。
六、完整源码
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import requests
import threading
import time
from pathlib import Path
from obs import ObsClient, Object
class ObsAutoDownloader:
def __init__(self, master):
self.master = master
master.title("华为云OBS自动下载工具")
master.geometry("900x700")
# 初始化状态
self.is_downloading = False
self.obs_client = None
self.file_list = []
self.current_file = ""
# 创建UI组件
self.create_widgets()
def create_widgets(self):
"""界面布局"""
main_frame = ttk.Frame(self.master, padding=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 配置区域
config_frame = ttk.LabelFrame(main_frame, text="OBS配置")
config_frame.pack(fill=tk.X, pady=5)
# 凭证输入
ttk.Label(config_frame, text="Access Key:").grid(row=0, column=0, sticky=tk.W)
self.ak_entry = ttk.Entry(config_frame)
self.ak_entry.grid(row=0, column=1, padx=5, pady=2)
ttk.Label(config_frame, text="Secret Key:").grid(row=0, column=2, sticky=tk.W)
self.sk_entry = ttk.Entry(config_frame, show="*")
self.sk_entry.grid(row=0, column=3, padx=5, pady=2)
# 区域和桶配置
ttk.Label(config_frame, text="区域端点:").grid(row=1, column=0, sticky=tk.W)
self.endpoint_entry = ttk.Entry(config_frame)
self.endpoint_entry.grid(row=1, column=1, padx=5, pady=2)
self.endpoint_entry.insert(0, "obs.cn-southwest-2.myhuaweicloud.com")
ttk.Label(config_frame, text="桶名称:").grid(row=1, column=2, sticky=tk.W)
self.bucket_entry = ttk.Entry(config_frame)
self.bucket_entry.grid(row=1, column=3, padx=5, pady=2)
ttk.Label(config_frame, text="文件夹路径:").grid(row=2, column=0, sticky=tk.W)
self.prefix_entry = ttk.Entry(config_frame)
self.prefix_entry.grid(row=2, column=1, columnspan=3, sticky=tk.EW, padx=5, pady=2)
self.prefix_entry.insert(0, "")
# 保存路径
ttk.Label(config_frame, text="保存目录:").grid(row=3, column=0, sticky=tk.W)
self.save_path = tk.StringVar()
ttk.Entry(config_frame, textvariable=self.save_path, width=40).grid(row=3, column=1, padx=5)
ttk.Button(config_frame, text="浏览", command=self.choose_directory).grid(row=3, column=2)
# 进度区域
progress_frame = ttk.LabelFrame(main_frame, text="进度")
progress_frame.pack(fill=tk.X, pady=5)
self.progress_bar = ttk.Progressbar(progress_frame, mode='determinate')
self.progress_bar.pack(fill=tk.X, padx=10, pady=5)
info_frame = ttk.Frame(progress_frame)
info_frame.pack(fill=tk.X, padx=10)
ttk.Label(info_frame, text="当前文件:").pack(side=tk.LEFT)
self.current_file_label = ttk.Label(info_frame, text="等待开始...")
self.current_file_label.pack(side=tk.LEFT, padx=10)
ttk.Label(info_frame, text="速度:").pack(side=tk.LEFT)
self.speed_label = ttk.Label(info_frame, text="0.00 KB/s")
self.speed_label.pack(side=tk.LEFT, padx=10)
# 控制按钮
btn_frame = ttk.Frame(main_frame)
btn_frame.pack(pady=5)
self.start_btn = ttk.Button(btn_frame, text="获取并下载", command=self.start_process)
self.start_btn.pack(side=tk.LEFT, padx=5)
self.stop_btn = ttk.Button(btn_frame, text="停止", state=tk.DISABLED, command=self.stop_process)
self.stop_btn.pack(side=tk.LEFT, padx=5)
# 日志区域
log_frame = ttk.LabelFrame(main_frame, text="操作日志")
log_frame.pack(fill=tk.BOTH, expand=True)
self.log_text = scrolledtext.ScrolledText(log_frame, height=12)
self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
def choose_directory(self):
path = filedialog.askdirectory()
if path:
self.save_path.set(path)
def log(self, message):
self.log_text.insert(tk.END, f"[{time.strftime('%H:%M:%S')}] {message}\n")
self.log_text.see(tk.END)
def validate_config(self):
"""验证配置有效性"""
required_fields = {
"AK": self.ak_entry.get().strip(),
"SK": self.sk_entry.get().strip(),
"Endpoint": self.endpoint_entry.get().strip(),
"Bucket": self.bucket_entry.get().strip(),
"SavePath": self.save_path.get()
}
for field, value in required_fields.items():
if not value:
messagebox.showerror("错误", f"{field} 不能为空")
return False
if not os.path.exists(required_fields["SavePath"]):
try:
os.makedirs(required_fields["SavePath"])
except:
messagebox.showerror("错误", "无法创建保存目录")
return False
return True
def start_process(self):
"""启动整个流程"""
if not self.validate_config():
return
self.start_btn.config(state=tk.DISABLED)
self.stop_btn.config(state=tk.NORMAL)
self.is_downloading = True
threading.Thread(target=self.full_process, daemon=True).start()
def full_process(self):
"""完整处理流程"""
try:
# 初始化OBS客户端
self.obs_client = ObsClient(
access_key_id=self.ak_entry.get().strip(),
secret_access_key=self.sk_entry.get().strip(),
server=self.endpoint_entry.get().strip()
)
# 获取文件列表
self.log("正在获取文件列表...")
self.file_list = self.list_obs_files()
if not self.file_list:
self.log("目标文件夹为空")
return
self.log(f"找到 {len(self.file_list)} 个文件")
# 开始下载
self.download_all_files()
except Exception as e:
self.log(f"操作失败: {str(e)}")
finally:
self.is_downloading = False
self.master.after(0, lambda: self.stop_btn.config(state=tk.DISABLED))
self.master.after(0, lambda: self.start_btn.config(state=tk.NORMAL))
def list_obs_files(self):
"""获取OBS文件列表"""
bucket = self.bucket_entry.get().strip()
prefix = self.prefix_entry.get().strip().rstrip('/') + '/'
file_list = []
marker = None
try:
while True:
resp = self.obs_client.listObjects(bucket, prefix=prefix, marker=marker)
if resp.status < 300:
# 过滤文件夹标记对象
valid_files = [
obj.key for obj in resp.body.contents
if not (obj.key.endswith('/') and obj.size == 0)
]
file_list.extend(valid_files)
if resp.body.is_truncated:
marker = resp.body.next_marker
else:
break
else:
raise Exception(f"列表获取失败: {resp.errorCode}")
return file_list
except Exception as e:
self.log(f"获取文件列表失败: {str(e)}")
return []
def download_all_files(self):
"""下载所有文件"""
total = len(self.file_list)
success_count = 0
start_time = time.time()
for idx, file_key in enumerate(self.file_list, 1):
if not self.is_downloading:
break
self.current_file = file_key
self.master.after(0, lambda: self.current_file_label.config(
text=f"{idx}/{total} {os.path.basename(file_key)}"))
try:
# 下载文件
local_path = os.path.join(self.save_path.get(), file_key)
os.makedirs(os.path.dirname(local_path), exist_ok=True)
resp = self.obs_client.getObject(
self.bucket_entry.get().strip(),
file_key,
downloadPath=local_path
)
if resp.status < 300:
success_count += 1
self.log(f"下载成功: {file_key}")
else:
self.log(f"下载失败[{resp.status}]: {file_key}")
# 更新进度
progress = (idx / total) * 100
self.master.after(0, lambda: self.progress_bar.configure(value=progress))
# 计算速度
time_used = time.time() - start_time
speed = (idx * 1024) / time_used # 假设平均每个文件1KB
self.master.after(0, lambda: self.speed_label.config(text=f"{speed:.2f} 文件/秒"))
except Exception as e:
self.log(f"下载异常 {file_key}: {str(e)}")
self.log(f"下载完成!成功 {success_count}/{total} 个文件")
def stop_process(self):
"""停止处理"""
self.is_downloading = False
self.log("操作已停止")
if self.obs_client:
self.obs_client.close()
if __name__ == "__main__":
root = tk.Tk()
app = ObsAutoDownloader(root)
root.mainloop()