写完自动化脚本后,怎么分享给同事?怎么部署到服务器?打包发布是必须掌握的技能。这篇文章系统讲解Python脚本的打包、发布和分发方法。

一、打包基础:setup.py和pyproject.toml

传统方式:setup.py

from setuptools import setup, find_packages

setup(
    name='my-automation',
    version='1.0.0',
    description='自动化脚本工具集',
    author='Your Name',
    author_email='your@email.com',
    packages=find_packages(),
    install_requires=[
        'requests>=2.28.0',
        'pandas>=1.5.0',
    ],
    entry_points={
        'console_scripts': [
            'my-auto=my_automation.main:main',
        ],
    },
    python_requires='>=3.8',
    classifiers=[
        'Programming Language :: Python :: 3',
        'License :: OSI Approved :: MIT License',
    ],
)

现代方式:pyproject.toml(推荐)

[build-system]
requires = ["setuptools>=45", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my-automation"
version = "1.0.0"
description = "自动化脚本工具集"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "your@email.com"}
]
dependencies = [
    "requests>=2.28.0",
    "pandas>=1.5.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0.0",
    "black>=22.0.0",
]

[project.scripts]
my-auto = "my_automation.main:main"

[tool.black]
line-length = 88
target-version = ['py38', 'py39', 'py310']

[tool.isort]
profile = "black"

二、打包成可执行文件

使用PyInstaller:打包成独立exe

# 安装
pip install pyinstaller

# 基本打包
pyinstaller --onefile your_script.py

# Windows下生成exe,Linux下生成可执行文件
# --onefile: 打包成单个文件
# --console: 显示控制台(无GUI时必须)
# --icon: 设置图标
# --name: 指定输出名称

高级打包配置:

# 带参数打包
pyinstaller \
    --onefile \
    --console \
    --name "MyAutomation" \
    --icon "app.ico" \
    --add-data "config;config" \
    --hidden-import=requests \
    --collect-all pandas \
    your_script.py

spec文件配置(复杂项目):

# automation.spec
from PyInstaller.utils.icons import collect_all_pyimg_icon

a = Analysis(
    ['src/main.py'],
    pathex=[],
    binaries=[],
    datas=[
        ('config/*.json', 'config'),
        ('templates/*', 'templates'),
    ],
    hiddenimports=['pkg_resources'],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=['tkinter'],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=None,
)

pyz = PYZ(a.pure, a.zipped_data, cipher=None)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='MyAutomation',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
)

使用cx_Freeze:跨平台打包

# setup.py for cx_Freeze
from cx_Freeze import setup, Executable

build_options = {
    'packages': ['requests', 'pandas'],
    'excludes': ['tkinter'],
    'include_files': ['config/', 'templates/'],
}

executables = [
    Executable('src/main.py', base='Console', target_name='MyAutomation'),
]

setup(
    name='MyAutomation',
    version='1.0.0',
    description='自动化脚本工具集',
    options={'build_exe': build_options},
    executables=executables,
)
# 打包
python setup.py build

三、打包成安装程序

使用HM Never Install:创建安装包

pip install hn
hn build --spec automation.spec

使用Inno Setup(Windows)

生成exe后,用Inno Setup创建安装程序:

[Setup]
AppName=MyAutomation
AppVersion=1.0.0
DefaultDirName={pf}\MyAutomation
OutputDir=installer
OutputBaseFilename=MyAutomation_Setup_v1.0.0

[Files]
Source: "dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs

四、发布到PyPI

准备发布

# 安装发布工具
pip install build twine

# 构建源码包和wheel
python -m build

上传到PyPI

# 注册账号 https://pypi.org/
# 创建API Token

