简介
通常在windows平台上开发完python服务后,需要通过引用虚拟环境后再运行,或者使用IDE软件启动,但如果想做成开机启动的本地服务,每次都这样启动就太麻烦了。
该文档介绍这个方法是把python程序制作成一个windows,启停特别方便,而且还可以设置开机启动。虚拟环境依赖的库也不需要考虑,只要在构建的时候在虚拟环境下构建,就会自动将依赖的库打包进去。
1、首先,有一段大家通用的代码,可以将python程序制作成服务。直接拷贝使用即可,在SvcDoRun函数内写上拉起自己服务的代码。
# -*- coding:utf-8 -*-
import win32serviceutil
import win32service
import win32event
import sys
import os
#设置编码
reload(sys)
sys.setdefaultencoding('utf-8')
#windows服务中显示的名字
class zlsService(win32serviceutil.ServiceFramework):
_svc_name_ ='web_movie' ###可以根据自己喜好修改
_svc_display_name_ ='web_movie' ###可以根据自己喜好修改
_svc_description_ ='web_movie' ###可以根据自己喜好修改
def __init__(self,args):
win32serviceutil.ServiceFramework.__init__(self,args)
self.stop_event = win32event.CreateEvent(None,0,0,None)
self.run =True
def SvcDoRun(self):
# 这里是你的启动代码,由于我的是flask框架程序,只需要把我的主文件from过来即可。
from web_movieimport app
app.run()
def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.stop_event)
self.ReportServiceStatus(win32service.SERVICE_STOPPED)
self.run =False
if __name__ =='__main__':
import sys
import servicemanager
if len(sys.argv) ==1:
try:
evtsrc_dll = os.path.abspath(servicemanager.__file__)
servicemanager.PrepareToHostSingle(zlsService)#如果修改过名字,名字要统一
servicemanager.Initialize('zlsService',evtsrc_dll)#如果修改过名字,名字要统一
servicemanager.StartServiceCtrlDispatcher()
except win32service.erroras details:
import winerror
if details == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
win32serviceutil.usage()
else:
win32serviceutil.HandleCommandLine(zlsService)#如果修改过名字,名字要统一
2、安装构建环境需要用的库,在虚拟环境内安装即可。
pip install PyInstaller==3.4
pip install pywin32==224
3、编写构建bat,其实就几个cmd命令,但是为了方便调试,我还是写成了一个脚本。
:: 停止运行的服务
sc stop web_movie
:: 删除这个服务
sc delete web_movie
:: 等个几秒钟,服务刚停止,文件不会马上释放,不然文件不让删除
TIMEOUT /T 3
:: 删除旧版本构建的内容
rmdir /s/q dist
rmdir /s/q build
del PythonService.spec
:: 构建现在的程序,这里有个坑,由于templates目录和static目录和代码不是直接引用,在pyinstaller构建的时候会忽略,导致服务无法运行,后面详细介绍。
pyinstaller -F --add-data "templates;templates" --add-data "static;static" PythonService.py
:: 将构建好的exe程序安装到系统服务
dist\PythonService.exe install
:: 启动服务
sc start web_movie
4、这时候在任务管理器里面就可以看到已经运行的服务了
5、如果不能启动,排查方法
如果启动后立即停止,或者弹窗说“服务没有及时响应启动或控制请求”,这时候可以查看windows的计算机管理界面应用日志,来看详细信息。
6、我遇到的坑
Traceback (most recent call last):
File "site-packages\win32\lib\win32serviceutil.py", line 839, in SvcRun
File "PythonService.py", line 28, in SvcDoRun
File "site-packages\flask\app.py", line 943, in run
File "site-packages\werkzeug\serving.py", line 812, in run_simple
File "site-packages\werkzeug\_reloader.py", line 267, in run_with_reloader
ValueError: signal only works in main thread
上面这个报错使我的代码出现过得,经过我的各种最小化测试,发现这个错误是加载配置项的时候就会出现,最终定位到了我的一行配置文件。
DEBUG =True,这一行配置让我排查了整整一天。
7、我遇到的坑
修复了上面的问题,我的的服务终于起来了,但是又发现,在浏览器里面竟然无法访问。如下提示:
Internal Server Error
The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
这个问题也让我苦恼了很久,在我最小化测试的时候,缩减到仅剩一个hello world都无法正常显示,但是创建一个新的flask项目就可以正常显示,经过我细心的diff这两个项目,
发现我在return的时候使用了render_template模板,然而这个目录在pyinstaller的时候,并没有打包进来,然后这一切都明白了,就是因为在构建的时候没有将这个目录添加进来,
导致项目启动后无法使用模板文件。
修复方法就是bat文件内写的,添加上静态目录的编译就可以了。
pyinstaller -F --add-data "templates;templates" --add-data "static;static" PythonService.py