【WeChatFriendTool】从0到1,从前端界面到后端接口,详解Python软件开发全过程。干货,看完小白也可以轻松写出一个软件!

在上一篇博客:【我写了个小软件,老板说要给我加薪】微信助手|个性化群发|好友头像照片墙|软件开发,笔者介绍了这款微信定制化群发新年祝福的软件,经过笔者和朋友这几天的试用,确实有些好用? 。

今年笔者会继续对此项目进行优化,主要包括:

  • 导出好友标签;
  • 用户可输入汉字,一键生成汉字形式的头像照片墙;
  • 代码优化,GUI与功能函数分离,封装成类,分别进行线程;
  • 部署web版本;

这是笔者第二次自己写软件,第一次是帮客户做可视化大屏时,客户不会使用Linux和Mysql,故帮他们写了一个很简陋的数据库增删改查软件(如下图所示)。

这次无论从功能模块的实现,还是GUI界面的设计都有了一些进步,今天主要复盘总结一下:如何用python从0到1实现一个exe软件。

软件原型设计

简单梳理了需求后,先画出一个初步的草图:

在有原型之后,可以参考一下现有的消费端桌面软件或者上站酷之类的设计网站看看专业 UI 设计师作品的样式和色彩搭配。这里笔者在有了设计思路之后,接着使用Figma(真·新一代设计神器)制作UI界面。第一版设计使用的是这两年很火的阴影悬浮风格,如下图所示:

但由于笔者此次使用的是python的Tkinter包来实现软件的界面(GUI),这些阴影样式有些难以实现(还是css好用…),设计稿出来后笔者初步尝试将其转化为Tkinter代码,但效果很差,如下图所示:

阴影悬浮要完全用Tkinter包实现还需要慢慢调整,时间成本有些高。本次软件的时间比较紧急,于是笔者加急设计了第二版软件界面,使用的是微信平面风,也就是本项目的最后设计稿:

图形界面(GUI)编程

Python GUI介绍

什么是图形界面(GUI)?

GUI 是 Graphical User Interface 的简称,即图形用户接口,通常人机交互图形化用户界面设计经常读做“goo-ee”,准确来说 GUI 就是屏幕产品的视觉体验和互动操作部分。GUI 是一种结合计算机科学、美学、心理学、行为学,及各商业领域需求分析的人机系统工程,强调人—机—环境三者作为一个系统进行总体设计。

作为 Python 开发者,图形用户界面(GUI)开发是必备技能之一。目前Python 的图形界面(GUI)编程库主要包括:

  • PyQt:https://www.qt.io/
  • wxPython:https://www.wxpython.org/
  • Tkinter:https://wiki.python.org/moin/TkInter/
  • PySide:https://pypi.org/project/PySide/1.2.4/

Python 的“GUI 工具包”很多,各有特点,虽然大多数工具包的基础类似,但要学习一个新包并掌握其细节还是非常耗时的,因此,在选用工具包时应仔细权衡。这里笔者选择Python 自带的 GUI 工具包 TKinter进行图形界面开发。

之所以选择 Tkinter,一是最为简单,二是自带库,不需下载安装,随时使用,跨平台兼容性非常好,三则是从需求出发的,Python 在实际应用中极少用于开发复杂的桌面应用,只是用于封装较为简单的软件。(这也是为什么上述设计稿V1的阴影部分用Tkinter实现比较麻烦,笔者之后会写系列博客说明如何通过Flutter或者CSS写软件或者网站界面)

对于TKinter,除了窗口创建与布局,还需要掌握控件。Tkinter 提供了各种控件,如按钮、标签和文本框。在一个 GUI 应用程序中使用,这些控件通常被称为控件或者部件,目前有15种Tkinter 控件,如下列表:

接下来笔者将基于本软件实现来介绍如何通过 Tkinter 和 Tkinter-Designer 进行GUI编程。

将Figma文件变为Tkinter代码

笔者使用的是Figma进行原型设计, 如今,Figma已经成为主流的原型和数字设计软件之一而存在,Figma插件库也变得越来越丰富。笔者在GitHub上也发现了一个利用Tkinter和Figma进行界面设计的项目:Tkinter-Designer,无需一行代码,即可设计精美的界面。

