詞云小程序設(shè)計(jì)并不難,但在封裝時(shí)卻碰到了一個(gè)大坑。歷經(jīng)一個(gè)多小時(shí)的搜索,問題才得以解決。做個(gè)后記,以備后用。
預(yù)期功能
- 打開任意一個(gè)UTF-8編碼的文本文件和一張圖片,能按圖片輪廓生成詞云;
- 生成詞云時(shí),能停用一些無用的關(guān)鍵詞。
開發(fā)工具
- Python 3.8.6
- PyCharm 2021.1.1
- Qt Designer
- PyUIC
需要加載的庫(kù)
- sys 系統(tǒng)內(nèi)置庫(kù)
- OpenCV 一款開源的計(jì)算機(jī)視覺和機(jī)器學(xué)習(xí)軟件庫(kù)
- matplotlib 一款 Python 的繪圖庫(kù)
- jieba 一款優(yōu)秀的 Python 第三方中文分詞庫(kù)
- wordcloud 一款優(yōu)秀的詞云展示第三方庫(kù)
- PyQt5 Python中一款優(yōu)秀的GUI界面模塊
UI界面的設(shè)計(jì)
-
使用Qt Designer進(jìn)行界面設(shè)計(jì)和布局。得到界面圖如下:
UI界面圖
- 使用擴(kuò)展工具PyUIC將UI界面文件Txt_Image.ui轉(zhuǎn)為Txt_Image.py文件。代碼如下:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'Txt_Image.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(958, 758)
MainWindow.setMinimumSize(QtCore.QSize(800, 600))
MainWindow.setMaximumSize(QtCore.QSize(16777215, 16777215))
MainWindow.setStyleSheet("font: 10pt \"微軟雅黑\";")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.formLayout = QtWidgets.QFormLayout(self.centralwidget)
self.formLayout.setObjectName("formLayout")
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
self.verticalLayout_3.setObjectName("verticalLayout_3")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setStyleSheet("font: 57 40pt \"李旭科書法 v1.4\";\n"
"color: rgb(85, 170, 0);")
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.verticalLayout_3.addWidget(self.label)
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName("label_2")
self.verticalLayout_3.addWidget(self.label_2)
self.verticalLayout_2.addLayout(self.verticalLayout_3)
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setTitle("")
self.groupBox.setAlignment(QtCore.Qt.AlignCenter)
self.groupBox.setObjectName("groupBox")
self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox)
self.horizontalLayout_3.setObjectName("horizontalLayout_3")
self.gridLayout = QtWidgets.QGridLayout()
self.gridLayout.setObjectName("gridLayout")
self.label_3 = QtWidgets.QLabel(self.groupBox)
self.label_3.setObjectName("label_3")
self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1)
self.lineEdit = QtWidgets.QLineEdit(self.groupBox)
self.lineEdit.setObjectName("lineEdit")
self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
self.pushButton = QtWidgets.QPushButton(self.groupBox)
self.pushButton.setObjectName("pushButton")
self.gridLayout.addWidget(self.pushButton, 0, 2, 1, 1)
self.label_4 = QtWidgets.QLabel(self.groupBox)
self.label_4.setObjectName("label_4")
self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
self.lineEdit_2 = QtWidgets.QLineEdit(self.groupBox)
self.lineEdit_2.setObjectName("lineEdit_2")
self.gridLayout.addWidget(self.lineEdit_2, 1, 1, 1, 1)
self.pushButton_2 = QtWidgets.QPushButton(self.groupBox)
self.pushButton_2.setObjectName("pushButton_2")
self.gridLayout.addWidget(self.pushButton_2, 1, 2, 1, 1)
self.horizontalLayout_3.addLayout(self.gridLayout)
self.verticalLayout_2.addWidget(self.groupBox)
self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox_2.setTitle("")
self.groupBox_2.setObjectName("groupBox_2")
self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2)
self.gridLayout_2.setObjectName("gridLayout_2")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.textEdit = QtWidgets.QTextEdit(self.groupBox_2)
self.textEdit.setObjectName("textEdit")
self.verticalLayout.addWidget(self.textEdit)
self.textEdit_2 = QtWidgets.QTextEdit(self.groupBox_2)
self.textEdit_2.setObjectName("textEdit_2")
self.verticalLayout.addWidget(self.textEdit_2)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.pushButton_3 = QtWidgets.QPushButton(self.groupBox_2)
self.pushButton_3.setObjectName("pushButton_3")
self.horizontalLayout.addWidget(self.pushButton_3)
self.pushButton_4 = QtWidgets.QPushButton(self.groupBox_2)
self.pushButton_4.setObjectName("pushButton_4")
self.horizontalLayout.addWidget(self.pushButton_4)
self.verticalLayout.addLayout(self.horizontalLayout)
self.verticalLayout.setStretch(0, 4)
self.verticalLayout.setStretch(1, 1)
self.horizontalLayout_2.addLayout(self.verticalLayout)
self.widget = QtWidgets.QWidget(self.groupBox_2)
self.widget.setObjectName("widget")
self.gridLayout_3 = QtWidgets.QGridLayout(self.widget)
self.gridLayout_3.setObjectName("gridLayout_3")
self.label_5 = QtWidgets.QLabel(self.widget)
self.label_5.setMaximumSize(QtCore.QSize(16777215, 16777215))
self.label_5.setScaledContents(False)
self.label_5.setAlignment(QtCore.Qt.AlignCenter)
self.label_5.setObjectName("label_5")
self.gridLayout_3.addWidget(self.label_5, 0, 0, 1, 1)
self.horizontalLayout_2.addWidget(self.widget)
self.horizontalLayout_2.setStretch(0, 1)
self.horizontalLayout_2.setStretch(1, 2)
self.gridLayout_2.addLayout(self.horizontalLayout_2, 0, 0, 1, 1)
self.verticalLayout_2.addWidget(self.groupBox_2)
self.formLayout.setLayout(0, QtWidgets.QFormLayout.SpanningRole, self.verticalLayout_2)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.label_3.setBuddy(self.lineEdit)
self.label_4.setBuddy(self.lineEdit_2)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "酷炫詞云 程序設(shè)計(jì):王立武"))
self.label.setText(_translate("MainWindow", "酷炫詞云"))
self.label_2.setText(_translate("MainWindow", "程序設(shè)計(jì):王立武 2021年6月8日"))
self.label_3.setText(_translate("MainWindow", "打開生成詞云文本"))
self.lineEdit.setPlaceholderText(_translate("MainWindow", "請(qǐng)打開UTF-8編碼的TXT格式的文本文件"))
self.pushButton.setText(_translate("MainWindow", "瀏覽"))
self.label_4.setText(_translate("MainWindow", "打開詞云輪廓圖片"))
self.lineEdit_2.setPlaceholderText(_translate("MainWindow", "建議使用白底的圖片以得至更好的輪廓詞云圖"))
self.pushButton_2.setText(_translate("MainWindow", "瀏覽"))
self.textEdit.setPlaceholderText(_translate("MainWindow", "在這里粘貼的文字不能生成詞云!"))
self.textEdit_2.setPlaceholderText(_translate("MainWindow", "注意:每個(gè)關(guān)鍵詞之間用空格隔開!"))
self.pushButton_3.setText(_translate("MainWindow", "過濾文本"))
self.pushButton_4.setText(_translate("MainWindow", "生成詞云"))
self.label_5.setText(_translate("MainWindow", "詞云圖片展示區(qū)"))
主程序的建立
- 導(dǎo)入相關(guān)的庫(kù),代碼如下:
import sys
import cv2 # 導(dǎo)入圖片處理模塊 需安裝庫(kù)OpenCV。
import matplotlib.pyplot as plt
import jieba # 導(dǎo)入jieba分詞模塊
import wordcloud # 導(dǎo)入詞云圖模塊
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import *
from Txt_Image import Ui_MainWindow # 加載UI界面Txt_Image ,實(shí)現(xiàn)UI界面和代碼分離
使用 from Txt_Image import Ui_MainWindow 的目的是為了實(shí)現(xiàn)UI界面和代碼的分離,以便后續(xù)的修改和維護(hù)。
- 搭建主程序基本框架,相關(guān)代碼如下:
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow): # 分離時(shí),要加載UI界面類
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent) # 調(diào)用父類的構(gòu)造方法
self.setupUi(self) # 調(diào)用構(gòu)建子控件的方法
def view_txt(self): # 打開文本文件
pass
def view_image(self): # 打開背景輪廓圖片文件
pass
def filter_key(self): # 過濾不生成圖云的關(guān)鍵詞
psss
def show_tuyu(self): # 生成圖云
pass
if __name__ == "__main__":
app = QApplication(sys.argv) # 實(shí)例化一個(gè)QApplication對(duì)象
myWindow = MyWindow() # 實(shí)例化自定義的窗口類
myWindow.show() # 展示窗口
sys.exit(app.exec_()) # 啟動(dòng)事件循環(huán),并將退出碼傳遞給系統(tǒng)退出方法
- 添加信號(hào)和槽機(jī)制,代碼如下:
self.pushButton.clicked.connect(self.view_txt)
self.pushButton_2.clicked.connect(self.view_image)
self.pushButton_3.clicked.connect(self.filter_key)
self.pushButton_4.clicked.connect(self.show_tuyu)
-
完善每個(gè)槽函數(shù)的功能設(shè)計(jì)。
a. 打開文本文件功能設(shè)計(jì)(view_txt)。因常見的文本文件編碼格式有幾種方式,在項(xiàng)目中指定打開的編碼方式為UTF-8,因此在打開其他編碼(如:ANSI)時(shí),會(huì)報(bào)錯(cuò),故在代碼中加入異常處理,提醒要求打開的文本文件為UTF-8編碼。
編碼格式不對(duì)時(shí)的提示對(duì)話框
具體代碼如下:
def view_txt(self): # 打開文本文件
try:
global file_content
file_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "選取文件", "./", "*.txt")
self.lineEdit.setText(file_name)
file_content = open(file_name, "r",encoding='utf-8').read()
self.textEdit.setText(file_content)
# print(file_content)
except:
QMessageBox.critical(self,'提示','請(qǐng)選擇一個(gè)文本文件!\n或者將文本文件的編碼改為utf-8')
b. 打開圖片文件功能設(shè)計(jì)(view_image)。具體代碼如下:
def view_image(self): # 打開背景輪廓圖片文件
global image_name
image_name, _ = QtWidgets.QFileDialog.getOpenFileName(self, "選取文件", "./", "*.png;;*.jpg;;*.jpeg")
self.lineEdit_2.setText(image_name)
c. 過濾關(guān)鍵詞文本功能設(shè)計(jì)(filter_key)。具體代碼如下:
def filter_key(self): # 過濾不生成圖云的關(guān)鍵詞
global stopwords
stopwords = set('')
filter_values = self.textEdit_2.toPlainText()
update_key = filter_values.split(" ")
# print(update_key)
stopwords.update(update_key)
這個(gè)槽函數(shù)中 stopwords 的實(shí)際為以后封裝失敗埋下了根源,后文再說。
代碼 update_key = filter_values.split(" ") 的目的,是將用戶在文本編輯框中輸入的停用關(guān)鍵詞,利用空格作為分隔符生成一個(gè)列表,以供在生成詞云時(shí)調(diào)用。
d. 生成詞云圖功能設(shè)計(jì)(show_tuyu)。因用戶有可能在沒有打開文本文件或圖像文件時(shí),程序會(huì)報(bào)錯(cuò),所以在這里也加入了異常處理,以提醒使用者。如圖

