故事是从机房角落的一台台式机开始的,ta有着奔腾的心和一条2G的内部存储颗粒,以及win7系统,如果只是运行业务软件的话,一切都是那么的,刚刚好。这个世界总有些地方不允许我们自由的安装python环境来欢快的运行py脚本,所以在完成开发之后,我们的工具需要打包成exe,来适配奔腾的心,以及奔腾的心的win8/win10/win7其他版本的众多小伙伴。
Akaroka:解决问题而不产生更多的问题zhuanlan.zhihu.com
在之前的文章里可以了解的到,手头的工具是一个立志于完成伟大自动化测试使命的Python脚本,根据业务大佬的需求加了点数据采集和截图收集的优秀feature。关于性能数据采集,那第一个想到的必然是优秀的psutil库,这里按下不表,有机会另一开一篇说。
psutil documentationpsutil.readthedocs.io那么截图呢?
Airtest自带了截图方法,也就是airtest.core.win.screen包里的screenshot方法,如果说为了截图功能而给项目安装上Airtest总觉得有些大材小用,毕竟人家多种算法的图像识别定位才是主业,这工具开发完之后还需要打包成exe供不具备python环境的测试机使用,所以整上一个无比巨大的第三方库总有些得不偿失。
(就不能看下源码,给丫拆了么? --住口)
那么PIL怎么样,PIL作为python图像标准处理库,一直都很行的。
没错,Python3下的继任者pillow,有着出色的Image.grab()方法,可以按照指定区域大小获取到缓存中的像素数据并创建一个图像的拷贝返回,并且返回的Image对象Image.save()方法能够将这份数据保存为所需格式的图片文件。
pillow包在windows上的截图方法grabscreen_win32没有给出具体代码,是通过C来实现。
from PIL import ImageGrab
if __name__ == '__main__':
image = ImageGrab.grab()
image.save("example_image.png")
然而,感觉还是要安装PIL,不够纯洁。
(强行说批话)
而且似乎有些慢,加上time模块测试了下截图时间,在写文章用的这台组装机上的话大概是花费0.11~0.12秒左右(慢?),如果是需要短时间尽可能多的截图记录操作细节的话(嗯?),这个速度可能还需要再提升一些。
(我看你就是为了整活?)
没错,或许咱们可以用windowsAPI来整活。我们可以安装pywin32库,来调用windows的API实现屏幕图像数据的获取与存储。
pywin32这个包为Python使用win平台的接口提供了诸多便利,感谢开源世界!
mhammond/pywin32: Python for Windows (pywin32) Extensions (github.com)github.com具体实现代码见下:
import win32gui
import win32api
import win32ui
import win32con
def win_capture(iamge_file):
SM_XVIRTUALSCREEN = 76
SM_YVIRTUALSCREEN = 77
SM_CXVIRTUALSCREEN = 78
SM_CYVIRTUALSCREEN = 79
hwnd = win32gui.GetDesktopWindow()
# get complete virtual screen including all monitors
w = win32api.GetSystemMetrics(SM_CXVIRTUALSCREEN)
h = win32api.GetSystemMetrics(SM_CYVIRTUALSCREEN)
x = win32api.GetSystemMetrics(SM_XVIRTUALSCREEN)
y = win32api.GetSystemMetrics(SM_YVIRTUALSCREEN)
hwndDC = win32gui.GetWindowDC(hwnd)
mfcDC = win32ui.CreateDCFromHandle(hwndDC)
saveDC = mfcDC.CreateCompatibleDC()
saveBitMap = win32ui.CreateBitmap()
saveBitMap.CreateCompatibleBitmap(mfcDC, w, h)
saveDC.SelectObject(saveBitMap)
saveDC.BitBlt((0, 0), (w, h), mfcDC, (x, y), win32con.SRCCOPY)
saveBitMap.SaveBitmapFile(saveDC, iamge_file)
if __name__ == '__main__':
win_capture("example_screenshot.png")
过于出色,截一张图的时间被压缩到了0.03秒这个量级!值得一提的是,Airtest用来截图的screenshot方法的底层也是通过win32gui/win32ui/win32con一众小伙伴来实现的。
事情似乎发展的过于顺利,在开发完截图功能之后,顺理成章的来到了打包exe的环节。当把打好的包部署到隔壁奔腾的心的win7上时,居然没问题。然而拿到隔壁另一台配置还算可以的win7上却报了个pythonXX.dll依赖查找不到的错误。回忆了下,奔腾的心之前有安装过与开发设备相同的python环境,看来是打包环节不够OK,还连着python的依赖。
后来实验了几把,发现是win32ui里有些方法打包时出了点差错,于是我们的问题来到了打包技术上。那么既然我们使用的是Pyinstaller -F,也或许可以尝试Nuitka这个打包神器。
User Manualnuitka.net且慢!是不是还可以有其他的方法,总感觉又要跑题了。
还真有,某天网上冲浪的时候发现了个挺香的截图包,mss!
Welcome to Python MSS’s documentation!python-mss.readthedocs.io他来了
他是一个跨平台的使用纯洁的python实现的截图库,通过ctypes加载user32.dll和gdi32.dll库来实现。
from mss import mss
if __name__ == '__main__':
with mss() as sct:
sct.shot()
完事,coding,打包。连着部署到勇敢的心的win7/win8/win10的众多小伙伴上都没有啥大问题,完美收官!
对了,我们来看看上面提到的若干截图库截一张全屏图片的速度,还是开发用的组装机,数字单位是秒:
PIL is 0.113354
pyscreeze is 0.100891
mss is 0.085594
pywin32 is 0.032675
可以看到mss的速度处于pillow(PIL)与pywin32之间,mss打包成exe之后在win平台各个系统之间的兼容性较win32好一些,而且足够轻巧,工具功能加上截图整体才6M,还算可以接受。当然以上结论都是在这次的工具开发的背景下的经验总结,暂时没有深究其原因哈。
那究竟只是偶尔出现的相关性还是不可名状的必然呢,不得而知,这个问题下节课提问,课代表记一下。
乱入的pyscreeze也是一个常用的图片处理的库,但是截图功能依赖的是pillow(PIL),所以不过多做介绍啦。
One more thing
这次工具的截图功能的原始需求,来自于工具需要记录详尽的操作过程,所以需要短时间收集到尽可能多的细节。
工具设计了两个进程,一个进程进行自动化测试的相关操作,另一个进程用来收集截图与性能数据,彼此通过共享内存的变量来单向通信。打包成单个exe可执行文件在第一次部署在测试机运行时,主进程拉起了无限个自己和无限个收集进程,简直吓尿。后来了解到这是一个多进程脚本,使用pyinstaller打包成单个exe时会出现的问题,pyinstaller官方也给出了解释与解决方案,当然这种都是老哥们早就知道的老问题了。
简单的处理方法就是在主文件的入口加一句话,
Freeze, don't move!(冻住!不许走!)
再打包就可以啦.
Recipe Multiprocessing · pyinstaller/pyinstaller Wiki (github.com)github.com multiprocessing — Process-based parallelism — Python 3.9.1rc1 documentationdocs.python.orgimport multiprocessing
if __name__ == '__main__':
multiprocessing.freeze_support()
# your code
print("main code")
这个世界,需要更多的英雄!