笔者就是借助Tkinter Designer提升了很大的效率,该项目还贴心的为开发者配备了中文版本的说明文档,可以帮助我们快速的理解其使用方法。但作为一款低代码工具,仍然有很多限制和依赖,使用过程中也踩了一些坑,接下来笔者将介绍使用步骤和避坑指南。

  1. 安装Tkinter Designer

    首先,将Tkinter Designer的项目下载到本地,然后在Tkinter-Designer文件夹下安装必需的第三方库,这就实现了Tkinter Designer的环境准备。

    # mac进入conda环境
    source ~/.bash_profile
    conda activate py36
    # 克隆项目到本地
    git clone https://github.com/ParthJadhav/Tkinter-Designer.git
    # 进入Tkinter Designer路径
    cd ./downloads/Tkinter-Designer
    # 安装必需的第三方库
    pip3 install -r requirements.txt
    

    接着进入gui的文件路径,并运行项目

    # 进入gui的文件路径(后面打开自己bulit后的文件也是这个思路)
    cd /Users/fubo/Downloads/Tkinter-Designer/gui
    # 运行文件
    python3 gui.py
    

    运行成功后,将可以看到软件的使用界面

  2. Figma设计

    接下来就是利用Figma,在线设计一个界面。需要注意的是,为了能够利用Tkinter Designer将Figma界面转换为python程序,由于源代码的要求,在对Figma设计稿的元素命名时,必须严格按照命名规则进行命名(参考官方中文指南)。比如,利用文本内容展示框命名为Text,按钮命名为Button等。

    需要注意的是,如果软件上的矩形是输入框,需要设置成textBox或textArea的命名格式。同时,按钮都是转换成图片形式进行展示,图形阴影或圆角都无法直接转换出来。

    对于Figma的界面搭建,Tkinter Designer也提供了完整的使用教程,搭配视频的讲解非常容易理解其使用方法。

  3. 获取Figma 文件 URL 和 API 令牌

    当界面制作完成后,接下来就是Tkinter Designer发挥的时候了,Tkinter Designer需要输入Figma文件的url地址和API的令牌信息。

    对于文件url地址获取,点击右上角的Share按钮,然后在弹出窗口中点击Copy link按钮即可。

    对于API令牌的获取。回到主页面,点击右上角的settings,向下翻动找到Personal access tokens即可申请API令牌。

    需要注意的是,token会失效,过一段时间要申请新的token。同时,Tkinter Designer在转换过程中,不要修改figma文件,不然会出错。如果figma文件修改后,要重新复制url粘贴到 Tkinter Designer 中。

  4. 将Figma文件转化为Tkinter代码

    然后将 Figma 文件 URL 和 API 令牌粘贴到运行后的 Tkinter Designer 中,并且选择导出后的路径。

    转换完成后,进入build文件夹路径,并运行gui.py,可以看到figma文件成功地转换为Tkinter代码了。

功能模块实现

初始化GUI

Tkinter Designer转化后的代码虽然可以直接使用,但还是需要在构建的过程中根据需求进行整理和修改。

首先构建函数,以获取文件的各种绝对路径,以方便后续进行调用

#  获取GUI文件路径
ASSETS_PATH = Path(__file__).resolve().parent / "assets"
def relative_to_assets(path: str) -> Path:
    return ASSETS_PATH / Path(path)

# 获取当前目录路径
BASE_DIR = os.getcwd()
print("BASE_DIR:",BASE_DIR)

# 获取电脑文件
path = getattr(sys, '_MEIPASS', os.getcwd())
os.chdir(path)

基于gui.py进行修改,进行GUI初始化以及构建界面。

# 初始化GUI
window = Tk()

# 设置软件logo
logo = PhotoImage( file= application_path + "\\logo.png")
window.call('wm', 'iconphoto', window._w, logo)
window.title("微信好友助手")

# 构建界面
window.geometry("862x519")
window.configure(bg="#F7F7F7")

canvas = Canvas(
    window,
    bg="#F7F7F7",
    height=519,
    width=862,
    bd=0,
    highlightthickness=0,
    relief="ridge"
)

canvas.place(x=0, y=0)

日志和滚动条

为了方便告知用户软件运行到哪个步骤了,需要在运行日志中实时显示提示,如果日志过多,还需要有滚动条进行下拉,这两个功能Tkinter Designer无法直接提供,需要我们自己实现。

