参考链接

https://blog.csdn.net/Helloorld_1/article/details/131767478

前言

Streamlit 是一个开源的Python库,它用于快速创建和分享数据应用。它专为数据科学家和工程师设计,使他们能够轻松地将数据脚本转换为交互式 web 应用。使用Streamlit,你可以用少量的 Python 代码就能构建美观、功能强大的数据仪表盘,它支持热重载,这意味着你可以写代码和查看变化实时发生在浏览器中。

这意味着,很多时候我们在使用 Python 编程时,没有必要使用pyqt或者专门写一个 web 前端页面来实现某个小功能,但又可以依托 Streamlit 快速实现界面化的交互功能。

但是因为Streamlit应用,使用pyinstaller进行打包时,会报错:

'streamlit' 不是内部或外部命令,也不是可运行的程序

让打包后的 exe 无法执行,但解决办法却十分简单,仅需将Scripts环境中的steamlit.exe文件和app.py复制到入口文件同目录下即可。

具体实现流程如下:

1. 功能实现

示例项目为一个可视化批量生成二维码的应用qrcode_maker.py

# qrcode_maker.py
import io
import zipfile

import streamlit as st
import pandas as pd
import qrcode
from PIL import Image, ImageDraw, ImageFont


def generate_qr_code(content):
    qr = qrcode.QRCode(
        version=1,
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        box_size=10,
        border=1,
    )
    qr.add_data(content)
    qr.make(fit=True)
    img = qr.make_image(fill='black', back_color='white').convert('RGB')
    return img


def add_text(draw, text, position, font_size, color):
    font = ImageFont.truetype("simhei.ttf", font_size)  # 确保您有这个字体文件或更换为您系统中的有效字体
    draw.text(position, text, fill=color, font=font)


def main():
    st.title('批量生成二维码工具')

    uploaded_file = st.file_uploader("上传背景图片", type=['png', 'jpg', 'jpeg'])
    background_buffer = io.BytesIO()
    if uploaded_file is not None:
        background_buffer.write(uploaded_file.getvalue())
        background = Image.open(background_buffer)
        # st.image(background, caption='上传的背景图片')
        st.write(f"图片像素大小: {background.size[0]} x {background.size[1]}")

    qr_content = st.text_input("二维码内容(预览功能)")

    qr_size = st.number_input("二维码大小/像素", min_value=1, value=100)
    qr_position = st.text_input("二维码位置(格式:x,y)", "100,100")

    label_content = st.text_input("文字标签内容(预览功能)")
    label_font_size = st.number_input("文字标签字体大小", min_value=1, value=20)
    label_color = st.color_picker("文字标签颜色")
    label_position = st.text_input("文字标签位置(格式:x,y)", "100,150")

    if st.button("生成预览"):
        if uploaded_file and qr_content:
            background_buffer.seek(0)
            background = Image.open(background_buffer)
            qr_image = generate_qr_code(qr_content)
            qr_image = qr_image.resize((qr_size, qr_size))
            qr_position_tuple = tuple(map(int, qr_position.split(',')))
            background.paste(qr_image, qr_position_tuple)
            draw = ImageDraw.Draw(background)
            label_position_tuple = tuple(map(int, label_position.split(',')))
            add_text(draw, label_content, label_position_tuple, label_font_size, label_color)

            st.image(background, caption='预览图')
        else:
            st.error("请上传背景图片和输入二维码内容")

    file = st.file_uploader("上传xlsx或csv文件", type=['xlsx', 'csv'])
    df = None
    if file is not None:
        if file.type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
            df = pd.read_excel(file)
        else:
            df = pd.read_csv(file)
        st.write(df)

    if st.button("批量生成"):
        if uploaded_file and df is not None:
            zip_buffer = io.BytesIO()
            with zipfile.ZipFile(zip_buffer, 'a', zipfile.ZIP_DEFLATED) as zip_file:
                background_buffer.seek(0)
                background = Image.open(background_buffer)
                for index, row in df.iterrows():
                    temp_image = background.copy()
                    qr_image = generate_qr_code(str(row['content']))
                    qr_image = qr_image.resize((qr_size, qr_size))
                    qr_position_tuple = tuple(map(int, qr_position.split(',')))
                    temp_image.paste(qr_image, qr_position_tuple)
                    draw = ImageDraw.Draw(temp_image)
                    label_position_tuple = tuple(map(int, label_position.split(',')))
                    add_text(draw, str(row['title']), label_position_tuple, label_font_size, label_color)

                    temp_buffer = io.BytesIO()
                    temp_image.save(temp_buffer, format='PNG')
                    temp_buffer.seek(0)
                    zip_file.writestr(f"{row['title']}.png", temp_buffer.read())

            zip_buffer.seek(0)
            st.success("批量生成成功!")
            st.download_button(
                label="下载二维码ZIP",
                data=zip_buffer,
                file_name="qrcodes.zip",
                mime="application/zip"
            )
        else:
            st.error("请上传背景图片和xlsx或csv文件")


if __name__ == "__main__":
    main()

2. 创建纯净的打包环境

# 创建虚拟环境
python -m venv make_qrcode
    
# 激活环境
.\Scripts\activate

# 安装环境
pip install -r requirements.txt

3. 创建pyinstaller hooks

在主代码目录下新建hooks文件夹,并创建一个hooks-streamlit.py文件,代码如下:

from PyInstaller.utils.hooks import copy_metadata

datas = copy_metadata("streamlit")

4. 创建运行文件run_app.py

import streamlit.web.cli as stcli
import os, sys
 
 
def resolve_path(path):
    resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))
    return resolved_path
 
 
if __name__ == "__main__":
    # 这里的 streamlit app 是什么名字,需要修改下
    sys.argv = [
        "streamlit",
        "run",
        resolve_path("qrcode_maker.py"),     # 这里
        "--global.developmentMode=false",
    ]
    sys.exit(stcli.main())

此时的文件结构

5. 打包和配置

5.1. 先打包一次,生成.spec文件

pyinstaller --onefile --additional-hooks-dir=./hooks run_app.py --clean

5.2. 修改.spec文件

# -*- mode: python ; coding: utf-8 -*-
# 添加以下部分代码
from PyInstaller.utils.hooks import collect_data_files
from PyInstaller.utils.hooks import copy_metadata

# 设置 streamlit 运行时目录,一般在 Lib/site-packages 里,如果你和我一样是用上述命令行创建的环境用这个 datas 配置就行,如果是用 pycharm 创建的,需要改下目录,使用第2个 .venv
# 本质就是要指定 运行时环境,一般报错都有提示;
datas = [("Lib/site-packages/streamlit/runtime","./streamlit/runtime")]
# datas = [(".venv/Lib/site-packages/streamlit/runtime","./streamlit/runtime")]
datas += collect_data_files("streamlit")
datas += copy_metadata("streamlit")
 
 
block_cipher = None

# 修改下面 a 里面的 datas 为 =datas
# 如果打包后缺失 module, 需要配置 hiddenimports,这里要看你打包后允许报的是缺失哪个模块,比如我这里就缺失 "qrcode"
a = Analysis(
    ['run_app.py'],
    pathex=[],
    binaries=[],
    datas=datas,
    hiddenimports=['qrcode'], 
    hookspath=['./hooks'],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='run_app',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

5.3. 再次打包,使用.spec文件进行打包

pyinstaller run_app.spec --clean

5.4. 复制streamlit app文件到 dist 目录

现在点击run_app.py即可运行程序了

文章作者: Mark
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 有限进步
脚本/工具 Python 实用脚本
喜欢就支持一下吧
打赏
微信 微信
支付宝 支付宝