最近想写个小demo,使用python实现文章的词频统计,并完成词云图的绘制,然后需要具有交互界面,并且能够在没有python环境的电脑下运行,方便不懂编程的人直接使用。
支持以下功能,运动结果如下(界面虽丑,五味俱全,毕竟没有美工 =_=!!):
全部代码实现的打包exe文件:hudongloop-WordCloudAnalysis(下载后直接双击exe即可,在win7和win10测简单测试过,可以直接运行)
主要使用的库和软件如下:
注意,默认上述环境和库以安装完毕!
###界面结构设计
首先界面如下:
界面使用pyqt4实现,主要要如下功能:
###功能实现
每个功能主要分为2部分,算法实现和事件绑定
####打开
读入.txt格式文件,由于文件有不同的编码方式,因此需要对读入的字符进行解码处理。主要代码如下:
def OpenData(self):
fname = QtGui.QFileDialog.getOpenFileName(self)
with open(fname, 'r') as f:
self.__data = f.read().replace('\n','').replace(' ','')
# show data in window
if self.__data != "":
sample_str = self.__data[:10] if len(self.__data) > 10 else self.__data
type = chardet.detect(sample_str)
try:
self.inputData.setText(self.__data.decode(type["encoding"]))
finally:
self.inputData.setText(self.__data.decode('gb18030'))
使用import chardet
库对字符编码方式进行识别,可以针对不同的编码格式进行处理(但是对于编码中有特殊字符的处理不好!)
然后PyQt进行事件关联:
self.openData.clicked.connect(self.OpenData)
其中,openData
为打开按钮的ID,OpenData
为实现打开按钮的方法。
####分析
该按钮主要实现对读入的数据进行处理,最后以词频的方式显示出来,需要使用到jieba
进行分词,使用nltk进行词频统计,主要代码如下:
def AnalysisWord(self):
# analysis text data
lztext = self.fontsTools.getText(self.__data)
tokenstr = nltk.word_tokenize(lztext)
self.__resultFDOrigin = nltk.FreqDist(tokenstr)
self.outputResult.setText(self.DealResult())
def DealResult(self):
# deal with result
listKVL = [] #save key val flag
resultFDDelete = nltk.FreqDist() # delete result data of freq dist
orderFdist = enumerate(sorted(self.__resultFDOrigin.iteritems(), key=lambda x: (x[1], x[0]), reverse=True))
for index, (key, val) in orderFdist:
words = pseg.cut(key)
for w in words:
if (w.flag in self.__poSpeech) and (val > self.__maxFreq):
listKVL.append(key)
listKVL.append(str(val))
listKVL.append(w.flag)
listKVL.append('\n')
else:
resultFDDelete.setdefault(key, val)
break # forced end
# show progress
self.ShowProgress((index+1)/len(self.__resultFDOrigin)*100)
self.__resultFDCurrent = self.__resultFDOrigin - resultFDDelete
return '\t\t'.join(listKVL).replace('\t\t\n\t\t','\n')
首先调用fontsTools.getText()
方法进行分析,该方法主要使用jieba
进行处理,完整代码见WordCloudAnalysis。DealResult()
方法主要实现结果的筛选(词频、词性),把该方法独立出来是方便之后不同参数选择时可以调用。
然后PyQt进行事件关联:
self.analysisWord.clicked.connect(self.AnalysisWord)
其中,analysisWord
为打开按钮的ID,AnalysisWord
为实现打开按钮的方法。
####设置
设置栏里有词频选择,使用Spin Box
组件实现。词性选择,使用Check Box
组件实现。
词频选择和词性选择实现比较简单,但是对于不同的词性进行分类比较繁琐。详细代码见WordCloudAnalysis
####保存
保存按钮只需要把显示结果框中的内容保存下来就好:
def SaveResult(self):
fname = QtGui.QFileDialog.getSaveFileName(self, filter="Text Files (*.txt);;All Files (*)")
with open(fname, 'w') as f:
f.write(self.outputResult.toPlainText())
####云图
使用wordcloud
库把处理后词频绘制云图即可,这也是为什么需要一直维护词频的原因,方便绘制云图:
def CalculationCloud(self):
font = r'simfang.ttf'
my_wordcloud = WordCloud(collocations=False, font_path=font, width=1400, height=1400,
margin=2).generate_from_frequencies(self.__resultFDCurrent)
plt.imshow(my_wordcloud)
plt.axis("off")
plt.show()
####进度条
实现也非常简单,使用Progress Bar
控件,然后传入参数设置即可:
def ShowProgress(self, value):
self.progressAnalysis.setValue(value)
###pyinstaller打包
以上功能对于一个程序员来说是非常容易的,但是对于不懂编程的来说,就比较困难,特别是其电脑没用相关环境,无法运行(有环境也不会来折腾个界面了_!)。因此使用pyinstaller进行打包处理,使其在仍和电脑上可以运行。
pyinstaller的安装和使用不多说了,见官方文档:pyinstaller manual
打包时代码也简单:
pyinstaller --onefile xxx.py
打包是简单,但是当你使用较多的其他库时,会发生一大堆问题,同时也是打包一时爽,打开5分钟。主要遇到的问题和解决方法接下来指出。
###pyinstaller打包问题处理
问题主要是针对pyinstaller对jieba
、wordcloud
、nltk
库打包时出现的问题。
1. No such or directory: u’C:\user\LOOP\AppData\Local\Temp\_MEI12~1\jieba\dict.txt’
这是用于jieba
中调用了dict.txt
文件,而pyinstaller在打包时不会自动把该文件打包,定位到出现错误的文件Lib\site-packages\jieba\_compat.py
的8行,可以看到如下:
try:
import pkg_resources
get_module_res = lambda *res: pkg_resources.resource_stream(__name__,
os.path.join(*res))
except ImportError:
get_module_res = lambda *res: open(os.path.normpath(os.path.join(
os.getcwd(), os.path.dirname(__file__), *res)), 'rb')
该方法就是在当前文件 (_compat.py) 目录作为get_module_res
的目录,在pyinstaller manual文档中解释如下:
也就是说使用__file__
的相对路径在打包完成后会变成绝对路径,并且以_MEIxxxxx
文件名存储在缓存文件夹中。并且也给出解决方案,可以使用sys.executable
也就是当前的运行目录,就是运行打包好的exe文件目录,如果是在使用python时,就是调用的python路径。
因此,解决方法如下:
try:
import pkg_resources
get_module_res = lambda *res: open(os.path.normpath(os.path.join(
os.getcwd(), os.path.dirname(sys.executable), *res)), 'rb')
except ImportError:
get_module_res = lambda *res: open(os.path.normpath(os.path.join(
os.getcwd(), os.path.dirname(__file__), *res)), 'rb')
同理,在打包wordcloud
也会出现相似的问题,定位到相关文件(Lib\site-packages\wordcloud\wordcloud.py
)第29行:
FONT_PATH = os.environ.get("FONT_PATH", os.path.join(os.path.dirname(__file__),
"DroidSansMono.ttf"))
STOPWORDS = set([x.strip() for x in open(
os.path.join(os.path.dirname(__file__), 'stopwords')).read().split('\n')])
很明显是__file__
问题,修改如下:
FONT_PATH = os.environ.get("FONT_PATH", os.path.join(os.path.dirname(sys.executable),
"DroidSansMono.ttf"))
STOPWORDS = set([x.strip() for x in open(
os.path.join(os.path.dirname(sys.executable), 'stopwords')).read().split('\n')])
注意:以上修改只是针对使用pyinstaller打包时,因为改变了其文件路径,在调试和运行时会出错
最后把dict.txt
和stopwords
文件拷贝到与生成的.exe文件同一目录即可。
2. Please use the NLTK Downloader to obtain the resource:
该错误是由于使用了nltk_data()
, 打包时也没有自动对其打包,因此增加nltk_data
的搜错路径,然后把该问价复制到该路径下即可。
nltk.data.path.append('./nltk_data')
就是以当前.exe目录为搜索路径,把nltk_data
放在当前目录即可。
3. Inter MKL FATAL ERROR:Cannot load mk2_avx.dll or mk2_def.dll
缺少这两个动态链接库,复制过来即可。