本文将利用PyQt5+Java+Apache Tika实现简单的文档内容提取程序。该程序使用PyQt5完成界面,后台使用python调用jar包的方法使调用Apache Tika库读取文件元数据和正文内容,并显示在文本框中。界面效果如下:
步骤为:
开发工具:
博主初次接触博客,文章难免有疏漏之处,如有错误,欢迎留言批评指正,感激不尽!
创建python项目,此处我将其命名为了codeChecker。
为了方便使用,此处我选择了使用Qt Designer和PyUIC工具来实现界面。其中Qt Designer可以通过简单的拖拽实现界面组件的设计,并生成codeChecker.ui文件,再利用PyUIC将codeChecker.ui文件转为codeChecker.py文件,无需编写任何代码,即可实现GUI的设计。重点是,利用这一方法可以使得前端界面与后端逻辑分离开来,。这种界面实现方法十分适合本例中的小项目。
Qt Designer工作界面:
使用PyUIC自动生成的该部分代码:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'codeChecker.ui'
#
# Created by: PyQt5 UI code generator 5.15.1
#
# 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_fileReader(object):
def setupUi(self, fileReader):
fileReader.setObjectName("fileReader")
fileReader.resize(800, 758)
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap("resource/文件夹.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
fileReader.setWindowIcon(icon)
self.centralwidget = QtWidgets.QWidget(fileReader)
self.centralwidget.setObjectName("centralwidget")
self.horizontalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(50, 30, 691, 80))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.horizontalLayout = QtWidgets.QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout.setObjectName("horizontalLayout")
self.filelabel = QtWidgets.QLabel(self.horizontalLayoutWidget)
self.filelabel.setObjectName("filelabel")
self.horizontalLayout.addWidget(self.filelabel)
self.filepath = QtWidgets.QLineEdit(self.horizontalLayoutWidget)
self.filepath.setReadOnly(True)
self.filepath.setObjectName("filepath")
self.horizontalLayout.addWidget(self.filepath)
self.filechooser = QtWidgets.QToolButton(self.horizontalLayoutWidget)
self.filechooser.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
self.filechooser.setIcon(icon)
self.filechooser.setObjectName("filechooser")
self.horizontalLayout.addWidget(self.filechooser)
self.ensure = QtWidgets.QPushButton(self.horizontalLayoutWidget)
self.ensure.setObjectName("ensure")
self.horizontalLayout.addWidget(self.ensure)
self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
self.verticalLayoutWidget.setGeometry(QtCore.QRect(50, 130, 691, 271))
self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.metadatalabel = QtWidgets.QLabel(self.verticalLayoutWidget)
self.metadatalabel.setObjectName("metadatalabel")
self.verticalLayout.addWidget(self.metadatalabel)
self.metadatatext = QtWidgets.QTextEdit(self.verticalLayoutWidget)
self.metadatatext.setReadOnly(True)
self.metadatatext.setObjectName("metadatatext")
self.verticalLayout.addWidget(self.metadatatext)
self.verticalLayoutWidget_2 = QtWidgets.QWidget(self.centralwidget)
self.verticalLayoutWidget_2.setGeometry(QtCore.QRect(50, 420, 691, 271))
self.verticalLayoutWidget_2.setObjectName("verticalLayoutWidget_2")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget_2)
self.verticalLayout_2.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.contentlabel = QtWidgets.QLabel(self.verticalLayoutWidget_2)
self.contentlabel.setObjectName("contentlabel")
self.verticalLayout_2.addWidget(self.contentlabel)
self.contenttext = QtWidgets.QTextEdit(self.verticalLayoutWidget_2)
self.contenttext.setReadOnly(True)
self.contenttext.setObjectName("contenttext")
self.verticalLayout_2.addWidget(self.contenttext)
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(50, 90, 241, 16))
self.label.setObjectName("label")
fileReader.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(fileReader)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
self.menubar.setObjectName("menubar")
fileReader.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(fileReader)
self.statusbar.setObjectName("statusbar")
fileReader.setStatusBar(self.statusbar)
self.retranslateUi(fileReader)
QtCore.QMetaObject.connectSlotsByName(fileReader)
def retranslateUi(self, fileReader):
_translate = QtCore.QCoreApplication.translate
fileReader.setWindowTitle(_translate("fileReader", "文件内容读取器"))
self.filelabel.setText(_translate("fileReader", "文件路径:"))
self.filechooser.setToolTip(_translate("fileReader", "选择一个文件"))
self.filechooser.setText(_translate("fileReader", "..."))
self.ensure.setText(_translate("fileReader", "开始读取"))
self.metadatalabel.setText(_translate("fileReader", "文件元数据:"))
self.contentlabel.setText(_translate("fileReader", "文件内容:"))
self.label.setText(_translate("fileReader", "支持.pdf/.doc/.docx/.txt格式"))
Qt Designer和PyUIC可以集成在PyCharm上使用,关于安装方法与使用教程这里不再赘述,附上几篇优质文章供大家参考学习:
PyCharm安装配置Qt Designer+PyUIC教程
python PYQT5编写UI画面并实现前后台分离
PyQT5速成教程-2 Qt Designer介绍与入门
有关Apache Tika的介绍和Apache Tika GUI app的安装使用可以参考我前一篇文章:
Apache Tika GUI:tika-app安装及使用体验
在这里我们不再使用Apache Tika的GUI app实现文件内容读取,而是利用Apache Tika提供的Java库来实现相应功能。
首先下载tika-app jar包,下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/tika/
这里我选择的版本是tika-app-2.0.0-ALPHA.jar。
接下来,创建Java项目,此处我将其命名为tikaTest,将刚刚下载好的jar包导入到项目中。这里介绍一种导入jar包的方式:
然后我们就可以编写利用tika读取文本内容的类了。创建类Checker,其中只有一个静态方法check,该方法接收一个字符串对象,表示要读取的文件地址。返回值是一个字符串数组,其中第一个字符串表示元数据,第二个字符串表示文件正文内容。
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.SAXException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class Checker {
public static String[] check(String fileAddr) throws IOException, TikaException, SAXException {
//新建File对象
File file = new File(fileAddr);
//读入文件
FileInputStream inputStream = new FileInputStream(file);
//创建内容处理器对象
BodyContentHandler handler = new BodyContentHandler();
//创建元数据对象
Metadata metadata = new Metadata();
//创建内容解析器对象
ParseContext parseContext = new ParseContext();
//实例化Parser对象
Parser parser = new AutoDetectParser();
//调用parse()方法解析文件
parser.parse(inputStream, handler, metadata, parseContext);
// 获取元数据信息
String metadataString = "";
for (String name : metadata.names()) {
metadataString += name + ":" + metadata.get(name) + "\n";
}
// 将元数据信息和文件内容信息加入数组
String[] results = new String[2];
results[0] = metadataString;
results[1] = handler.toString();
return results;
}
}
为了能够使python调用刚才完成的Java程序,我们需要将刚才的Java项目打包成jar包。打包方法为:
点击Artifacts->上方“+”->JAR->From modules with dependencies
点击OK
其中的Module:tikaTest是本项目项目名,因为本项目只有一个类Checker且该类并无main方法,所以无需指定Main Class,直接点击OK即可。
在IDEA上方菜单栏点击Build->Build Artifacts
在弹出来的Build Artifact中点击Build。
此时等待片刻,就可以在刚才指定的目录下看到打包好的jar包。为了方便使用,可以直接将jar包复制到python项目目录中,或是直接将jar包输出路径设置为python项目目录。。
最后一步,使用python编写后台程序,使得前端界面可以调用Java程序实现文件内容读取功能。首先在刚才创建好的python项目codeChecker中创建main.py作为程序的入口。在main.py中需要实现两件事:
创建类Start,其继承QMainWindow类和codeChecker.Ui_fileReader类,其中codeChecker.Ui_fileReader是第一部分自动生成的界面类,为了实现前后端的分类,新类需要继承该类,以直接操控GUI组件实现交互。
类Start有两个属性:
在__init__方法中,需要设置UI才能显示界面,此外要完成点击界面上的按钮发生事件,需要绑定两个按钮组件到特定的函数:
def __init__(self):
QMainWindow.__init__(self)
codeChecker.Ui_fileReader.__init__(self)
self.setupUi(self)
# 设置两个按钮的事件监听
self.filechooser.clicked.connect(self.open_file)
self.ensure.clicked.connect(self.choose)
其中self.filechooser是GUI上的文件选择按钮,self.ensure是GUI上的读取按钮,clicked表示点击该按钮这个事件,两个按钮事件分别与self.open_file,self.choose两个方法连接,代表点击按钮后将执行相应的方法。其中self.open_file,self.choose两个方法需要在Start类中实现。
接下来实现两个处理按钮点击事件的方法。
首先是打开点击打开文件按钮时的调用方法open_file()。该方法需要调用QtWidgets.QFileDialog.getOpenFileName()方法来打开文件选择器,其中第二个参数"选取文件"为打开的文件选择器的名称,第三个参数为默认的文件目录,此处设置为os.getcwd(),即项目当前工作目录,第四个参数为文件类型过滤器,在这里可以设置可选择的文件格式类型,此出我设置为.pdf/.txt/.docx/.doc,格式为类型名(*.xxx),其中“.xxx”代表格式后缀,两个文件类型之间需要用“;;”隔开,同一文件类型的两种格式需要用空格隔开,例如Word Files(*.docx *.doc)。
# 打开文件选择器的事件方法
def open_file(self):
# QtWidgets.QFileDialog.getOpenFileName()方法返回一个元组,分别为文件路径和文件类型
self.fileName, self.fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", os.getcwd(), # os.getcwd用于返回当前工作目录
"PDF Files(*.pdf);;Text Files(*.txt);;Word Files(*.docx *.doc)") # 设置文件类型过滤器,即设置文件可选格式
self.filepath.setText(self.fileName)# 在文本框中显示文件路径
self.filepath是显示文件路径的单行文本框,setText可以设置文本框内的内容。
其次是读取文件的方法choose()。
# 开始读取文件的方法
def choose(self):
# 如果没有选择文件,弹出提示框
if self.filepath.text() == '':
msg_box = QMessageBox(QMessageBox.Warning, '警告', '请先选择文件!')
msg_box.exec_()
else: # 如果选择了文件
# 读取文件信息的jar包
jarpath = r'F:/python_codes/codeChecker/tikaTest.jar'
# 开启JVM 注意每个人的JVM所在路径不同
if not jpype.isJVMStarted():
jpype.startJVM(r"C:/Program Files/Java/jdk1.8.0_202/jre/bin/server/jvm.dll", "-ea",
"-Djava.class.path=%s" % jarpath)
tkClass = jpype.JClass("Checker") # Checker是自己的类名
tk = tkClass()
results = tk.check(self.fileName) # 调用check()方法读取文本内容,返回值为java.lang.String数组
# 将结果展示在文本框中,注意要将java.lang.String类型转换成string才能显示,否则程序会崩溃
self.metadatatext.setText(str(results[0]))
self.contenttext.setText(str(results[1]))
# 关闭虚拟机
if jpype.isJVMStarted():
jpype.shutdownJVM()
首先通过if-else判断用户是否选择了文件,如果没有,则直接弹出提示框提醒用户,否则,继续向下执行。
如过用户选择了文件,则可以着手调用Java程序了,此过程需要用到jpype库,注意安装此库时需要指定的库名为jpype1,而调用时依然使用jpype。
找到本机JVM安装路径,我的是C:/Program Files/Java/jdk1.8.0_202/jre/bin/server/JVM.dll,可以参考找到自己的安装路径,然后利用jpype.startJVM()方法打开JVM,并调用jar包,此处jarpath是上文中打包的jar包位置。然后使用jpype.JClass()方法获取Java类Checker对象,并调用方法check(),得到结果。最后在两个文本框self.metadatatext和self.contenttext上显示出来。**注意,调用方法check()所得到的数组字符串类型为java.lang.String,而非python所使用的string类型,需要使用str()函数将其进行类型转化才能在文本框中显示!**最后,关闭JVM。
最后我们在main.py中编写一个__main__模块来运行程序。
# 程序入口
if __name__ == '__main__':
app = QApplication(sys.argv)
s = Start()
s.show()
sys.exit(app.exec_())
运行程序,显示界面,点击选择文件按钮,选择一个PDF文件
点击开始读取,控制台会输出调用tika的信息。
读取结果也会显示在文本框中了。
main.py完整代码:
import os
import sys
import jpype
import os.path
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
from PyQt5 import QtWidgets
import codeChecker
# 新类继承UI类,使得前后端分离,更改UI界面时无需再更改后台逻辑
class Start(QMainWindow, codeChecker.Ui_fileReader):
# 读取的文件的两个属性,文件名和文件类型
fileName = ''
fileType = ''
def __init__(self):
QMainWindow.__init__(self)
codeChecker.Ui_fileReader.__init__(self)
self.setupUi(self)
# 设置两个按钮的事件监听
self.filechooser.clicked.connect(self.open_file)
self.ensure.clicked.connect(self.choose)
# 打开文件选择器的事件方法
def open_file(self):
# QtWidgets.QFileDialog.getOpenFileName()方法返回一个元组,分别为文件路径和文件类型
self.fileName, self.fileType = QtWidgets.QFileDialog.getOpenFileName(self, "选取文件", os.getcwd(), # os.getcwd用于返回当前工作目录
"PDF Files(*.pdf);;Text Files(*.txt);;Word Files(*.docx *.doc)") # 设置文件类型过滤器,即设置文件可选格式
self.filepath.setText(self.fileName)# 在文本框中显示文件路径
# 开始读取文件的方法
def choose(self):
# 如果没有选择文件,弹出提示框
if self.filepath.text() == '':
msg_box = QMessageBox(QMessageBox.Warning, '警告', '请先选择文件!')
msg_box.exec_()
else: # 如果选择了文件
# 读取文件信息的jar包
jarpath = r'F:/python_codes/codeChecker/tikaTest.jar'
# 开启JVM 注意每个人的JVM所在路径不同
if not jpype.isJVMStarted():
jpype.startJVM(r"C:/Program Files/Java/jdk1.8.0_202/jre/bin/server/jvm.dll", "-ea",
"-Djava.class.path=%s" % jarpath)
tkClass = jpype.JClass("Checker") # Checker是自己的类名
tk = tkClass()
results = tk.check(self.fileName) # 调用check()方法读取文本内容,返回值为java.lang.String数组
# 将结果展示在文本框中,注意要将java.lang.String类型转换成string才能显示,否则程序会崩溃
self.metadatatext.setText(str(results[0]))
self.contenttext.setText(str(results[1]))
# 关闭虚拟机
if jpype.isJVMStarted():
jpype.shutdownJVM()
# 程序入口
if __name__ == '__main__':
app = QApplication(sys.argv)
s = Start()
s.show()
sys.exit(app.exec_())
Python+Pyqt5 文件选择对话框_BTboay-CSDN博客_pyqt5选择文件
使用Tika进行文档解析抽取_MrGrant.blog-CSDN博客
pyQt5 学习笔记(14)QToolButton 工具按钮控件_白杨木屋-CSDN博客
如何让pycharm运行Java代码_梦平的博客-CSDN博客
Python 如何调用 Java - 轻微强迫症 - 博客园
Intellij IDEA 添加jar包的三种方式_mfcbest的专栏-CSDN博客_idea导入jar包