传送门
本系列原创博文传送门:
本章目标
本章目标是将工具打包成可执行文件。
暂时选择py2exe模块。
步骤实施
找了一篇示范性的文章,《使用 py2exe 打包 Python 程序》。
第一步是安装py2exe模块:
pip install py2exe --user
这里需要加上 “--user” ,否则会出现权限问题。
第二步,在文件夹下,新建python文件 setup.py :
from distutils.core import setup
setup(console=["PortSearchGUI.py"]) # 入口文件
第三步,将命令行切换到python文件目录下:
C:\Users\admin\PycharmProjects\PortSearch>python setup.py py2exe
出现报错:
error: invalid command 'py2exe'
修改代码:
from distutils.core import setup
import py2exe
setup(console=["PortSearchGUI.py"]) # 入口文件
第二行的代码 “import py2exe”,一开始提示没有被调用,实际上在命令行运行时,会使用到,加上之后,前面的报错没有了。
出现了新的报错:
running py2exe
9 missing Modules
------------------
? AppKit imported from pyperclip
? Foundation imported from pyperclip
? PyQt4 imported from pyperclip
? _posixshmem imported from multiprocessing.resource_tracker, multiprocessing.shared_memory
? _testinternalcapi imported from test.support
? gtk imported from pyperclip
? qtpy imported from pyperclip
? readline imported from cmd, code, pdb
? resource imported from test.support
Building 'dist\PortSearchGUI.exe'.
error: [Errno 2] No such file or directory: 'C:\\Users\\admin\\AppData\\Roaming\\Python\\Python39\\site-packages\\py2exe\\run-py3.9-win-amd64.exe'
先找找缺少 run-py3.9-win-amd64.exe 的原因,据说py2exe只支持 python 3.4及以下。
查了下,有人建议先从缺失的模块开始入手,这些missing Modules理论上讲在代码里是不会调到的,试着修改代码试试:
from distutils.core import setup
import py2exe
setup(console=["PortSearchGUI.py"], # 入口文件
options={'py2exe': {'excludes': ['AppKit', 'Foundation', 'PyQt4', '_posixshmem', '_testinternalcapi', 'gtk',
'qtpy', 'readline', 'resource']}},) # 入口文件
再次运行下看看,报错,提示少了,只剩下一个问题:
running py2exe
Building 'dist\PortSearchGUI.exe'.
error: [Errno 2] No such file or directory: 'C:\\Users\\admin\\AppData\\Roaming\\Python\\Python39\\site-packages\\py2exe\\run-py3.9-win-amd64.exe'
到 C:\Users\admin\AppData\Roaming\Python\Python39\site-packages\py2exe 目录下看一看,发现有个
run_w-py3.9-win-amd64.exe 文件:
复制一个文件出来,将其命名为: run-py3.9-win-amd64.exe
再次运行:
C:\Users\admin\PycharmProjects\PortSearch>python setup.py py2exe
运行成功:
running py2exe
Building 'dist\PortSearchGUI.exe'.
Building shared code archive 'dist\library.zip'.
Copy c:\python39\python39.dll to dist
Copy C:\Python39\DLLs\unicodedata.pyd to dist\unicodedata.pyd
Copy C:\Python39\DLLs\select.pyd to dist\select.pyd
Copy C:\Python39\DLLs\_bz2.pyd to dist\_bz2.pyd
Copy C:\Python39\DLLs\_lzma.pyd to dist\_lzma.pyd
Copy C:\Python39\DLLs\pyexpat.pyd to dist\pyexpat.pyd
Copy C:\Python39\DLLs\_socket.pyd to dist\_socket.pyd
Copy C:\Python39\DLLs\_hashlib.pyd to dist\_hashlib.pyd
Copy C:\Python39\DLLs\_decimal.pyd to dist\_decimal.pyd
Copy C:\Python39\DLLs\_ssl.pyd to dist\_ssl.pyd
Copy C:\Python39\DLLs\_queue.pyd to dist\_queue.pyd
Copy C:\Python39\DLLs\_elementtree.pyd to dist\_elementtree.pyd
Copy C:\Python39\DLLs\_ctypes.pyd to dist\_ctypes.pyd
Copy C:\Python39\DLLs\_tkinter.pyd to dist\_tkinter.pyd
Copy C:\Python39\DLLs\_testcapi.pyd to dist\_testcapi.pyd
Copy C:\Python39\DLLs\_asyncio.pyd to dist\_asyncio.pyd
Copy C:\Python39\DLLs\_multiprocessing.pyd to dist\_multiprocessing.pyd
Copy C:\Python39\DLLs\_overlapped.pyd to dist\_overlapped.pyd
Copy C:\Python39\lib\site-packages\shiboken2\shiboken2.pyd to dist\shiboken2.shiboken2.pyd
Copy C:\Python39\lib\site-packages\PySide2\QtNetwork.pyd to dist\PySide2.QtNetwork.pyd
Copy C:\Python39\lib\site-packages\PySide2\QtWidgets.pyd to dist\PySide2.QtWidgets.pyd
Copy C:\Python39\lib\site-packages\PySide2\QtCore.pyd to dist\PySide2.QtCore.pyd
Copy C:\Users\admin\AppData\Roaming\Python\Python39\site-packages\PyQt5\QtWidgets.pyd to dist\PyQt5.QtWidgets.pyd
Copy DLL C:\Python39\lib\site-packages\PySide2\Qt5Qml.dll to dist\
Copy DLL C:\Python39\DLLs\tcl86t.dll to dist\
Copy DLL C:\Python39\DLLs\libssl-1_1.dll to dist\
Copy DLL C:\Python39\DLLs\libcrypto-1_1.dll to dist\
Copy DLL C:\Python39\lib\site-packages\PySide2\Qt5Core.dll to dist\
Copy DLL C:\Python39\lib\site-packages\PySide2\pyside2.abi3.dll to dist\
Copy DLL C:\Python39\DLLs\tk86t.dll to dist\
Copy DLL C:\Python39\lib\site-packages\PySide2\Qt5Widgets.dll to dist\
Copy DLL C:\Python39\lib\site-packages\PySide2\Qt5Gui.dll to dist\
Copy DLL C:\Python39\DLLs\libffi-7.dll to dist\
Copy DLL C:\Python39\lib\site-packages\PySide2\Qt5Network.dll to dist\
Copy DLL C:\Python39\lib\site-packages\shiboken2\shiboken2.abi3.dll to dist\
Copy ExtensionDLL C:\Python39\python3.dll to dist\
在项目目录下看到新增一个文件夹:
找到入口文件 PortSearchGUI.exe
到项目目录下看一下,双击运行入口文件,出现报错:
打开log文件,发现报错:
Traceback (most recent call last):
File "PortSearchGUI.py", line 3, in <module>
File "<frozen zipimport>", line 259, in load_module
File "<loader>", line 10, in <module>
File "<loader>", line 8, in __load
ImportError: (could not import module 'PySide2.QtGui') 'C:\\Users\\admin\\PycharmProjects\\PortSearch\\dist\\PySide2.QtWidgets.pyd'
在命令行里输入:python
输入:import PySide2.QtGui
C:\Users\admin\PycharmProjects\PortSearch>python
Python 3.9.0 (tags/v3.9.0:9cf6752, Oct 5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import PySide2.QtGui
发现不报错,试着在 PortSearchGUI.py 中加入一行引入 import PySide2.QtGui,删除dist目录,再试着打包一次。
报错:
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
查一下问题,根据 信息 ,先进入 C:\Python39\Lib\site-packages\PySide2 ,将 plugins 文件夹下的 platforms 文件夹复制到 PySide2目录下,与 designer.exe 同目录。
再次打包,还是报一样的错误,再将 PySide2文件夹复制到 C:\Users\admin\AppData\Roaming\Python\Python39\site-packages 目录下,再次打包,再次试图运行,报了另外一个错:
查看日志文件,看到报错:
Traceback (most recent call last):
File "PortSearchGUI.py", line 3, in <module>
ModuleNotFoundError: No module named 'PySide2.QtWidgets'
在PortSearchGUI.py中加入引用
import PySide2.QtWidgets
再次打包并运行,在日志里又看到:
Traceback (most recent call last):
File "PortSearchGUI.py", line 3, in <module>
ModuleNotFoundError: No module named 'PySide2.QtWidgets'
修改:
from PySide2 import *
再次打包并运行,在日志里又看到:
Traceback (most recent call last):
File "PortSearchGUI.py", line 3, in <module>
AttributeError: module 'PySide2' has no attribute 'QtCore'
找不到问题,试着运行了下 PortSearchGUI.py,发现报错:
Traceback (most recent call last):
File "C:\Users\admin\PycharmProjects\PortSearch\PortSearchGUI.py", line 3, in <module>
from PySide2.QtWidgets import QApplication, QLineEdit, QLabel, QPlainTextEdit, QPushButton, QWidget # 引入模块
File "C:\Users\admin\AppData\Roaming\Python\Python39\site-packages\PySide2\__init__.py", line 107, in <module>
_setupQtDirectories()
File "C:\Users\admin\AppData\Roaming\Python\Python39\site-packages\PySide2\__init__.py", line 54, in _setupQtDirectories
for dir in _additional_dll_directories(pyside_package_dir):
File "C:\Users\admin\AppData\Roaming\Python\Python39\site-packages\PySide2\__init__.py", line 26, in _additional_dll_directories
raise ImportError(shiboken2 + ' does not exist')
ImportError: C:\Users\admin\AppData\Roaming\Python\Python39\shiboken2\libshiboken does not exist
安装 shiboken2,发现安装在c盘根目录下,跟之前的操作一样,复制一份到 C:\Users\admin\AppData\Roaming\Python\Python39\site-packages
再运行试试,能跑起来了,再打包试试,又报这个错:
在个人环境变量里新建:
QT_PLUGINS_PATH C:\Users\admin\AppData\Roaming\Python\Python39\site-packages\PySide2\plugins
QT_PLATFORMS_PATH C:\Users\admin\AppData\Roaming\Python\Python39\site-packages\PySide2\plugins\platforms
再次打包,运行,终于出来了:
泪流满面……
精简不必要的代码,最后PortSearchGUI.py代码如下:
# coding=utf-8
from PySide2 import QtGui
from PySide2.QtWidgets import QApplication, QLineEdit, QLabel, QPlainTextEdit, QPushButton, QWidget # 引入模块
from PySide2.QtWidgets import QMessageBox
from PySide2.QtWidgets import QGroupBox, QVBoxLayout, QHBoxLayout # 布局容器
from PySide2.QtCore import Slot # 插槽模块
from PortSearch import PortSearch
import PortText
import threading
import pyperclip
import time
import sys
class PortSearchGUI(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(600, 480) # 主窗体尺寸
self.setWindowTitle('端口嗅探器 v1.0') # 窗体名称
self.ip_line_edit = QLineEdit() # 修改父类
self.ip_line_edit.setPlaceholderText('输入ip或者网址')
self.ip_line_edit.setMinimumSize(180, 22)
self.thread_label = QLabel('并发数:')
self.thread_line_edit = QLineEdit() # 修改父类
self.thread_line_edit.setText('300')
self.thread_line_edit.setMinimumSize(40, 22)
self.port_label = QLabel('端口范围:')
self.port_line_edit1 = QLineEdit() # 修改父类
self.port_line_edit1.setText('0')
self.port_label2 = QLabel('~')
self.port_line_edit2 = QLineEdit() # 修改父类
self.port_line_edit2.setText('65535')
self.report_box_edit = QPlainTextEdit() # 修改父类
self.report_box_edit.setReadOnly(True)
self.start_btn = QPushButton() # 修改父类
self.start_btn.setText('启动')
self.start_btn.clicked.connect(self.collect_data) # 建立连接
self.copy_all_btn = QPushButton() # 修改父类
self.copy_all_btn.setText('复制全文')
self.copy_all_btn.clicked.connect(self.copy_all) # 建立连接
self.copy_ori_btn = QPushButton() # 修改父类
self.copy_ori_btn.setText('复制原文')
self.copy_ori_btn.clicked.connect(self.copy) # 建立连接
self.clear_btn = QPushButton() # 修改父类
self.clear_btn.setText('清空')
self.clear_btn.clicked.connect(self.clear) # 建立连接
self.first_group_box = QGroupBox() # 第一个分组框组控件
self.first_group_box.setTitle('参数设置')
self.second_group_box = QGroupBox() # 第二个分组框组控件
self.second_group_box.setTitle('端口开放情况')
self.first_h_layout = QHBoxLayout() # 第一个横向布局容器,属于第一个分组框控件
self.second_h_layout = QHBoxLayout() # 第二个横向布局容器,属于第一个分组框控件
self.third_h_layout = QHBoxLayout() # 第三个横向布局容器,属于第一个分组框控件
self.first_v_layout = QVBoxLayout() # 第一个纵向布局容器,属于第二个横向布局容器
self.second_v_layout = QVBoxLayout() # 第二个纵向布局容器,属于第二个横向布局容器
self.first_h_layout.addWidget(self.ip_line_edit)
self.first_h_layout.addWidget(self.thread_label)
self.first_h_layout.addWidget(self.thread_line_edit)
self.first_h_layout.addWidget(self.port_label)
self.first_h_layout.addWidget(self.port_line_edit1)
self.first_h_layout.addWidget(self.port_label2)
self.first_h_layout.addWidget(self.port_line_edit2)
self.first_h_layout.addWidget(self.start_btn)
self.first_group_box.setLayout(self.first_h_layout)
self.first_v_layout.addWidget(self.report_box_edit)
self.second_v_layout.addWidget(self.copy_all_btn)
self.second_v_layout.addWidget(self.copy_ori_btn)
self.second_v_layout.addWidget(self.clear_btn)
self.second_h_layout.addItem(self.first_v_layout)
self.second_h_layout.addItem(self.second_v_layout)
self.second_group_box.setLayout(self.second_h_layout)
self.layout = QVBoxLayout()
self.layout.addWidget(self.first_group_box)
self.layout.addWidget(self.second_group_box)
self.setLayout(self.layout)
self.MESSAGE = (
'ip或者网址不得为空',
'并发数不得小于1',
'开始端口号不得小于0',
'结束端口号需要大于开始端口号',
'原文复制成功',
'全文复制成功'
)
self.G_LIST = ['', '']
self.ip = ''
self.thread_line = 0
self.port_start = 0
self.port_end = 0
def show_tip(self, message): # 遇到问题,则丢到这里,抛到界面上
tip = QMessageBox(self)
tip.setWindowTitle('提示')
tip.setText(message)
tip.show()
# 函数B 负责调用逻辑
def logic(self):
search = PortSearch(ip=self.ip, thread_line=self.thread_line, port_start=self.port_start, port_end=self.port_end)
result = search.run()
string = ''
string1 = ''
str_len = len(result)
self.report_box_edit.setPlainText('')
if str_len > 0: # 处理返回数据,输出到编辑框内
for ele in result:
string = string + '端口:' + str(ele[1]) + ' ,状态:开启, 输出信息:' + str(ele[3]) + "\n"
time.sleep(1)
if ele[3] == b'': # 如果没有端口说明,则输出参考端口服务说明
string1 = string1 + '端口:' + str(ele[1]) + ',状态:开启,输出信息:无' + ',参考端口服务说明:' + \
PortText.port_text(ele[1]) + "\n"
else:
string1 = string1 + '端口:' + str(ele[1]) + ',状态:开启,输出信息:' + str(ele[3]) + "\n"
else:
string1 = '所有端口均未开启'
self.G_LIST[0] = string # 保存原文结果
self.G_LIST[1] = string1 # 保存全文结果
self.report_box_edit.setPlainText(string1)
self.start_btn.setEnabled(True)
self.setWindowTitle('端口嗅探器 v1.0')
@Slot()
def collect_data(self):
self.ip = self.ip_line_edit.text()
self.thread_line = int(self.thread_line_edit.text())
self.port_start = int(self.port_line_edit1.text())
self.port_end = int(self.port_line_edit2.text())
if not self.ip:
self.show_tip(self.MESSAGE[0])
return
if self.thread_line < 1:
self.show_tip(self.MESSAGE[1])
return
if self.port_start < 0:
self.show_tip(self.MESSAGE[2])
return
if self.port_end <= self.port_start:
self.show_tip(self.MESSAGE[3])
return
thread = threading.Thread(target=self.logic)
thread.start() # 异步调用,避免工具卡死
self.start_btn.setEnabled(False) # 限制按钮,避免重复调用
self.setWindowTitle('端口嗅探器 v1.0 ---- 执行中')
@Slot()
def copy(self):
pyperclip.copy(self.G_LIST[0])
self.show_tip(self.MESSAGE[4])
@Slot()
def copy_all(self):
pyperclip.copy(self.G_LIST[1])
self.show_tip(self.MESSAGE[5])
@Slot()
def clear(self):
self.report_box_edit.setPlainText('')
app = QApplication(sys.argv) # 创建app
window = PortSearchGUI() # 创建主窗体
window.show() # 显示窗体
app.exec_() # 启动app
sys.exit()
结语
这个系列就写到这里了。
ps: 20210220 今天在其他同事的电脑上试了下,报 计算机中丢失 api-ms-win-core-path-l1-1-0.dll 的报错,查了下原因,需要在该电脑上安装 Visual C++ Redistributable for Visual Studio 2015。