zoukankan      html  css  js  c++  java
  • Python3.6+pyinstaller+Django

    方案(一)Python3.6+pyinstaller+windows服务

    一、Python3.6(64位)环境清单

    • Django==1.11.7
    • django-windows-tools==0.2
    • PyInstaller==3.3

    主要的工具包就这些,如果其中还需其他依赖包,可再进行安装。

    二、创建Helloworld

    2.1、创建django工程示例

    E:>django-admin startproject helloworld
    E:helloworld>python manage.py startapp hello
    

      若还有数据库,请按django正常流程进行数据库操作,更多详细操作可参见自强学堂的Django 基本命令
      然后就是尝试启动,看是否启动成功。

    E:helloworld>python manage.py runserver 8800
    

      在浏览器中访问http://localhost:8800/,若可以显示django的服务接口清单,则表示服务启动正常,若不正常,请参考django的文档。
      当然,还可以查看端口是否正常启动【netstat –ano | find “8800”】,如果有监听,则应该不会有问题。

    2.2、添加windows服务功能

    2.2.1、安装与配置

    • 【pip install django-windows-tools】,可参见官方文档
    • 添加django配置
      在【E:helloworldhellosettings.py】文件中,向变量INSTALLED_APPS中添加'django_windows_tools'
    • 生成项目的service.*文件
      【E:helloworld>python manage.py winservice_install】,此处可能与官方文档不一样,官方应该是错的。此时会自动生成service.py和service.ini两个文件
    • 配置service.ini文件
      官网有详细说明,大体为services是调用服务的入口,其中run的值为下文中设置的节点名称,如果run指令中含有多个节点命令,那么就会起多个线程来执行。本例中只会用到services、runserver、log这3个节点。其余节点可视情况配置。例如:
    [services]
    # Services to be run on all machines
    run=runserver      ## 表示会使用到下文的[runserver]中的命令,此名称可随意定
    clean=d:logsservice.log  ## 要定期清理的日志路径,需与[log]中filename对应,否则在停止服务时会报找不到此日志文件
    
    [runserver]
    # Runs the debug server and listen on port 8000
    # This one is just an example to show that any manage command can be used
    command=runserver    ## django的runserver命令
    parameters=--noreload --insecure 0.0.0.0:8800  ## django的启动参数
    
    [log]
    filename=d:logsservice.log
    level=INFO
    
    • 修改service.py文件:
      为使服务更人性化,可在service.py文件的_svc_display_name_变量后面添加服务的描述【_svc_description_】:
    _svc_display_name_ = "HelloWorldService"
    _svc_description_ = "HelloWorldService"  ## 建议处
    _config_filename = "service.ini"
    

    2.2.2、手工添加和启动服务

    E:helloworld>python service.py install
    Installing service HelloWroldService
    Service installed
    
    E:helloworld>python service.py start
    Starting service HelloWroldService
    

      此时,浏览器访问url能正常显示服务接口清单,且端口都正常,表示服务启动正常,windows服务搭建进行到一半了。一般此步骤之前不会存在问题。

    2.3、打包工程Pyinstaller

    如何下载安装就不讲解了,官网非常详细。

    2.3.1、先打包成文件夹形式(方便排查问题)

    E:helloworld>pyinstaller -n hello -y --add-data "service.ini;." service.py
    E:helloworld>disthellohello.exe install
    E:helloworld>disthellohello.exe start
    

      上述会在当前目录下生成dist文件夹,下面是生成的最终打包的文件。
      -add-data表示将service.ini手工添加到打包程序中,若不添加,在运行时会提示找不到此文件。
      -y表示自动覆盖上次的打包程序。
      最后的参数service.py为windows服务的入口。

    编译和运行过程中可能会存在如下报错:

    • 问题1:缺少win32timezone包
      直接在service.py中添加【import win32timezone】,pyinstaller就能自己找到加载包了
    • 问题2:服务install正常,但start异常:
      报【Error starting service: 服务没有及时响应启动或控制请求。】
      此报错可根据网上的解决方法(具体原理本人也不清楚,能解决问题就可以了):
    import win32serviceutil
    import traceback
    import servicemanager
    import winerror
    ......
    if __name__ == "__main__":
        if len(sys.argv) > 1 and sys.argv[1] == 'test':
            test_commands(base_path)
        else:
            if len(sys.argv) == 1:
                try:evtsrc_dll = os.path.abspath(servicemanager.__file__)
                    servicemanager.PrepareToHostSingle(Service)
                    servicemanager.Initialize('HelloService', evtsrc_dll)
                    servicemanager.StartServiceCtrlDispatcher()
                except Exception as exp:
                    print('ERROR : %s, Detail : %s' % (exp, traceback.format_exc()))
                    if exp.args[0] == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
                        win32serviceutil.usage()
            else:
                win32serviceutil.HandleCommandLine(Service)
    
    • 问题3:启动时不会报错,但端口是没有监听,windows事件查看器中会显示报错:
    The instance's SvcRun() method failed 
    Traceback (most recent call last):
      File "site-packageswin32libwin32serviceutil.py", line 835, in SvcRun
      File "site-packagesdjango_windows_toolsservice.py", line 269, in SvcDoRun
    FileNotFoundError: [WinError 2] 'C:\Windows\system32\service.ini'
    

     即找不到service.ini文件,虽然我们打包时添加了此文件,但此exe文件在实际运行时并没有查找找到当前目录的文件。原因与pyinstaller运行机制有关,详见文档,需修改service.py文件import后中配置文件路径:

    base_path = os.path.dirname(os.path.abspath(__file__))
    if getattr(sys, 'frozen', False):
        base_path = sys._MEIPASS  ## 表示在实际生产运行时让程序去此目录查找
    if not base_path in sys.path:
        sys.path.append(base_path)
    
    • 问题4:启动时不会报错,windows事件查看器中会显示报错(即问题3已经修改),日志文件中也无报错记录,但端口是没有监听,windows事件查看器中现象为:在打印出'starting'后就停止打印其他信息,未打印出'Starting command'。
      正常日志应显示'Starting command D:...site-pack..service.py runserver --noreload --insecure 0.0.0.0:8800'。
      • 分析过程:
        手工在site-packagesdjango_windows_toolsservice.py文件中添加日志分析,发现在调用链【self.start()】-【start_commands()】-【spawn_command()】-【start_django_command()】中,最后那次调用未生效,即spawn_command中生成Process多线程处理时未生效;经google后,找到了PyInstaller-built Windows EXE fails with multiprocessing ,里面提到

        无标题.png

        也就是说在windows平台中需要添加freeze_support()函数。
      • 解决方法:
    if __name__ == "__main__":
        multiprocessing.freeze_support()
    
    • 问题5:线程正常启动,事件查看器中显示报错:

      pyinstall少包.png

      说明我们在pyinstaller打包时少打了包。经过多次这种尝试,最终找出了所有django所需的包,需修改hello.spec文件,在【hiddenimports】数组中添加如下包:

    hiddenimports=['django.contrib.admin.apps', 'django.contrib.auth.apps', 'django.contrib.contenttypes.apps', 'django.contrib.messages.apps', 'django.contrib.staticfiles.apps', 'django.contrib.sessions.models', 'django.contrib.sessions.apps', 'django.contrib.messages.middleware', 'django.contrib.auth.middleware', 'django.contrib.sessions.middleware', 'django.contrib.sessions.serializers']
    

    注意,此时应执行:【pyinstaller -y hello.spec】命令

    • 问题6:Django参数命令不正确。
      • 事件查看器显示:
    Exception occured : Traceback (most recent call last):
      File "site-packagesdjango_windows_toolsservice.py", line 156, in start_django_command
      File "site-packagesdjangocoremanagement\__init__.py", line 364, in execute_from_command_line
      File "site-packagesdjangocoremanagement\__init__.py", line 356, in execute
      File "site-packagesdjangocoremanagementase.py", line 277, in run_from_argv
      File "site-packagesdjangocoremanagementase.py", line 58, in parse_args
      File "argparse.py", line 1733, in parse_args
      File "site-packagesdjangocoremanagementase.py", line 62, in error
      File "argparse.py", line 2389, in error
      File "argparse.py", line 2376, in exit
    SystemExit: 2
    
      • d:logsservice.log显示:
    [INFO/Process-1] Starting command : service.py runserver --noreload --insecure 0.0.0.0:8800
    [INFO/Process-1] usage: service.py runserver [-h] [--version] [-v {0,1,2,3}]
                                [--settings SETTINGS] [--pythonpath PYTHONPATH]
                                [--traceback] [--no-color] [--ipv6]
                                [--nothreading] [--noreload]
                                [addrport]
    
    [INFO/Process-1] service.py runserver: error: unrecognized arguments: --insecure
    
      • 即insecure参数不对,那就去掉吧。把service.ini的runserver节点下修改成【parameters=--noreload 0.0.0.0:8800】

    小结:
    本节主要介绍了使用django-windows-tools及pyinstaler打包过程中遇到的各种问题,在本文最后再贴出源码。

    2.3.2、打包成单文件形式

    打包生成单文件,可有2种方法:

    • 通过命令行倒推:
      先备份hello.spec文件,再运行命令【pyinstaller -n hello -y --add-data "service.ini;." –F service.py】添加-F参数,表示打包单文件;再打包生成新的hello.spec;然后把上述少的程序包添加到hiddenimports数组里;最后再进行打包。
    • 直接修改hello.spec文件:
      去掉【exclude_binaries=True,】,并添加【a.binaries, a.zipfiles, a.datas,runtime_tmpdir=None,】4行配置,再进行打包。

    打包完后,最终运行时如:

    E:helloworld>disthello.exe install
    E:helloworld>disthello.exe start
    

    比文件夹形式的,中间会少一层目录。

    注意:运行单文件会比目录形式的时间长一点,它会首先解压至临时目录中,再运行,如果中间没有报错,就会立刻删除临时文件。因此,若项目中存在一些要时刻访问的配置文件,则需新建其他目录进行额外的管理。

    2.4、其他可能遇到的问题

    2.4.1、Error installing service: 指定的服务已标记为删除。 (1072)

    一般为【服务】或【事件查看器】未关闭,关闭了即可解决,如果关闭了还出问题,可能就需要重启电脑,把与这个服务关联的进程给清理掉。

    2.4.2、PermissionError: [WinError 5] 拒绝访问:'E:helloworlddisthelloservicemanager.pyd'

    一般为【事件查看器】未关闭,关闭了即可解决,如果关闭了还出问题,可能就需要重启电脑,把与这个服务关联的进程给清理掉。

    三、完整源码

    3.1、service.ini

    [services]
    # Services to be run on all machines
    run=runserver
    clean=APPLOGSservice.log
    
    [BEATSERVER]
    # There should be only one machine with the celerybeat service
    run=celeryd celerybeat
    clean=APPLOGScelerybeat.pid;APPLOGSeat.log;APPLOGScelery.log
    
    [celeryd]
    command=celeryd
    parameters=-f APPLOGScelery.log -l info
    
    [celerybeat]
    command=celerybeat
    parameters=-f APPLOGSeat.log -l info --pidfile=APPLOGScelerybeat.pid
    
    [runserver]
    # Runs the debug server and listen on port 8000
    # This one is just an example to show that any manage command can be used
    command=runserver
    parameters=--noreload 0.0.0.0:18800
    
    [log]
    filename=APPLOGSservice.log
    level=INFO
    

    3.2、service.py

    #!/usr/bin/env python
    import os
    import os.path
    import sys
    import win32serviceutil
    import win32timezone
    import traceback
    import servicemanager
    import winerror
    import multiprocessing
    import re
    
    # This is my base path
    base_path = os.path.dirname(os.path.abspath(__file__))
    if getattr(sys, 'frozen', False):
        base_path = sys._MEIPASS
    if not base_path in sys.path:
        sys.path.append(base_path)
    
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hello.settings")
    
    from django_windows_tools.service import DjangoService,test_commands, error
    
    class HelloService(DjangoService):
        _base_path = base_path
        _svc_name_ = "HelloService"
        _svc_display_name_ = "HelloService"
        _svc_description_ = "XXXXXXX"
        _config_filename = "service.ini"
    
        def __init__(self, args):
            self.__replace_log_path()
            DjangoService.__init__(self, args)
    
        ## 将service.ini手工移动到系统目录下的LOTS目录中
        def __replace_log_path(self):
            try:
                file_dir = os.getenv('SYSTEMROOT') + '\..\LOTS'
                if not os.path.exists(file_dir):
                    os.makedirs(file_dir)
                old_file = os.path.join(HelloService._base_path, HelloService._config_filename)
                new_file = old_file + '.run'
                with open(old_file, 'r') as f:
                    old = f.read()
                new = re.sub("APPLOGS", file_dir, old)
                with open(new_file, 'w') as f:
                    f.write(new)
                HelloService._config_filename = new_file
            # win32file.CopyFile(new_file, old_file, 0)
            except Exception as exp:
                err = 'ERROR : %s, Detail : %s' % (exp, traceback.format_exc())
                error(err)
    
    if __name__ == "__main__":
        multiprocessing.freeze_support()
        argv_len = len(sys.argv)
        if argv_len > 1 and sys.argv[1] == 'test':
            test_commands(base_path)
        else:
            if argv_len == 1:
                try:
                    evtsrc_dll = os.path.abspath(servicemanager.__file__)
                    servicemanager.PrepareToHostSingle(HelloService)
                    servicemanager.Initialize('HelloService', evtsrc_dll)
                    servicemanager.StartServiceCtrlDispatcher()
                except Exception as exp:
                    print('ERROR : %s, Detail : %s' % (exp, traceback.format_exc()))
                    if exp.args[0] == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
                        win32serviceutil.usage()
            else:
                win32serviceutil.HandleCommandLine(HelloService)
    

    3.3、hello.spec——文件夹形式

    # -*- mode: python -*-
    
    block_cipher = None
    
    a = Analysis(['service.py'],
                 pathex=['E:\hello'],
                 binaries=[],
                 datas=[('service.ini', '.')],
                 hiddenimports=['django.contrib.admin.apps', 'django.contrib.auth.apps', 'django.contrib.contenttypes.apps', 'django.contrib.messages.apps', 'django.contrib.staticfiles.apps', 'django.contrib.sessions.models', 'django.contrib.sessions.apps', 'django.contrib.messages.middleware', 'django.contrib.auth.middleware', 'django.contrib.sessions.middleware', 'django.contrib.sessions.serializers'],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    pyz = PYZ(a.pure, a.zipped_data,
                 cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              exclude_binaries=True,
              name='hello',
              debug=False,
              strip=False,
              upx=True,
              console=True )
    coll = COLLECT(exe,
                   a.binaries,
                   a.zipfiles,
                   a.datas,
                   strip=False,
                   upx=True,
                   name='hello')
    

    3.4、hello.spec——单文件形式

    # -*- mode: python -*-
    
    block_cipher = None
    
    a = Analysis(['service.py'],
                 pathex=['E:\hello'],
                 binaries=[],
                 datas=[('service.ini', '.')],
                 hiddenimports=['django.contrib.admin.apps', 'django.contrib.auth.apps', 'django.contrib.contenttypes.apps', 'django.contrib.messages.apps', 'django.contrib.staticfiles.apps', 'django.contrib.sessions.models', 'django.contrib.sessions.apps', 'django.contrib.messages.middleware', 'django.contrib.auth.middleware', 'django.contrib.sessions.middleware', 'django.contrib.sessions.serializers'],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher)
    pyz = PYZ(a.pure, a.zipped_data,
                 cipher=block_cipher)
    exe = EXE(pyz,
              a.scripts,
              a.binaries,
              a.zipfiles,
              a.datas,
              name='hello',
              debug=False,
              strip=False,
              upx=True,
              runtime_tmpdir=None,
              console=True )

    转载:https://www.jianshu.com/p/a53b430b1410

  • 相关阅读:
    warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
    Windows10+CLion+OpenCV4.5.2开发环境搭建
    Android解决部分机型WebView播放视频全屏按钮灰色无法点击、点击全屏白屏无法播放等问题
    MediaCodec.configure Picture Width(1080) or Height(2163) invalid, should N*2
    tesseract
    Caer -- a friendly API wrapper for OpenCV
    Integrating OpenCV python tool into one SKlearn MNIST example for supporting prediction
    Integrating Hub with one sklearn mnist example
    What is WSGI (Web Server Gateway Interface)?
    Hub --- 机器学习燃料(数据)的仓库
  • 原文地址:https://www.cnblogs.com/mxhmxh/p/9367666.html
Copyright © 2011-2022 走看看