使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序

一、概述

本文将利用PyQt5+Java+Apache Tika实现简单的文档内容提取程序。该程序使用PyQt5完成界面,后台使用python调用jar包的方法使调用Apache Tika库读取文件元数据和正文内容,并显示在文本框中。界面效果如下:
使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序

步骤为:

  1. 使用PyQt5实现前端界面。
  2. 使用Java调用Apache Tika库完成读取文件的类和方法。
  3. 使用python完成后台调用Java类和方法。

开发工具:

  • IntelliJ IDEA 2020.1.4 x64
  • PyCharm Community Edition 2020.2.2 x64

博主初次接触博客,文章难免有疏漏之处,如有错误,欢迎留言批评指正,感激不尽!


二、 使用PyQt5实现前端界面

创建python项目,此处我将其命名为了codeChecker。
为了方便使用,此处我选择了使用Qt Designer和PyUIC工具来实现界面。其中Qt Designer可以通过简单的拖拽实现界面组件的设计,并生成codeChecker.ui文件,再利用PyUIC将codeChecker.ui文件转为codeChecker.py文件,无需编写任何代码,即可实现GUI的设计。重点是,利用这一方法可以使得前端界面与后端逻辑分离开来,。这种界面实现方法十分适合本例中的小项目。

Qt Designer工作界面:
使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
使用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介绍与入门


三、使用Java调用Apache Tika库完成读取文件的类和方法。

有关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/
使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
这里我选择的版本是tika-app-2.0.0-ALPHA.jar。
接下来,创建Java项目,此处我将其命名为tikaTest,将刚刚下载好的jar包导入到项目中。这里介绍一种导入jar包的方式:

  1. 点击File->Project Structure
    使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
  2. 依次点击Moudules->Dependencies->右侧的“+”->“JARs or directories”
    使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
  3. 找到jar包所在位置,点击“OK”->“Apply”即可。

然后我们就可以编写利用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类和方法

为了能够使python调用刚才完成的Java程序,我们需要将刚才的Java项目打包成jar包。打包方法为:

  • 点击File->Project Structure
    使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序

  • 点击Artifacts->上方“+”->JAR->From modules with dependencies
    使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
    点击OK使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
    其中的Module:tikaTest是本项目项目名,因为本项目只有一个类Checker且该类并无main方法,所以无需指定Main Class,直接点击OK即可。

  • 点击OK->Apply。此处可以设置jar包的名字和输出路径。使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序

  • 在IDEA上方菜单栏点击Build->Build Artifacts
    使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
    在弹出来的Build Artifact中点击Build。
    使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
    此时等待片刻,就可以在刚才指定的目录下看到打包好的jar包。为了方便使用,可以直接将jar包复制到python项目目录中,或是直接将jar包输出路径设置为python项目目录。。
    使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
    最后一步,使用python编写后台程序,使得前端界面可以调用Java程序实现文件内容读取功能。首先在刚才创建好的python项目codeChecker中创建main.py作为程序的入口。在main.py中需要实现两件事:

    • 创建一个新类实现前后端的连接
    • 编写__main__模块,运行程序。

创建一个新类实现前后端的连接

创建类Start,其继承QMainWindow类和codeChecker.Ui_fileReader类,其中codeChecker.Ui_fileReader是第一部分自动生成的界面类,为了实现前后端的分类,新类需要继承该类,以直接操控GUI组件实现交互。
类Start有两个属性:

  • fileName = ‘’ # 代表要读取的文件名
  • fileType = ‘’ # 代表要读取的文件类型

在__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类中实现。
使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
接下来实现两个处理按钮点击事件的方法。
首先是打开点击打开文件按钮时的调用方法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__模块,运行程序

最后我们在main.py中编写一个__main__模块来运行程序。

# 程序入口
if __name__ == '__main__':
    app = QApplication(sys.argv)
    s = Start()
    s.show()
    sys.exit(app.exec_())

运行程序,显示界面,点击选择文件按钮,选择一个PDF文件

使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
点击开始读取,控制台会输出调用tika的信息。
使用PyQt5+Java+Apache Tika实现简单的文档内容提取程序
读取结果也会显示在文本框中了。
使用PyQt5+Java+Apache 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包

上一篇:字符流的父类


下一篇:解决datafountain比赛提交.csv文件报错问题