小白也能掌握的Python部署應(yīng)用技術(shù)
點(diǎn)擊關(guān)注“Python數(shù)據(jù)分析實(shí)例”
設(shè)為“置頂或星標(biāo)”,送達(dá)干貨不錯過!

前言
如何將你寫的Python程序發(fā)布給其他人用呢?
今天分享一下非常簡單可行的方式發(fā)布 Python 應(yīng)用程序,發(fā)布后只需要通過計(jì)算機(jī)管理后臺啟停你的應(yīng)用程序,熟悉后可在其基礎(chǔ)上進(jìn)行功能拓展改進(jìn),是小白快速開發(fā)一個可用落地應(yīng)用的最佳選擇。本文將詳細(xì)介紹兩種方法將Python程序部署成windows服務(wù)。Python web應(yīng)用服務(wù)器部署不在此次討論之中。
一、Python腳本部署成windows定時(shí)任務(wù)
1) Outline_analysis.py腳本準(zhǔn)備
主要實(shí)現(xiàn)數(shù)據(jù)在線定時(shí)反饋功能,函數(shù)執(zhí)行流主要分為3部分,tick1()函數(shù)實(shí)現(xiàn)數(shù)據(jù)提取生產(chǎn),tick2()函數(shù)實(shí)現(xiàn)發(fā)送報(bào)警郵件,end_program()函數(shù)實(shí)現(xiàn)關(guān)閉后臺python解釋器釋放資源,確保程序持續(xù)穩(wěn)定運(yùn)行。
from?datetime?import?datetime
import?time
from?tools.sender_mail?import?Sender_mail
from?tools.Wrapper_timer?import?time_logger
now_time?=?datetime.now().date()
#業(yè)務(wù)數(shù)據(jù)生產(chǎn)過程
def?tick1():
????time.sleep(4)
????print('Tick!The?time?is:%s'?%?datetime.now())
#預(yù)警信息發(fā)送
def?tick2():
????#發(fā)送郵件
????Sender_mail.sender_mail()
????print('發(fā)送完成')
#?def?get_pid(name):
#?????'''
#??????作用:根據(jù)進(jìn)程名獲取進(jìn)程pid
#?????'''
#?????import?psutil
#?????pids?=?psutil.process_iter()
#?????print("["?+?name?+?"]'s?pid?is:")
#?????for?Pid?in?pids:
#?????????if(Pid.name()?==?name):
#?????????????P=Pid.pid
#?????return?P
#關(guān)閉解釋器
def?end_program():
????#?pr_name?=?get_pid(name="pythonw.exe")
????#?os.system('%s%s'?%?("taskkill?/F?/PID?",pr_name))
????#os.system('%s%s'?%?("taskkill?/F?/IM?pythonw.exe"))
????
????import?subprocess
????CREATE_NO_WINDOW?=?0x08000000
????subprocess.call('taskkill?/F?/IM?pythonw.exe',?creationflags=CREATE_NO_WINDOW)
#業(yè)務(wù)流程
@time_logger
def?tick():
????tick1()
????tick2()
????end_program()
if?__name__?=='__main__':
????tick()注:
1、業(yè)務(wù)數(shù)據(jù)生產(chǎn)過程--tick1()函數(shù)
該函數(shù)用休眠模擬數(shù)據(jù)處理耗時(shí),實(shí)際應(yīng)用中連接生產(chǎn)數(shù)據(jù)庫,經(jīng)過一系列處理流程,本地開發(fā)數(shù)據(jù)庫Mysql,數(shù)據(jù)庫連接主機(jī)名寫的是localhost;如果項(xiàng)目部署到遠(yuǎn)程服務(wù)器上,其數(shù)據(jù)庫和項(xiàng)目部署在不同機(jī)器上,數(shù)據(jù)庫連接的主機(jī)名就需要修改成數(shù)據(jù)庫所部署的那臺機(jī)器的公網(wǎng)ip或者域名,通過ipconfig查看。
2、關(guān)閉python解釋器--end_program()函數(shù)
調(diào)用taskkill命令,關(guān)閉指定的進(jìn)程
詳細(xì)參數(shù)用法可網(wǎng)上搜索查看;常見用法:TASKKILL [/S system [/U username [/P [password]]]] { [/FI filter] [/PID processid | /IM imagename] } [/F] [/T]
窗口隱藏不顯示
將腳本解析程序python.exe改為pythonw.exe.將不會彈出控制臺窗口。
使用系統(tǒng)os.system()關(guān)閉解釋器程序?qū)⑵灵W退出,建議使用python調(diào)用cmd命令隱藏窗口方法subprocess.call(),將解決這個問題,詳細(xì)可參考--
https://stackoverflow.com/questions/7006238/how-do-i-hide-the-console-when-i-use-os-system-or-subprocess-call
3、數(shù)據(jù)管道流按順序執(zhí)行
該處可利用多線程隊(duì)列實(shí)現(xiàn)數(shù)據(jù)流按順序執(zhí)行
def?tick():
????print('job?thread_id-{0},?process_id-{1}'.format(threading.get_ident(),?os.getpid()))
????w?=?WorkerThread()
????w.start()
????w.send(tick1())
????w.send(tick2())
????w.run()
????w.send(end_program())
????w.run()
????w.close()#?使用?隊(duì)列一般可以簡化多線程的程序。例如,可以使用共享隊(duì)列將線程連接在一起,而不必依賴鎖的保護(hù)。
#?在這種模型下,工作者線程一般充當(dāng)數(shù)據(jù)的消費(fèi)者。
from?threading?import?Thread
from?queue?import?Queue
import?time,os,threading
class?WorkerThread(Thread):
????def?__init__(self,*args,**kwargs):
????????Thread.__init__(self,*args,**kwargs)
????????self.input_queue=Queue()
????def?send(self,item):
????????self.input_queue.put(item)
????def?close(self):
????????self.input_queue.put(None)
????????self.input_queue.join()
????def?run(self):
????????while?True:
????????????item=self.input_queue.get()
????????????if?item?is?None:
????????????????break
????????????#實(shí)際開發(fā)中,此處應(yīng)該使用有用的工作代替
????????????print(item)
????????????self.input_queue.task_done()
????????#完成,指示收到和返回哨兵
????????self.input_queue.task_done()
????????return4、后期優(yōu)化細(xì)節(jié)
tools文件夾下存放Sender_mail、time_logger等拓展模塊。每個執(zhí)行任務(wù)函數(shù)都可能失敗,因此可用加入裝飾器拓展函數(shù)功能,增加計(jì)時(shí)、日志記錄等,比如一個任務(wù)不確定什么時(shí)間完成,可設(shè)置超時(shí)時(shí)間,如果超時(shí)仍然未完成可用通過控制超時(shí)重新運(yùn)行,也可以設(shè)置重試次數(shù),超過一定次數(shù)報(bào)錯退出。
import?functools
import?logging
import?time
from?datetime?import?datetime
log_format?=?"%(asctime)s?%(message)s"
logging.basicConfig(format=log_format,
????????????????????level=logging.INFO,
????????????????????datefmt="?%Y-%m-%d?%H:%M:%S",??#?時(shí)間格式
????????????????????filename="log1.log",
????????????????????filemode="a")
#?裝飾器會被任何函數(shù)使用。其中的func(*args, **kwargs)中的func就是目標(biāo)函數(shù),args、kwargs是這個函數(shù)調(diào)用的參數(shù)
def?time_logger(func):
[email protected](func)
??def?wrapper_timer(*args,?**kwargs):
????logging.info(f"BeginFunction?{func.__name__!r},?args={args},?kwargs={kwargs}")
????start_time?=?time.perf_counter()??#?1
????value?=?func(*args,?**kwargs)
????end_time?=?time.perf_counter()??#?2
????run_time?=?end_time?-?start_time??#?3
????logging.info(f"EndFunction?{func.__name__!r}?in?{run_time:.4f}?secs")
????return?value,run_time
??return?wrapper_timer
2)將腳本設(shè)置成windows定時(shí)任務(wù)
首先搜索框-計(jì)算機(jī)管理-點(diǎn)擊任務(wù)計(jì)劃程序庫-可查看已有的定時(shí)計(jì)劃任務(wù)
點(diǎn)擊創(chuàng)建任務(wù)進(jìn)入按提示設(shè)置即可

