前言
经常会遇到在windows上上传文件到AWS S3的需求,由于某些原因不方便直接使用AWS提供的HTTP界面去上传文件,只好用脚本上传。在windows上更多的是编译好了一个程序想放到AWS的机器跑一跑,用S3做文件中转。直接用脚本的话,每次都要输入对应的文件路径,比较麻烦,更想有个GUI界面选择文件后自动把路径填上去,AWS提供了boto3这个库,支持java、python等等,我们用python3来做这件事。
一、前期准备
(1)IDE:推荐使用PyCharm,方便的是当有库没有安装时会提示你Alt+Enter快速安装
(2)Python:3.6+
(3)python库安装:用pip3 install提前安装好所有的库
- aws库:boto3,官方文档和示例
- python打包成EXE库:pyinstaller
- pythonGUI库:tkinter(之前的版本叫Tkinter)
- 解析INI配置文件库:configparser(之前的版本叫ConfigParser)
二、预览最终效果
- 本地文件路径:要上传的本地文件路径,在”选择文件“后会自动添加
- S3文件名:上传到S3后的文件名,默认用原文件名
- aws_kid:AWS Access Key ID, IAM生成的密钥ID
- aws_sak:AWS Secret Access Key,IAM生成的密钥,和密钥ID是成对分配的
- region_name:区域,例如ap-south-1,详细的区域可以看区域列表中S3部署的区域
- 桶(bucket):你建立的S3桶的名称
- 键(key):这里指的是前缀,例如填入myfolder,S3文件名为xxxx.jpg,那么S3上存储的路径就会变为myfolder/xxxx.jpg,在S3的界面上直观看是一个文件夹叫myfolder,里面放了xxxx.jpg这个文件
点击保存配置后,可将配置保存到同目录下的config.ini文件中;
点击选择文件会GUI选择文件,选择完毕会将路径和文件名填入“本地文件路径”和“S3文件名”中;
点击上传文件到S3会将文件上传到S3,且会看到实时的上传进度条以及百分比数字,如果出错会提示出错。
三、分解步骤
1、熟悉boto3的上传操作
boto3的上传代码:
session = boto3.Session(
aws_access_key_id = xxxx,
aws_secret_access_key = xxxx,
region_name = xxxx)
s3 = session.resource('s3')
s3.meta.client.upload_file(srcPath, bucket, destPath)
其实就几行,建立一个boto3的Session对象,初始化为's3'服务,然后调用上传文件的函数,填入源文件名、桶的位置、目的文件名,就可以传上去了。
2、熟悉python tkinter
tkinter是python经典的GUI框架。
(1)布局
布局包括3种,pack、place、grid:
- pack:比较简单的相对布局,适合初学
- place:绝对布局
- grid:类似CSS分格子的布局,适合仔细研究和生产
开始我尝试了pack,发现不满足需求,组件必须放在Frame上,我想要分成多行,每行一个标签和一个文本框,就需要每行构建一个Frame,太麻烦,grid又需要仔细学,因此直接用了place绝对布局,规定在哪个像素显示就会在哪里显示,虽然不像grid一样适合后面扩展,但由于只是小工具不需要扩展,所以使用比较方便。
(2)常用组件和对应方法
把常用的例子列出来:
from tkinter import *
from tkinter.filedialog import askopenfilename
from tkinter import messagebox
# 1. 创建容器
window = Tk()
window.title("s3文件上传工具")
window.geometry('600x320') # 设置绝对宽高
window.wm_resizable(False,False) # 不允许调整宽高
# 2. Label标签
## place绝对布局,10px,10px,且x横向放置在0.07*600的位置,通过调整这个数字就可以调整位置
untitle_label = Label(window, text='本地文件路径').place(x=10, y=10, relx=0.07)
# 3. 可动态设置内容的Label标签
## 建立一个变量,并绑定到容器
var_upload_percentage = StringVar(window, "")
## Label绑定这个变量,标签会显示为这个变量的值
untitle_label = Label(window, textvariable=var_upload_percentage).place(x=140, y=300)
## 改变变量的值就能改变标签值
var_upload_percentage.set("")
# 4. Text文本框
text_filepath = Text(window, width=50, height=1)
## insert可以追加文本(不清空)
text_filepath.insert(INSERT, "请上传文件...")
text_filepath.place(x=140, y=15)
## get可以获取第0行从第0个字符到末尾的文本
## 末尾的:-1是去掉最后一个字符,因为获取的文本总会带换行符\n
filepath = text_filepath.get(0.0, END)[:-1]
## 清空和追加=重置
text_filepath.delete(0.0, END)
text_filepath.insert(INSERT, "xxxxxx")
# 5. 普通按钮
## 点击就会调用save_config函数
save_button = Button(window, text="保存配置", command=save_config)
save_button.place(x=140, y=225)
# 6. 可更改按钮内容的按钮(和前面一样的方法)
var_upload_botton = StringVar(window, "上传文件到s3")
upload_button = Button(window, textvariable=var_upload_botton, command=fileupload)
upload_button.place(x=300, y=225)
# 7.按钮的禁用、启用
upload_button.configure(state='disable')
upload_button.configure(state='active')
# 8.Canvas画布
## 创建一个画布
canvas_width = 350
canvas = Canvas(window, width=canvas_width, height=22, bg="white")
canvas.place(x=140, y=270)
## 绘制一个矩形,左上角1.5px 1.5px,宽度current/total*canvas_width,高度23,填充为绿色
## 绘制完之后一定要刷新容器,否则不会显示
canvas.create_rectangle(1.5, 1.5, current/total*canvas_width, 23, fill="green")
window.update()
## 清空画布内容
canvas.delete(ALL)
# 9. 提示框
messagebox.showerror("上传结果", "文件上传失败!请检查请求参数和网络连接")
messagebox.showinfo("上传结果", "文件上传成功!")
# 10. 最后一句话,就能显示这个GUI界面了
mainloop()
3、利用boto3的回调制作上传进度条
原理:boto3的upload_file有一个可选的参数CallBack,上传时会定时调用这个函数,并提供当前传送的数据量、总数据量、百分比等数据;利用前面的画布,根据百分比绘制对应宽度的矩形,由于被定时调用,看起来就像进度条在往前走一样。这里一定要注意使用多线程,否则GUI会卡死。
相关代码:
class ProgressPercentage(object):
def __init__(self, filename):
self._filename = filename
self._size = float(os.path.getsize(filename))
self._seen_so_far = 0
self._lock = threading.Lock()
def __call__(self, bytes_amount):
# To simplify, assume this is hooked up to a single filename
with self._lock:
self._seen_so_far += bytes_amount
percentage = (self._seen_so_far / self._size) * 100
sys.stdout.write(
"\r%s %s / %s (%.2f%%)" % (
self._filename, self._seen_so_far, self._size,
percentage))
sys.stdout.flush()
try:
progress(self._seen_so_far, self._size)
except:
print("出错")
def fileupload():
filepath = text_filepath.get(0.0, END)[:-1]
filename = text_filename.get(0.0, END)[:-1]
bucket = text_bucket.get(0.0, END)[:-1]
key = text_key.get(0.0, END)[:-1]
aws_access_key_id = text_aws_access_key_id.get(0.0, END)[:-1]
aws_secret_access_key = text_aws_secret_access_key.get(0.0, END)[:-1]
region_name = text_region_name.get(0.0, END)[:-1]
print("文件路径=" + filepath +
"\n新文件名=" + filename +
"\n桶=" + bucket +
"\n键=" + key +
"\nawskid=" + aws_access_key_id +
"\nawssak=" + aws_secret_access_key +
"\n区域=" + region_name + "\n")
t = threading.Thread(target=s3upload,
args=(filepath, aws_access_key_id, aws_secret_access_key, region_name, bucket, key, filename))
t.setDaemon(True)
t.start()
var_upload_botton.set("正在上传")
upload_button.configure(state='disable')
def clear_progress(canvas):
canvas.delete(ALL)
var_upload_percentage.set("")
var_upload_botton.set("上传文件到s3")
upload_button.configure(state='active')
def progress(current, total):
# 填充进度条
canvas.create_rectangle(1.5, 1.5, current/total*canvas_width, 23, fill="green")
window.update()
# 填写进度
var_upload_percentage.set(str(round(current/total*100, 2)) + "%")
if __name__ == '__main__':
var_upload_botton = StringVar(window, "上传文件到s3")
upload_button = Button(window, textvariable=var_upload_botton, command=fileupload)
upload_button.place(x=300, y=225)
untitle_label = Label(window, text='上传进度').place(x=10, y=270, relx=0.11)
canvas_width = 350
canvas = Canvas(window, width=canvas_width, height=22, bg="white")
canvas.place(x=140, y=270)
var_upload_percentage = StringVar(window, "")
untitle_label = Label(window, textvariable=var_upload_percentage).place(x=140, y=300)
4、增加配置文件
希望能够自己添加配置文件,因此本地保存一个INI就可以了,然后启动时读取,点击“保存配置”按钮时写入。
def save_config():
bucket = text_bucket.get(0.0, END)[:-1]
key = text_key.get(0.0, END)[:-1]
aws_access_key_id = text_aws_access_key_id.get(0.0, END)[:-1]
aws_secret_access_key = text_aws_secret_access_key.get(0.0, END)[:-1]
region_name = text_region_name.get(0.0, END)[:-1]
print(aws_access_key_id, aws_secret_access_key, region_name, bucket, key)
try:
conf.set("aws_account", "aws_kid", aws_access_key_id)
conf.set("aws_account", "aws_sak", aws_secret_access_key)
conf.set("bucket_info", "region_name", region_name)
conf.set("bucket_info", "bucket", bucket)
conf.set("bucket_info", "key", key)
conf.write(open(cfgpath, "w+")) #w是替换,r+是修改
except:
messagebox.showerror("配置保存结果", "保存失败!")
return False
else:
messagebox.showinfo("配置保存结果", "保存成功!")
if __name__ == '__main__':
# 读取配置
curpath = os.path.dirname(os.path.realpath(__file__))
cfgpath = os.path.join(curpath, "config.ini")
conf = configparser.ConfigParser()
conf.read(cfgpath, "utf-8")
aws_kid = conf.get("aws_account", "aws_kid")
aws_sak = conf.get("aws_account", "aws_sak")
region_name = conf.get("bucket_info", "region_name")
bucket = conf.get("bucket_info", "bucket")
key = conf.get("bucket_info", "key")
save_button = Button(window, text="保存配置", command=save_config)
save_button.place(x=140, y=225)
配置文件config.ini类似这样:
[aws_account]
aws_kid = A****
aws_sak = o******
[bucket_info]
region_name = ******
bucket = *****
key = *****
5、文件选择框
文件选择框调用一个函数即可,底层库帮我们做复杂的GUI操作。
def filefound():
filepath= askopenfilename()
if (filepath != ""):
(filename_prefix, filename) = os.path.split(filepath)
text_filepath.delete(0.0, END)
text_filepath.insert(END, filepath)
text_filename.delete(0.0, END)
text_filename.insert(INSERT, filename)
6、打包成EXE
利用pyinstaller命令:
pyinstaller -w main.py
-w:启动时不显示命令行窗口
-F:打包成一个独立的EXE文件。不推荐这样做,因为这样会在启动的时候慢慢释放那些依赖库,导致启动缓慢,启动一次要10秒不能忍受
-D(默认):打包成一个文件夹,含有依赖库,启动快。
还有其他参数,例如设置图标等,可以查pyinstaller的API。
打包下来有50M左右,还是很大,只是一个小程序。
四、总结
这个脚本的适用性不广,但是熟悉了很多python的库,而且也方便我平时使用。如果习惯命令行的,可以直接写个windows批处理程序;还可以扩展诸如选择文件夹上传、多文件上传、日志查看等,这里我不需要所以不继续做了。
完整代码和可执行文件:AwsS3WindowsUploder