# 上传(测试)
twine upload --repository testpypi dist/*

# 上传(正式)
twine upload dist/*

使用API Token

# ~/.pypirc配置
[pypi]
username = __token__
password = pypi-xxxxxxxxxxxx

五、自动化打包脚本

写一个自动化打包脚本:

#!/usr/bin/env python3
"""
自动化打包脚本
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
from datetime import datetime

class AutoPackager:
    def __init__(self, project_name, version='1.0.0'):
        self.project_name = project_name
        self.version = version
        self.root_dir = Path(__file__).parent.resolve()
        self.dist_dir = self.root_dir / 'dist'
        self.build_dir = self.root_dir / 'build'
    
    def clean(self):
        """清理构建目录"""
        dirs_to_clean = ['dist', 'build', '__pycache__', '*.egg-info']
        for pattern in dirs_to_clean:
            for p in self.root_dir.rglob(pattern):
                if p.is_dir():
                    print(f"删除目录: {p}")
                    shutil.rmtree(p, ignore_errors=True)
                elif p.is_file():
                    print(f"删除文件: {p}")
                    p.unlink()
    
    def build_wheel(self):
        """构建wheel包"""
        print("构建wheel包...")
        subprocess.run([sys.executable, '-m', 'build'], check=True)
        print("✓ wheel包构建完成")
    
    def build_exe(self):
        """打包成exe"""
        print("打包成exe...")
        subprocess.run([
            'pyinstaller',
            '--onefile',
            '--console',
            '--name', f"{self.project_name}_v{self.version}",
            '--distpath', str(self.dist_dir),
            'src/main.py'
        ], check=True)
        print("✓ exe打包完成")
    
    def create_zip(self):
        """创建zip分发包"""
        print("创建zip包...")
        zip_name = f"{self.project_name}_v{self.version}_{datetime.now():%Y%m%d}"
        zip_path = self.dist_dir / zip_name
        
        # 收集所有文件
        files_to_package = []
        for pattern in ['*.py', '*.txt', '*.md', 'config/**/*', 'templates/**/*']:
            files_to_package.extend(self.root_dir.glob(pattern))
        
        shutil.make_archive(str(zip_path), 'zip', self.root_dir, '.')
        print(f"✓ zip包创建完成: {zip_path}.zip")
    
    def run_all(self):
        """执行所有打包步骤"""
        print(f"{'='*50}")
        print(f"开始打包: {self.project_name} v{self.version}")
        print(f"{'='*50}\n")
        
        self.clean()
        self.dist_dir.mkdir(exist_ok=True)
        
        # 选择打包方式
        if '--exe' in sys.argv:
            self.build_exe()
        elif '--zip' in sys.argv:
            self.create_zip()
        else:
            self.build_wheel()
        
        print(f"\n{'='*50}")
        print(f"打包完成!输出目录: {self.dist_dir}")
        print(f"{'='*50}")
        
        # 列出生成的文件
        print("\n生成的文件:")
        for f in self.dist_dir.iterdir():
            size = f.stat().st_size / 1024
            print(f"  {f.name} ({size:.1f} KB)")

if __name__ == '__main__':
    import argparse
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--name', default='my-automation')
    parser.add_argument('--version', default='1.0.0')
    args = parser.parse_args()
    
    packager = AutoPackager(args.name, args.version)
    packager.run_all()

六、自动化发布到GitHub

使用GitHub Actions自动发布:

# .github/workflows/release.yml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'
      
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install build twine
      
      - name: Build
        run: python -m build
      
      - name: Publish to PyPI
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
        run: twine upload dist/*

七、分发清单

发布前检查清单:

CHECKLIST = [
    "代码是否清理了调试信息和测试数据?",
    "配置文件是否需要修改(路径、密钥等)?",
    "依赖版本是否固定?",
    "README.md是否完整?",
    "许可证文件是否包含?",
    "版本号是否更新?",
    "是否在多个环境测试过?",
    "打包后是否测试过exe?",
]

总结

打包发布的关键点:

  1. 小项目:直接分享.py文件或打包成exe
  2. 可复用项目:打包成wheel发布到PyPI
  3. 内部工具:打包成exe或zip,使用自动化脚本
  4. 持续发布:用GitHub Actions实现自动发布

掌握打包技能,让你的自动化脚本能够优雅地分享和部署。

更多推荐