常規(guī)設(shè)置,名稱、用戶設(shè)置,更改用戶或組注意用戶權(quán)限設(shè)置

接下來,設(shè)置觸發(fā)器,定時(shí)任務(wù)開始運(yùn)行觸發(fā)條件,根據(jù)需要設(shè)置

最關(guān)鍵的一步,設(shè)置python解釋器位置及執(zhí)行腳本路徑

電腦休眠狀態(tài)定時(shí)任務(wù)不會執(zhí)行,需要勾選喚醒計(jì)算機(jī)執(zhí)行該任務(wù)

最后,啟用該定時(shí)任務(wù),確保單個實(shí)例執(zhí)行

Windows 10定時(shí)任務(wù)運(yùn)行報(bào)錯:操作員或系統(tǒng)管理員拒絕了請求的解決方法
解決辦法:首先確保python解釋器在進(jìn)程列表中退出,打開控制面板->管理工具->本地安全策略,選擇安全設(shè)置->本地策略->安全選項(xiàng),在右邊列表中找到域控制器:允許服務(wù)器操作者計(jì)劃任務(wù),將狀態(tài)改為已啟用。其次,用戶權(quán)限分配設(shè)置問題及環(huán)境變量配置。

二、Python的exe執(zhí)行文件部署成windows服務(wù)
1)Outline_analysis.exe腳本準(zhǔn)備
from?datetime?import?datetime
import?time,os,threading
from?tools.Wrapper_timer?import?time_logger
from?apscheduler.schedulers.blocking?import?BlockingScheduler
from?apscheduler.executors.pool?import?ThreadPoolExecutor
from?tools.Queue_thread?import?WorkerThread
import?logging
now_time?=?datetime.now().date()
#?業(yè)務(wù)數(shù)據(jù)生產(chǎn)過程
@time_logger
def?tick1():
????print('job1?thread_id-{0},?process_id-{1}'.format(threading.get_ident(),?os.getpid()))
????time.sleep(1)
????print('Tick!The?time?is:%s'?%?datetime.now())
#?預(yù)警信息發(fā)送
@time_logger
def?tick2():
????print('job2?thread_id-{0},?process_id-{1}'.format(threading.get_ident(),?os.getpid()))
????#?發(fā)送郵件
????time.sleep(1)
????print('郵件發(fā)送完成?%s'%?datetime.now())
#數(shù)據(jù)存儲
@time_logger
def?tick3():
????print('job3?thread_id-{0},?process_id-{1}'.format(threading.get_ident(),?os.getpid()))
????#?數(shù)據(jù)存儲
????time.sleep(1)
????print('存儲完成?%s'%?datetime.now())
def?tick():
????print('job?thread_id-{0},?process_id-{1}'.format(threading.get_ident(),?os.getpid()))
????w?=?WorkerThread()
????w.start()
????w.send(tick1())
????w.send(tick2())
????w.run()
????w.send(tick3())
????w.run()
????w.close()
if?__name__?==?'__main__':
????executors?=?{
????????'default':?ThreadPoolExecutor(10)
????}
????scheduler?=?BlockingScheduler(executors=executors,)
????#?scheduler.add_job(tick,max_instances=5,trigger='interval',?seconds=10,id='interval_task')
????#注意裝飾器@time_logger不能放在tick前面,否則會出現(xiàn)阻塞,程序無法繼續(xù)運(yùn)行
????scheduler.add_job(tick,max_instances=5,trigger='interval',?seconds=10)
????#?run_time=tick1()[1]
????#?print("完成時(shí)間",run_time)
????#?scheduler._logger=logging
????try:
????????scheduler.start()
????except?(KeyboardInterrupt,SystemExit):
????????passexe部署與py腳本部署不同:py腳本運(yùn)行不需要在代碼中設(shè)置時(shí)間控制邏輯,在定時(shí)任務(wù)設(shè)置運(yùn)行時(shí)間計(jì)劃,而exe部署需要將定時(shí)運(yùn)行代碼寫入腳本后打包。當(dāng)然,你也可以在編輯器中運(yùn)行程序,確保程序不會被關(guān)閉或者設(shè)置定時(shí)任務(wù)控制服務(wù)的開啟關(guān)閉。
bug解決:max_instances默認(rèn)值為1,它表示id相同的任務(wù)實(shí)例數(shù);通過設(shè)置max_instances參數(shù)。裝飾器不要放在主線程tick()上面即可。
打包:進(jìn)入項(xiàng)目目錄,激活虛擬環(huán)境,切換到python基本所在文件夾位置,輸入:“pyinstaller -F -w *.py” 就可以制作出exe。生成的文件放在同目錄dist下。-F(注意大寫)是所有庫文件打包成一個exe,-w是不出控制臺窗口。
打包錯誤問題定位-cmd路徑下執(zhí)行Outline_analysis.exe,通過查看運(yùn)行輸出代碼運(yùn)行print信息:比如python使用pyinstaller打包成exe報(bào)Faild to execute script 解決,這個問題出現(xiàn)的原因是,有些模塊是隱藏導(dǎo)入的,但是pyinstaller打包時(shí)并未指定,所以執(zhí)行時(shí)找不到此模塊。其他錯誤可按提示百度、谷歌、GitHub 和 StackOverflow四件套解決。


