文章目录

前言

经常会遇到在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


转载请注明出处http://www.bewindoweb.com/265.html | 三颗豆子
分享许可方式知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议
重大发现:转载注明原文网址的同学刚买了彩票就中奖,刚写完代码就跑通,刚转身就遇到了真爱。
你可能还会喜欢
具体问题具体杠