首先构建日志输入框(此部分Tkinter Designer已经帮我们实现了)

# 构建日志输入框
entry_image_3 = PhotoImage(
    file=relative_to_assets("entry_3.png"))
entry_bg_3 = canvas.create_image(
    629.5000000000001,
    354.4999999999999,
    image=entry_image_3
)
entry_3 = Text(
    bd=0,
    bg="#F2F2F2",
    fg="#000716",
    highlightthickness=0,
    yscrollcommand = sbar1.set
)
entry_3.place(
    x=436.0000000000001,
    y=216.9999999999999,
    width=387.0,
    height=273.0
)

接着创建一个滚动条控件,默认为垂直方向,将滚动条放置在右侧,设置当窗口大小改变时滚动条会沿着垂直方向延展,并且将控件绑定到日志框。

sbar1= Scrollbar(window) 
sbar1.pack(side=RIGHT, fill=Y)
sbar1.config(command=entry_3.yview)

接着定义一个notice(text)函数实现实时保存日志。由于这里的日志框使用的是Tkinter的entry控件,可以直接利用entry控件的函数。

# 保存日志函数,用entry组件
def notice(text):
    now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())  # 保存操作时间 
    entry_3.configure(state='normal') # entry可以修改
    entry_3.insert('insert', '[' + now + ']' + ':' + text + '\n' + '\n')  # 带上操作时间 
    entry_3.update() # 实时更新
    entry_3.configure(state='disabled') # entry修改关闭
    entry_3.see(END) # 自动下滑到底部

有了这个notice(text)函数之后,之后需要输出日志的时候,只需要直接调用即可,非常方便。比如在日志框中提示:

# 因为要等待5s,登录前在日志中进行提示
def printLoginBegin():
    notice("正在启动微信中,请稍候") # 调用notice函数
    time.sleep(0.5)

按钮

按钮的实现思路基本相同,只需要画出按钮GUI,并绑定点击事件函数。这里以登录微信按钮为例。Tkinter Designer已经帮我们实现了按钮的GUI,我们只需要在comand中将事件函数进行绑定。

# 绑定到tk的button_1
button_image_1 = PhotoImage(
    file=relative_to_assets("button_1.png"))

button_1 = Button(
    image=button_image_1,
    borderwidth=0,
    highlightthickness=0,
    # command=lambda: print("button_1 clicked"),
    command=lambda: [printLoginBegin(), thread_it(loginWechat,w)],
    relief="flat"
)
button_1.place(
    x=71.00000000000011,
    y=107.99999999999989,
    width=120.0,
    height=33.0
)

跳转到网页

WeChatFriendTool软件中,笔者还希望实现点击按钮,即跳转到网页的功能。首先需要定义一个跳转事件函数,并且将事件绑定到按钮。

以点击跳转到笔者的个人网页为例:

# 跳转事件
def personalWebsite(event):
    time.sleep(0.5)
    websiteLink = (
        "https://ferryxie.com")
    webbrowser.open_new_tab(websiteLink)
# 按钮
button_image_8 = PhotoImage(
    file=relative_to_assets("button_8.png"))
button_8 = Button(
    image=button_image_8,
    borderwidth=0,
    highlightthickness=0,
    command=lambda: notice("正在跳转去Ferry的个人网站:https://ferryxie.com"),
    relief="flat"
)
button_8.place(
    x=201.0000000000001,
    y=457.9999999999999,
    width=172.0,
    height=28.0
)

# 绑定事件
button_8.bind('<Button-1>', personalWebsite)

在经过Tkinter Designer的转换后,需要修改的大致如上,其他的Tkinter GUI代码基本可以拿来即用,大大提供了开发效率。到这里,软件的前端基本完成了,接下里需要实现后端功能。

后端功能实现

微信自动化

首先需要解决的是如何通过python调用API来登录微信的问题。如果现在在网上通过「微信自动化」、「微信API」、「微信好友列表」等关键词进行搜索,找到的项目几乎都是通过python的itchat包来登录网页版微信。但2017年后,新注册的微信基本登录不了网页版微信,这也就意味着很多项目代码都使用不了。