2)將exe執(zhí)行文件部署成windows服務(wù)
使用windows自帶的命令sc 使用sc create 方法創(chuàng)建。
這種方法不一定能成功,如果你的exe不符合服務(wù)的規(guī)范,可能會啟動失敗
在第一種方法失敗的情況下,我們可以在官網(wǎng)下載instsrv.exe 和 srvany.exe 兩個小工具注冊服務(wù)。
1、下載后放入C盤下創(chuàng)建的一個文件夾。以管理員的身份運(yùn)行命令行,首先進(jìn)入工具所在的文件夾。具體操作如下:
執(zhí)行命令 instsrv.exe 你的服務(wù)名稱 srvany.exe文件路徑

2、啟動注冊表:win+r——regedit,打開注冊表路徑:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\OnlineServer
3、找到剛注冊的服務(wù)名稱
新建項(xiàng):Parameters
在Parameters下新建字符串值:
名稱為:Application 值: exe 所在的全路徑 包含exe名稱為:Application 值: exe 所在的全路徑 包含exe在Parameters下新建字符串值:名稱為:AppDirectory 值: exe 所在的路徑

4、打開服務(wù),找到剛才所創(chuàng)建的服務(wù)名稱,配置屬性,點(diǎn)擊啟動

5、刪除服務(wù),先將服務(wù)停止cd 到instsrv.exe 所在目錄 然后執(zhí)行instsrv.exe OnlineServer remove
補(bǔ)救措施
如果加入定時(shí)任務(wù)模塊apscheduler打包失敗的話,可以通過以下方式實(shí)現(xiàn)服務(wù)定時(shí)啟動。
1、創(chuàng)建bat快捷方式,然后右鍵快捷方式-->properties-->advanced-->Run as administrator。
2、下載bat轉(zhuǎn)成exe工具,將bat轉(zhuǎn)成exe,然后右鍵exe-->properties-->Compatibility-->Run as administrator。
給大家分享一個windows的批處理文件(.bat文件)轉(zhuǎn)exe可執(zhí)行文件的工具。使用非常簡單,輸入需要轉(zhuǎn)換的腳本語句,點(diǎn)擊轉(zhuǎn)換即可。
StartServers.bat文件
net stop OnlineServers 停止服務(wù)
net start OnlineServers 重啟服務(wù)


以上工具獲取見文末

將生成的restart設(shè)置成定時(shí)任務(wù),設(shè)置方法見上文。
通過以上流程設(shè)置,一個簡單可用的腳本程序部署完畢,靜靜的在后臺運(yùn)行為你服務(wù)。當(dāng)然,簡單的應(yīng)用可以通過以上方式簡單部署,復(fù)雜的大型項(xiàng)目還是得上部署框架啦!
Python 項(xiàng)目開發(fā)部署與發(fā)布一般流程如下:
1、環(huán)境配置
(1)開發(fā)環(huán)境Python 版本、anaconda環(huán)境、 pip 安裝 Python 依賴等
(2)虛擬環(huán)境搭建,用 pipenv 安裝 項(xiàng)目的Python 依賴
(3)安裝IDE如Pycharm編輯器
(4)數(shù)據(jù)庫的部署等
2、測試環(huán)境測試
3、配置服務(wù),部署到生產(chǎn)環(huán)境,并發(fā)布應(yīng)用
趕快上手部署一波,給你小伙伴每日郵箱轟炸、定時(shí)關(guān)心!哈哈

后臺回復(fù)【注冊服務(wù)小工具】獲取
