Java实现从pdf简历中智能识别关键字

1,157 阅读4分钟

「这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

前言

大家好,这里是经典鸡翅,最近在做一个面试系统,需求是这样的,根据面试人的简历,自动获取到简历上的技术关键字,例如CSS,JS。然后从题库中找到相关的题目,推荐给面试官,面试官根据题目进行面试,最后给出评价。接下来就给大家说一下如何实现从pdf中提取出关键字。

pdf转为文字

我们首先要有一个pdf简历,然后将简历中的所有内容转换为我们的文字,这个地方我们可以直接使用apache的pdfbox。

Apache PDFBox是一个开源Java库,支持PDF文档的开发和转换。 使用此库,您可以开发用于创建,转换和操作PDF文档的Java程序。

通过pdfbox的各种api,就可以实现我们的转换,一般转换有两种方式,第一种是将网络上的url的pdf转为文字。另一种是直接读取文件的方式,我们的选择是直接从文件存储的url中读取pdf并转换。

给大家上一下,写的代码工具类。

public class PDFUtil {
​
    private static Pattern pattern = Pattern.compile("\s*|\t|\r|\n");
​
    public static String getPdfText(String pdfUrl, boolean sort, int startPage, int endPage) throws Exception {
        PDDocument document = null;
        String text = "";
        try {
            URL url = new URL(pdfUrl);
            document = PDDocument.load(url.openStream());
            PDFTextStripper stripper = null;
            stripper = new PDFTextStripper();
            stripper.setSortByPosition(sort);
            stripper.setStartPage(startPage);
            stripper.setEndPage(endPage);
            text = stripper.getText(document);
            text = pattern.matcher(text).replaceAll("");
        } catch (Exception e) {
            log.error("获取pdf转为文字错误:{}", e.getMessage(), e);
        } finally {
            if (document != null) {
                document.close();
            }
        }
        return text;
    }
​
    /**
     * 将pdf输出到指定文件
     */
    public static void getPdfTextToText(String pdfUrl, String outputFilePath, boolean sort, int startPage, int endPage) throws Exception {
        Writer output = null;
        PDDocument document = null;
        try {
            URL url = new URL(pdfUrl);
            document = PDDocument.load(url.openStream());
            output = new OutputStreamWriter(new FileOutputStream(outputFilePath), "UTF-8");
            PDFTextStripper stripper = null;
            stripper = new PDFTextStripper();
            stripper.setSortByPosition(sort);
            stripper.setStartPage(startPage);
            stripper.setEndPage(endPage);
            stripper.writeText(document, output);
        } catch (Exception e) {
            log.error("获取pdf转为文字出错:{}", e.getMessage(), e);
        } finally {
            if (output != null) {
                output.close();
            }
            if (document != null) {
                document.close();
            }
        }
    }
​
}

我们看到的第一个方法就是我现在所使用的通过url进行转换。第二个方法就是将我们的电脑上的文件地址的pdf转为文字。

介绍一下两个方法的参数:

  • sort:是否按照文档定位排序
  • startPage:开始解析的文件的页码
  • endPage:结束解析的文件的页码

更多的参数以及更多的api可以去官网查看。

url的pdf转文字的坑

当大家使用我上面的这种方式的时候,大概率会遇到一个和我一样的坑,就是报错,并且提示url获取超时。这个问题是openStream造成的,我们的解决方案如下。就是在url的基础上,先包一层http的connection。

URL url = new URL(pdfUrl);
HttpURLConnection htpcon = (HttpURLConnection) url.openConnection();
htpcon.setRequestMethod("GET");
htpcon.setDoOutput(true);
htpcon.setDoInput(true);
htpcon.setUseCaches(false);
htpcon.setConnectTimeout(10000);
htpcon.setReadTimeout(10000);
InputStream in = htpcon.getInputStream();
document = PDDocument.load(in);

这样的话,我们可以自己调整超时时间和读取时间,就解决了超时的情况。有了pdf转换后的文字,我们就要开始对关键字进行识别,如果使用传统的equals比对方式,效率是极其慢的,这里我们使用dfa算法,进行一种过滤实现。

DFA算法

DFA全称为:Deterministic Finite Automaton,即确定有穷自动机。其特征为:有一个有限状态集合和一些从一个状态通向另一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。但不同于不确定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。

我们实现主要是这样的,将文本分解为一个个状态。“我是中国人”可以分解为“我”,“我是”,‘我是中’,“我是中国”,“我是中国人”。这样当我们解析文本的时候,当发现我的时候,就会继续走下去,如果是我不,就不会走到我是。这样就发现没有这个关键词,直接跳出,继续进行下一个比对,由于是树结构,其整体的效率比不同的for循环比对快很多很多。

DFA算法的工具类实现

先贴一下实现的思路。

public class KeyWordUtil {
​
    public static int minMatchTYpe = 1;
    public static int maxMatchType = 2;
​
    /**
     * 生成关键字map
     */
    public static Map generateKeyWordMap(Set<String> keyWordSet) {
        HashMap keyWordMap = new HashMap(keyWordSet.size());
        String key = null;
        Map nowMap = null;
        Map<String, String> newWorMap = null;
        Iterator<String> iterator = keyWordSet.iterator();
        while (iterator.hasNext()) {
            key = iterator.next();
            nowMap = keyWordMap;
            for (int i = 0; i < key.length(); i++) {
                char keyChar = key.charAt(i);
                Object wordMap = nowMap.get(keyChar);
                if (wordMap != null) {
                    nowMap = (Map) wordMap;
                } else {
                    newWorMap = new HashMap<>();
                    newWorMap.put("isEnd", "0");
                    nowMap.put(keyChar, newWorMap);
                    nowMap = newWorMap;
                }
​
                if (i == key.length() - 1) {
                    nowMap.put("isEnd", "1");
                }
            }
        }
        return keyWordMap;
    }
​
    /**
     * 关键字过滤
     */
    public static int checkKeyWord(Map<String, String> keyWordMap, String txt, int beginIndex, int matchType) {
        boolean flag = false;
        int matchFlag = 0;
        char word = 0;
        Map nowMap = keyWordMap;
        for (int i = beginIndex; i < txt.length(); i++) {
            word = txt.charAt(i);
            nowMap = (Map) nowMap.get(word);
            if (nowMap != null) {
                matchFlag++;
                if ("1".equals(nowMap.get("isEnd"))) {
                    flag = true;
                    if (minMatchTYpe == matchType) {
                        break;
                    }
                }
            } else {
                break;
            }
        }
        if (matchFlag < 2 || !flag) {
            matchFlag = 0;
        }
        return matchFlag;
    }
​
    /**
     * 获取关键字
     */
    public static Set<String> getKeyWord(Set<String> keyWord, String txt, int matchType) {
        Map<String, String> keyWordMap = generateKeyWordMap(keyWord);
        Set<String> keyWordList = new HashSet<>();
        for (int i = 0; i < txt.length(); i++) {
            int length = checkKeyWord(keyWordMap, txt, i, matchType);
            if (length > 0) {
                keyWordList.add(txt.substring(i, i + length));
                i = i + length - 1;
            }
        }
        return keyWordList;
    }
}

我们最终要调取的方法就是getKeyWord。这个方法,我们传入的keyWord参数,就是我们数据库内已经有的关键字。例如CSS,HTML等,这样的话,当我们的简历中包含如上字符。返回的结果就会展示出来,我们就可以根据识别的关键字进行其他的各种操作。