但最近两年也出来了一些不依赖itchat的微信自动化项目,例如:

  • WeChatPYAPI,分成收费版本和免费版本,免费版本只有基本的功能。

  • Wechaty: 基于Javascript(ES6)的微信个人账号机器人NodeJS/库

  • ItChat-UOS:利用UOS的网页版微信,绕过网页微信的登录限制

本次笔者使用免费版本的 WeChatPYAPI 作为登录微信和导出微信好友列表的API。这是一个基于PC端的Python接口,开发者可通过Python轻松调用,可进行二次开发,实现微信机器人、群管理等强大的功能,在此感谢作者的开源共享!

接口调用的依赖有些多,容易出bug,但总体使用起来非常简单,完整步骤如下:

  1. 克隆 WeChatPYAPI 项目(需要关闭杀毒软件,否则可能会误删libs文件夹下的dll文件)

  2. 选择对应python解释器环境(笔者使用的是python 3.9,所以保留了文件夹下的WeChatPYAPI.cp39-win_amd64.pyd文件,删除了其他版本的pyd文件)

  3. 安装指定版本的微信(免费版本使用的微信版本是3.3.0.115,可以在此点击下载:[WeChat 3.3.0.115](https://images-ferry.oss-cn-shenzhen.aliyuncs.com/WeChat 3.3.0.115.exe))

  4. 通过终端执行命令,安装所需要的依赖包

    pip install -r requirements.txt
    
  5. 这时候执行文件夹中的demo.py文件,扫码登录后可以看到接口调用成功了:

设置多线程

软件需要将GUI和功能函数通过多线程进行分离,不然在点击了界面按钮时,功能函数运行过程时,界面点击会出现无响应问题,甚至卡退。这里首先创建一个线程执行程序:

# 创建线程执行程序,防止功能函数阻塞
def thread_it(func, *args):     # 传入函数名和参数
    # 创建线程
    t = threading.Thread(target=func, args=args)
    # 守护线程
    t.setDaemon(True)
    # 启动
    t.start()

后续只需要调用此函数即可创建出一个线程,tkinter的lambda允许调用多个函数,例如:

   command=lambda: [printLoginBegin(), thread_it(loginWechat,w)]

初始化API,登录微信

接下来需要将接口封装成函数,以给GUI按钮进行调用。

WeChatPYAPI会占用电脑的8888端口,因此在初始化实例前,需要先检查8888端口是否占用,如果占用则杀掉。

# 根据端口号杀死进程
def kill_port_process(port):
    ret = os.popen("netstat -nao|findstr " + str(port))
    str_list = ret.read()
    if not str_list:
        print('端口未使用')
        return
    # 只关闭处于LISTENING的端口
    if 'TCP' in str_list:
        ret_list = str_list.replace(' ', '')
        ret_list = re.split('\n', ret_list)
        listening_list = [rl.split('LISTENING') for rl in ret_list]
        process_pids = [ll[1] for ll in listening_list if len(ll) >= 2]
        process_pid_set = set(process_pids)
        for process_pid in process_pid_set:
            os.popen('taskkill /pid ' + str(process_pid) + ' /F')
            print(port, '端口已被释放')
            time.sleep(1)

    elif 'UDP' in str_list:
        ret_list = re.split(' ', str_list)
        process_pid = ret_list[-1].strip()
        if process_pid:
            os.popen('taskkill /pid ' + str(process_pid) + ' /F')
            print('端口已被释放')
        else:
            print("端口未被使用")

# 每次启动前都杀死8888端口
kill_port_process(8888)

接着根据接口文档初始化实例:

# 构建API实例
w = WeChatPYApi(msg_callback=on_message, exit_callback=on_exit, logger=logging)

然后封装一个登录函数:

# 登录函数
def loginWechat(w):
    # 检查是否已经登录
    global loginFlag

    # 如果loginFlag==1,说明已经登录了,直接退出,否则继续
    if loginFlag:
        notice('您已经登录了微信,无需重复登录')
        return

    # 启动微信
    w.start_wx()

    # 可以选择保存登录二维码
    # w.start_wx(path=os.path.join(BASE_DIR, "login_qrcode.png"))  

    # 这里需要阻塞,等待获取个人信息
    while not w.get_self_info():
        time.sleep(5)
    # 日志提示
    if my_info and self_wx:
        notice("微信登录成功")
        notice("登录信息为:%s" % (my_info))
        notice("---------------------")
        loginFlag = 1
    else:
        notice("出错了,请重新进行登录")

最后将此函数绑定GUI的登录微信按钮即可实现,点击按钮,跳出微信二维码,进行扫码登录。

导出好友列表到Excel

此功能实现主要分为以下几个步骤:

首先检查是否已经登录了微信,如果已经登录,则通过WeChatPYAPI导出好友列表

# 导出好友列表
def friendList(w):
    if loginFlag == 1:
        lists = w.pull_list(self_wx=self_wx, pull_type=1)
        list2excel(lists)
        notice("好友列表导出完成,文件路径为:%s\\friends_list.xlsx" % (BASE_DIR))
        notice("---------------------")
    else:
        notice("您仍未登录,请先登录微信")

在这个过程中,需要将好友列表存储为Excel friends_list.xlsx,以供用户进行编辑。

# 将好友列表存储为 friends_list.xlsx
def list2excel(your_list):
    workbook = openpyxl.Workbook()
    worksheet = workbook.active
    worksheet.append(['wx_id', 'nick_name', 'remark', 'wx_account', 'avatar_url'])
    for item in your_list:
        worksheet.append([item['wx_id'], item['nick_name'], item['remark'], item['wx_account'], item['avatar_url']])
    workbook.save('friends_list.xlsx')

最后在日志中进行提示,以及将功能函数绑定到按钮即可。

上传excel文件和图片文件

用户需要将编辑好的将要发送的信息以及图片上传到软件,文件上传可以通过tkinter的filedialog实现,以上传excel文件为例,图片上传功能同理。

首先通过entry.get获取上传文件的绝对路径并填入界面的方框中:

# excel路径
excel_path = entry_1.get()
excel_path = excel_path.strip()

# 选择excel文件(button_5 以及 entery_1)
def select_excel():
    global excel_path
    # 打开文件选择对话框
    excel_path = filedialog.askopenfilename()
    # 清空控件
    entry_1.delete(0, END)
    # 将文件路径显示在输入框中
    entry_1.insert(0, excel_path)

接着将用户上传的excel文件读取为字典格式,以供后续发送信息时进行调用。同时在日志框中进行重复提示,确保用户不要发错消息。

# 读取编辑好将要发送信息的excel并存储为字典
def read_data_from_excel(filename):
    workbook = openpyxl.load_workbook(filename)
    worksheet = workbook.active
    for row in worksheet.iter_rows(min_row=2): 
        item = {}
        for cell in row:
            item[cell.column] = cell.value
        data.append(item)
    return data

# 在日志框中进行重复展示
def showExcel():
    global data
    if excel_path != "":
        data = read_data_from_excel(excel_path)
        print(data)
        notice(f'Excel文件读取成功,路径为:{excel_path} ')
        notice("请确认下面是否是您要发送的信息")
        notice(str(data))
    else:
        notice("仍未选择excel文件,请您选择要发送的信息文件")

最后将功能函数绑定到对应的按钮即可。

批量发送信息

在上述功能中读取了字典格式的信息后,只需要在接口中传入微信id和信息即可,功能函数如下,将其绑定到按钮即可。

def sendmsg(w):
    # 循环发送消息
    if excel_path != "":
        for i, item in enumerate(data):
            notice("第{}个:正在给{}发送".format(i + 1, str(item[1])))
            # 发送文本消息
            w.send_text(self_wx=self_wx, to_wx=str(item[1]), msg=str(item[2]))
            notice("文本信息发送成功")
            time.sleep(1)

            if image_path != "":
                # 发送图片消息
                w.send_img(self_wx=self_wx, to_wx=str(item[1]), path=image_path)
                time.sleep(1)
                notice("图片发送成功")
        notice("----------------------")
    else:
        notice("请选择您要发送的信息或图片")

经过多次测试,都没出现过问题。

批量下载好友头像

首先需要调用之前的导出好友列表函数,导出好友列表的excel文件,其中的avatar_url字段即为此好友的微信头像链接,通过requests库循环爬取链接,并下载所有的jpeg格式头像图片,保存在imgaes文件夹下。函数如下:

def download_image():

    printFriendBegin()
    friendList(w)

    df = pd.read_excel(friendlists_path)

    if not os.path.exists(avatar_path):
        os.makedirs(avatar_path)
        notice("存放头像图片的images文件夹创建完成")
    else:
        notice("存放头像图片的images文件夹已经存在")

    # 开始计数,循环
    show_download_image()
    count = 0
    for index, row in df.iterrows():
        if row["avatar_url"] != "":
            if not os.path.exists(os.path.join(BASE_DIR, "images", f"{row['wx_id']}.jpeg")):
                try:
                    response = requests.get(row["avatar_url"])
                    open(os.path.join(BASE_DIR, "images", f"{row['wx_id']}.jpeg"), "wb").write(response.content)
                    count += 1
                    notice(f"正在处理第{index+1}个图片: {row['wx_id']}.jpeg已经下载")
                except (requests.exceptions.MissingSchema, requests.exceptions.ConnectionError):
                    notice(f"正在处理第{index+1}个图片: {row['wx_id']}.jpeg链接无效,已跳过")
                    count += 1
                    pass
            else:
                notice(f"正在处理第{index + 1}个图片: {row['wx_id']}.jpeg已经存在")
                count += 1
    notice(f"{count} 个好友头像图片处理完成")

拼接头像照片墙

下载完所有的好友头像,接下来只需要对其进行读取,并通过PIL库(pip3 install Pillow)进行图像处理。PIL(Python Imaging Library)是Python平台事实上的图像处理标准库了,功能非常强大,但API却非常简单易用。

这里需要注意的是,这里PIL的导入需要放在tkinter之后进行import,不然可能会出错。

try:
    from PIL import Image
except ImportError:
    import Image

拼接成正方形的头像照片墙很简单,只需要计算各行列的头像数量并进行循环拼接即可,如下所示:

# 拼接方形头像
def joint_avatar_square():
    # 获取文件夹内头像个数
    length = len(os.listdir(avatar_path))
    notice(f'读取images文件夹成功,共有 {length} 张头像图片')
    show_joint_avatar_square()
    # 拼接后的图片大小
    image_size = 2560
    # 设置每个头像大小
    each_size = math.ceil(image_size / math.floor(math.sqrt(length)))
    # 计算所需各行列的头像数量
    x_lines = math.ceil(math.sqrt(length))
    y_lines = math.ceil(math.sqrt(length))
    image = Image.new('RGB', (each_size * x_lines, each_size * y_lines))
    x = 0
    y = 0
    # 循环拼接
    for image_file in os.listdir(avatar_path):
        try:
            with Image.open(os.path.join(avatar_path, image_file)) as img:
                img = img.resize((each_size, each_size))
                image.paste(img, (x * each_size, y * each_size))
                x += 1
                if x == x_lines:
                    x = 0
                    y += 1
        except IOError:
            notice(f"此头像读取失败:{os.path.join(avatar_path, image_file)}")

    img = image.save(avatar_square)
    notice(f'微信好友头像方形照片墙拼接成功,路径为:{avatar_square} ')

拼接成心形形状的思路差不多,只是多了判断图片是否在心形函数范围内的步骤,并进行坐标转化。在直角坐标系中,利用python可以表达心形的方程,并定义一个函数判断图像的坐标是否在心形函数内

# 计算心形
x** 2+ y** 2 + a * x= a * sqrt(x** 2+y** 2) 
x** 2+ y** 2 - a * x= a * sqrt(x** 2+y** 2)

# 判断图像的坐标是否在心形函数内 512**2=262144 1024**2=1048576
def get_heart_shape(x,y):
    y1 = 0.618 * np.abs(x) - 0.7 * np.sqrt(1048576 - x ** 2)
    y2 = 0.618 * np.abs(x) + 0.7 * np.sqrt(1048576 - x ** 2)
    if y<y1 or y>y2:
        return False
    else:
        return True

接着进行判断和拼接:

# 拼接为心形照片墙
def joint_avatar_love():
    # 获取文件夹内头像个数
    length = len(os.listdir(avatar_path))
    # 设定每个头像的大小
    image_size = 2048
    each_size = int(math.sqrt(float(image_size * image_size) / length))
    # 一行的图像个数,若为偶数个,则+1转换为奇数个
    # 修改每个图像的大小,使最后的心形对称
    num = int(image_size / each_size)
    if num % 2 == 0:
        num += 1
    each_size = int(image_size / num)
    # 照片墙的行数
    lines = int(image_size / each_size)
    # 创建Image对象,初始化大小,用实际拼接的尺寸
    image = Image.new('RGBA', (lines * each_size, lines * each_size))
    x, y = 0, 0
    for image_file in os.listdir(avatar_path):
        try:
            try:
                # 图像坐标从(0,0)开始,而心形函数对应左上角坐标为(-1024,1024),故此在判断坐标时,进行转换
                is_heart_part = get_heart_shape((-image_size/2) + x * each_size, (image_size/2) - y * each_size)
                if not is_heart_part:
                    pass
                else:
                    img = Image.open(os.path.join(avatar_path, image_file))
                    # 重新设置图像大小
                    img = img.resize((each_size, each_size), Image.BICUBIC)
                    # 根据x,y坐标位置拼接图像
                    image.paste(img, (x * each_size, y * each_size))
                # 更新下一张图像位置
                x += 1
            except:
                pass
            finally:
                # 一行一行拼接
                if x == lines:
                    x = 0
                    y += 1
        except IOError:
            notice(f"此头像读取失败:{os.path.join(avatar_path, image_file)}")

    img = image.save(avatar_love)
    notice(f'心形照片墙拼接成功,路径为:{avatar_love} ')

最后将函数绑定到GUI按钮即可,效果如下:

将python程序打包为exe软件

将 python 程序打包为exe软件主要可以使用:

  • pyinstaller:pyInstaller是一个十分有用的第三方库,它能够在Windows、Linux、 Mac OS X 等操作系统下将Python 源文件打包,通过对源文件打包, Python 程序可以在没有安装Python 的环境中运行,也可以作为一个独立文件方便传递和管理。
  • auto-py-to-exe:auto-py-to-exe 是一个用于将Python程序打包成可执行文件的图形化工具,相比于 pyinstaller ,它多了 GUI 界面,用起来更为简单方便。使用教程可以参考这篇文章:可视化打包 exe,这个Python神器绝了

这里由于使用了Tkinter和WeChatPYAPI,包含了一些外部文件,如果打包为单一exe文件会比较麻烦(需要逐一在spec文件中修改外部文件路径),所以这里笔者直接将项目打包为文件夹。

参考WeChatPYAPI的接口文档,在打包成软件的过程中需要注意以下两个问题:

cd进行项目路径,在终端中输入如下命令,等待打包完成即可(这里的logo.ico是软件的logo,可以通过这个网站将png转ico格式)

pyinstaller -w -i logo.ico main.py

打包完成后,会在当前路径中生成dist文件夹,其中的文件夹即是打包后的项目。

结语

至此,一个小软件就写完了,是不是其他也很简单?

在这个过程中,笔者本身也没有GUI基础,很多东西都是有需求后找解决方案进行尝试解决,所以好奇心、自学能力和解决问题的能力还挺重要的。在信息检索的过程中,笔者经过尝试实践后也发现很多教程都是无用的(也可能是我太菜了没理解作者的意思….),这也是我坚持写详细化博客的原因✊。

但这次笔者也发现了很多惊喜,以前一直觉得低代码平台很垃圾,但这次使用到的Tkinter Designer已经算很好用了,可以让小白也轻松写出漂亮的GUI,关键还是要有这份学习探索的心态以及一些计算机基础。

希望这篇文章可以给大家带来一些启发,经过一些学习,你也可以根据需求写一个软件来提高效率!


? 本项目源代码已开源至Github:WeChatFriendTool(如果对你有帮助,请动动小手给一个star,感谢!)

? 更具体的使用教程也可查看上一篇文章:【我写了个小软件,老板说要给我加薪】微信助手|个性化群发|好友头像照片墙|软件开发

参考链接

  1. Python 的图形界面(GUI)编程?
  2. Tkinter图形界面设计(GUI)
  3. 推荐一个Tkinter GUI 设计神器,不用一行代码就能搞定!
  4. Python学习笔记–exe文件打包与UI界面设计
  5. windows杀死占用某端口的进程
  6. Python实现一键生成微信好友头像墙
  7. python生成微信好友头像心形照片墙

评论

  1. Windows Chrome
    1 年前
    2023-1-29 17:47:41

    赞技术高手。

    • 博主
      土木坛子
      Macintosh Chrome
      1 年前
      2023-1-30 18:58:50

      谢谢前辈鼓励???

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