「这是我参与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等,这样的话,当我们的简历中包含如上字符。返回的结果就会展示出来,我们就可以根据识别的关键字进行其他的各种操作。