具體代碼如下:
def show_tuyu(self): # 生成圖云
try:
cut_text = jieba.cut(file_content)
word = ' '.join(cut_text)
img = cv2.imread(image_name) # 讀取數(shù)據(jù)
if self.textEdit_2.toPlainText() == "":
wd = wordcloud.WordCloud(
mask=img, # 背景圖形,如果根據(jù)圖片繪制,則需要設(shè)置
font_path='simhei.ttf', # 可以改成自己喜歡的字體
background_color='white', # 詞云圖背景顏色可以換成自己喜歡的顏色
)
else:
wd = wordcloud.WordCloud(
mask=img, # 背景圖形,如果根據(jù)圖片繪制,則需要設(shè)置
font_path='simhei.ttf', # 可以改成自己喜歡的字體
stopwords=stopwords,
background_color='white', # 詞云圖背景顏色可以換成自己喜歡的顏色
)
wd.generate(word)
plt.imshow(wd)
plt.axis('off') # 關(guān)閉顯示x軸、y軸下標(biāo)
# plt.show()
plt.savefig('.\images\chiyu.png', dpi=100)
jpg = QtGui.QPixmap("./images/chiyu.png")
self.label_5.setScaledContents(True)
self.label_5.setPixmap(jpg)
except:
QMessageBox.critical(self, "提示", "請(qǐng)先打開文本文件和輪廓圖片文件后\n再運(yùn)行生成詞云!")
因?yàn)檫@只是一個(gè)練手的項(xiàng)目,所以沒有過多去考慮把生成的詞云圖片都保存下來的想法,只是簡(jiǎn)單的寫了一句保存圖片代碼:
plt.savefig('.\images\chiyu.png', dpi=100)
實(shí)際上,在這里利用獲取生成圖片的當(dāng)前日期和時(shí)間為文件名,更為妥當(dāng)。這個(gè)代碼,這里就不給出了。
測(cè)試運(yùn)行
-
效果圖如下:
測(cè)試運(yùn)行效果圖
封裝小程序
前面的工作都很順利,沒有遇到什么麻煩,但封裝時(shí)卻碰到了一個(gè)大問題。
回到命令行環(huán)境,并轉(zhuǎn)到相應(yīng)的路徑下,運(yùn)行如下代碼開始封裝:
pyinstaller -F -w Tuyun.py # Tuyun.py為主程序文件名
封裝完成,沒有報(bào)錯(cuò),本以為大功告成了,但在運(yùn)行程序時(shí),卻報(bào)錯(cuò)了,如下圖:

出這種錯(cuò)的原因,大多是外加的資源文件沒有添加到生成EXE文件的目錄中,但此程序沒有加入圖標(biāo)等資源文件。原因到底在哪呢?
通過某娘搜索得知出在詞云庫(kù) wordcloud 中,解決辦法:
- 找到詞云庫(kù) wordcloud 的安裝目錄,將文件夾中的 stopwords 文件拷貝到生成EXE的文件夾中。
- 找到詞云庫(kù) wordcloud 的安裝目錄,打開文件夾中的 wordcloud.py 文件,找到第33行:
FILE = os.path.dirname(__file__)
將這句代碼改為:
FILE = os.path.dirname(sys.executable)
建議在做此步操作時(shí),對(duì)原文件做好備份,以防不測(cè)。完成這些操作后,再次進(jìn)行封裝,運(yùn)行,終于完成了。


