Python+Playwright及软件打包
Selenium/Playwright网页自动化测试工具
在做办公自动化过程中接触了Selenium这个工具,方便爬取数据或者自动模拟鼠标/键盘操作,后面发现了更牛逼的Playwright,而且可以自动录制操作,直接生成相关代码,Playwright具体使用不在这里介绍,随便浏览器输入Playwright,就会发现很详细的内容。
Python+Playwright及软件打包
1、Python(版本必须≥3.7)使用Playwright,需要先安装对应的包,这个跟Python安装其他包是一样的,pip install playwright。
2、Playwright执行自动化操作依赖指定的浏览器,Chromium/FireFox/WebKit,这个是指定的,不能自己乱下载。详见官网说明
3、每个版本的 Playwright 都需要特定版本的浏览器二进制(exe可执行文件)文件才能运行。默认情况下,Playwright从Microsoft和Google公共CDN将Chromium,WebKit和Firefox浏览器下载到特定于操作系统的缓存文件夹中:
(Windows)%USERPROFILE%\AppData\Local\ms-playwright
(MacOS)~/Library/Caches/ms-playwright
(Linux)~/.cache/ms-playwright
4、这些浏览器的大小:281M chromium-XXXXXX、187M firefox-XXXX、180M webkit-XXXX,另外还有一个几兆大小的FFMPEG(音视频库)
5、安装完Playwright包,c:\users\cnxigao13.conda\envs\webgui\lib\site-packages\playwright\driver\package\browsers.json中已经定义了浏览器的版本(路径c:\users\cnxigao13.conda\envs\webgui是我自己的虚拟环境路径)。
默认情况下在使用指令:
pip install playwright # 这个安装python的playwright包
playwright install # 这个安装所有默认的浏览器
playwright install chromium # 一般只装这一个浏览器就够了,要是装另外两个浏览器,后面的参数名可以修改
上面安装的浏览器在windows的默认位置是%USERPROFILE%\AppData\Local\ms-playwright
重点
6、在实际应用中,尤其是要打包的时候,浏览器也要放在打包的文件夹内,就牵涉到浏览器安装位置的问题,在playwright官网说明中就写了下载浏览器导指定位置,而不是默认位置的方法:
set PLAYWRIGHT_BROWSERS_PATH=绝对地址
playwright install chromium
这样就会把浏览器的文件夹ms-playwright安装到指定路径下,也可以安装到playwright包的路径下,但一般没必要。
很多教程里面,尤其是能搜索到的一些打包playwright教程(如pyinstaller打包playwright),里面会写
PLAYWRIGHT_BROWSERS_PATH=0 playwright install chromium
这个其实很容易搞错,这个是指你切换到对应的路径下,然后调用指定,0代表安装在本路径(你已经在命令行或者Anaconda Prompt中切换到这个路径下了),而如果你没有进入这个路径,直接执行,就要按上面我说的绝对地址去指定。
7、很多人也用playwright codegen进行自动生成操作代码了,在pycharm等编译器中也能正常运行了,可一打包,在使用自己打包好的exe程序时,就会出现闪退。然后又重新打包,保留控制台,以确定到底是哪里报错。
报错一
无法在路径中发现:%USERPROFILE%\AppData\Local\ms-playwright\chromium-XXXX\chrome-win\chrome.exe,然后提示让你用playwright install进行安装或更新。
很多人都进行安装,然后发现还是搞不定,还是会报这个错误,但明明已经安装上了,在编译器中运行也不会报错,单一打包就报错找不到浏览器。
核心问题:
from playwright.sync_api import sync_playwright
def run(playwright):
browser = playwright.chromium.launch(headless=True)
context = browser.new_context()
你用playwright codegen生成的代码都是使用函数playwright.chromium.launch()进行浏览器加载,这个它只识别默认的浏览器路径,也有人在其中增加参数executable_path=包含chrome.exe的绝对路径,以确保能找到指定的路径,但最终只是编译阶段没问题,一打包还是找不到路径。
这里要改一下浏览器加载函数,改用playwright.chromium.launch_persistent_context()点击查看1以及点击查看2,此时,就真正制定了需要加载浏览器的相对路径。等后面软件打包好,就把ms-playwright这个文件夹扔到.dist文件夹(与生成的exe同层级),同时还要把playwright这个包(…/lib/site-packages/playwright)也扔到.dist文件夹(与生成的exe同层级),此时打包的exe就能正常运行了。
但是,你也会发现,playwright和ms-playwright这两个文件夹是没打包的,这个没什么问题,一个是包文件夹(300多兆),一个是浏览器文件夹(200多兆),这也就是为什么前面安装只安装一种浏览器,浏览器数量严重影响软件的大小。
user_data_dir = "ms-playwright/chromium-1005/chrome-win/user data" # 指定缓存目录,用相对路径
executable_path = "ms-playwright/chromium-1005/chrome-win/chrome.exe" # 指定可执行文件路径,用相对路径
# 上面这两个参数是必须的
browser = playwright.chromium.launch_persistent_context(
user_data_dir=user_data_dir,
executable_path=executable_path,
headless=True, # 控制无头或有头
)
# 修改浏览器加载函数后,下面两行改为一行
# context = browser.new_context()
# page = context.new_page()
page = browser.new_page()
page.goto("https://www.baidu.com")
...
page.close()
# 修改浏览器加载函数后,context就变成未定义,此行要注释掉
# context.close()
browser.close()
报错二
也有的人没有修改浏览器加载函数,只是增加了相应的可执行程序路径,如点击查看3
from playwright.sync_api import sync_playwright
def run(playwright):
browser = playwright.chromium.launch(headless=True, executable_path='...chrome.exe所在的绝对路径')
context = browser.new_context()
编译没问题,但打包后,控制台运行时会报错,类似于下面:
Traceback (most recent call last):
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\app.py", line 3, in <module>
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\playwright\sync_api\_context_manager.py", line 67, in __enter__
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\playwright\sync_api\_context_manager.py", line 48, in greenlet_main
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\asyncio\base_events.py", line 642, in run_until_complete
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\playwright\_impl\_connection.py", line 161, in run_as_sync
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\playwright\_impl\_connection.py", line 166, in run
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\playwright\_impl\_transport.py", line 60, in run
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\asyncio\subprocess.py", line 236, in create_subprocess_exec
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\asyncio\base_events.py", line 1661, in subprocess_exec
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\asyncio\windows_events.py", line 389, in _make_subprocess_transport
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\asyncio\base_subprocess.py", line 36, in __init__
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\asyncio\windows_events.py", line 885, in _start
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\asyncio\windows_utils.py", line 153, in __init__
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\subprocess.py", line 951, in __init__
File "C:\Users\chwba\PYCHAR~1\NUITKA~3\APP~1.DIS\subprocess.py", line 1420, in _execute_child
FileNotFoundError: [WinError 2] The system cannot find the file specified
或着FileNotFoundError: [WinError 2] 然后是一串乱码汉字
这个需要手动将playwright这个包(…/lib/site-packages/playwright)放到打包好的exe程序同级文件夹下(.dist文件夹),如果还搞不定,请参考报错一,修改浏览器加载函数,然后将playwright和ms-playwright这两个包都放到exe程序同级文件夹下(.dist文件夹)
上面一直没提打包指令,因为nuitka、pyinstaller好像都没有对playwright支持,没法用一些特殊的指令,这里就把playwright当成常规的包,手动将playwright和ms-playwright这两个包都放到exe程序同级文件夹下(.dist文件夹),打包指令还沿用前期的打包指令,不会因为playwright的应用而特意采用新参数。
参考博客
https://blog.csdn.net/qq_36991535/article/details/124781189
其他的参考就是nuitka/pyinstaller/playwright这几个的github,把它们所有的提问都翻一遍,发现也有人遇到用nuitka/pyinstaller打包playwright的难题,钻研了两个星期,终于搞定。