詞云小程序設計并不難埋酬,但在封裝時卻碰到了一個大坑厌漂。歷經(jīng)一個多小時的搜索,問題才得以解決斟珊。做個后記苇倡,以備后用富纸。
預期功能
- 打開任意一個UTF-8編碼的文本文件和一張圖片,能按圖片輪廓生成詞云旨椒;
- 生成詞云時晓褪,能停用一些無用的關鍵詞。
開發(fā)工具
- Python 3.8.6
- PyCharm 2021.1.1
- Qt Designer
- PyUIC
需要加載的庫
- sys 系統(tǒng)內(nèi)置庫
- OpenCV 一款開源的計算機視覺和機器學習軟件庫
- matplotlib 一款 Python 的繪圖庫
- jieba 一款優(yōu)秀的 Python 第三方中文分詞庫
- wordcloud 一款優(yōu)秀的詞云展示第三方庫
- PyQt5 Python中一款優(yōu)秀的GUI界面模塊
UI界面的設計
-
使用Qt Designer進行界面設計和布局综慎。得到界面圖如下:
- 使用擴展工具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", "酷炫詞云 程序設計:王立武"))
self.label.setText(_translate("MainWindow", "酷炫詞云"))
self.label_2.setText(_translate("MainWindow", "程序設計:王立武 2021年6月8日"))
self.label_3.setText(_translate("MainWindow", "打開生成詞云文本"))
self.lineEdit.setPlaceholderText(_translate("MainWindow", "請打開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", "注意:每個關鍵詞之間用空格隔開示惊!"))
self.pushButton_3.setText(_translate("MainWindow", "過濾文本"))
self.pushButton_4.setText(_translate("MainWindow", "生成詞云"))
self.label_5.setText(_translate("MainWindow", "詞云圖片展示區(qū)"))
主程序的建立
- 導入相關的庫好港,代碼如下:
import sys
import cv2 # 導入圖片處理模塊 需安裝庫OpenCV。
import matplotlib.pyplot as plt
import jieba # 導入jieba分詞模塊
import wordcloud # 導入詞云圖模塊
from PyQt5 import QtWidgets, QtGui
from PyQt5.QtWidgets import *
from Txt_Image import Ui_MainWindow # 加載UI界面Txt_Image 米罚,實現(xiàn)UI界面和代碼分離
使用 from Txt_Image import Ui_MainWindow 的目的是為了實現(xiàn)UI界面和代碼的分離钧汹,以便后續(xù)的修改和維護。
- 搭建主程序基本框架录择,相關代碼如下:
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow): # 分離時拔莱,要加載UI界面類
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent) # 調(diào)用父類的構造方法
self.setupUi(self) # 調(diào)用構建子控件的方法
def view_txt(self): # 打開文本文件
pass
def view_image(self): # 打開背景輪廓圖片文件
pass
def filter_key(self): # 過濾不生成圖云的關鍵詞
psss
def show_tuyu(self): # 生成圖云
pass
if __name__ == "__main__":
app = QApplication(sys.argv) # 實例化一個QApplication對象
myWindow = MyWindow() # 實例化自定義的窗口類
myWindow.show() # 展示窗口
sys.exit(app.exec_()) # 啟動事件循環(huán),并將退出碼傳遞給系統(tǒng)退出方法
- 添加信號和槽機制,代碼如下:
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)
-
完善每個槽函數(shù)的功能設計隘竭。
a. 打開文本文件功能設計(view_txt)塘秦。因常見的文本文件編碼格式有幾種方式,在項目中指定打開的編碼方式為UTF-8,因此在打開其他編碼(如:ANSI)時动看,會報錯尊剔,故在代碼中加入異常處理,提醒要求打開的文本文件為UTF-8編碼弧圆。
具體代碼如下:
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,'提示','請選擇一個文本文件赋兵!\n或者將文本文件的編碼改為utf-8')
b. 打開圖片文件功能設計(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. 過濾關鍵詞文本功能設計(filter_key)搔预。具體代碼如下:
def filter_key(self): # 過濾不生成圖云的關鍵詞
global stopwords
stopwords = set('')
filter_values = self.textEdit_2.toPlainText()
update_key = filter_values.split(" ")
# print(update_key)
stopwords.update(update_key)
這個槽函數(shù)中 stopwords 的實際為以后封裝失敗埋下了根源霹期,后文再說。
代碼 update_key = filter_values.split(" ") 的目的拯田,是將用戶在文本編輯框中輸入的停用關鍵詞历造,利用空格作為分隔符生成一個列表,以供在生成詞云時調(diào)用船庇。
d. 生成詞云圖功能設計(show_tuyu)吭产。因用戶有可能在沒有打開文本文件或圖像文件時,程序會報錯鸭轮,所以在這里也加入了異常處理臣淤,以提醒使用者。如圖
具體代碼如下:
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ù)圖片繪制窃爷,則需要設置
font_path='simhei.ttf', # 可以改成自己喜歡的字體
background_color='white', # 詞云圖背景顏色可以換成自己喜歡的顏色
)
else:
wd = wordcloud.WordCloud(
mask=img, # 背景圖形,如果根據(jù)圖片繪制邑蒋,則需要設置
font_path='simhei.ttf', # 可以改成自己喜歡的字體
stopwords=stopwords,
background_color='white', # 詞云圖背景顏色可以換成自己喜歡的顏色
)
wd.generate(word)
plt.imshow(wd)
plt.axis('off') # 關閉顯示x軸姓蜂、y軸下標
# 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, "提示", "請先打開文本文件和輪廓圖片文件后\n再運行生成詞云!")
因為這只是一個練手的項目医吊,所以沒有過多去考慮把生成的詞云圖片都保存下來的想法钱慢,只是簡單的寫了一句保存圖片代碼:
plt.savefig('.\images\chiyu.png', dpi=100)
實際上,在這里利用獲取生成圖片的當前日期和時間為文件名卿堂,更為妥當束莫。這個代碼,這里就不給出了草描。
測試運行
-
效果圖如下:
封裝小程序
前面的工作都很順利览绿,沒有遇到什么麻煩,但封裝時卻碰到了一個大問題陶珠。
回到命令行環(huán)境挟裂,并轉(zhuǎn)到相應的路徑下,運行如下代碼開始封裝:
pyinstaller -F -w Tuyun.py # Tuyun.py為主程序文件名
封裝完成揍诽,沒有報錯诀蓉,本以為大功告成了,但在運行程序時暑脆,卻報錯了渠啤,如下圖:
出這種錯的原因,大多是外加的資源文件沒有添加到生成EXE文件的目錄中添吗,但此程序沒有加入圖標等資源文件沥曹。原因到底在哪呢?
通過某娘搜索得知出在詞云庫 wordcloud 中碟联,解決辦法:
- 找到詞云庫 wordcloud 的安裝目錄妓美,將文件夾中的 stopwords 文件拷貝到生成EXE的文件夾中。
- 找到詞云庫 wordcloud 的安裝目錄鲤孵,打開文件夾中的 wordcloud.py 文件壶栋,找到第33行:
FILE = os.path.dirname(__file__)
將這句代碼改為:
FILE = os.path.dirname(sys.executable)
建議在做此步操作時,對原文件做好備份普监,以防不測贵试。完成這些操作后,再次進行封裝凯正,運行毙玻,終于完成了。