使用 JavaScript 实现对 PDF 的全文索引
我曾今在一个售卖法律和财务数据库访问方案(他们称之为“智能信息”)的公司工作。大多数法庭记录都是通过PACER以PDF形式提供的,一个站点被特地开发出来用于发布法庭记录。基于这个数据集的一个意义重大的数据库产品需要建立一条处理管道,它能够从超过两亿分份PDF文档中提取文本并对其进行索引,展示美国超过20年的诉讼记录。这些处理过程将花费数月的机器时间,使得软件工作组在构建它们时的面临很大的压力。在这一处理过程中的早期有个一步骤是从电子文档化的PDF中提取出内容,其在稍后的将会被送入一个NLP处理阶段——显示关键字,标注部分词类,识别实体,而然后发出报告(如果你对此感兴趣,可以检索 Python中的自然语言处理 作为入门 - 再读一读 这里我的评论 )
Mozilla实验室最近已经收到了许多为一个项目做出的尝试,这一项目的野心令人印象深刻:在一个浏览器中仅仅使用Javascript来对PDF进行渲染。PDF文档的结构令人难以置信的复杂,因此要祝pdf.js工作组的兄弟们好运了!在另外一条不同的尝试道路上,Oliver Nightingale使用Javascript实现了一个的Javascript全文索引装置——将这两个项目结合起来,就可以在web浏览器中完全再现PDF处理管道。
站在一名新手的角度来看,全文索引能用户可以搜索非结构化的文档,也可以依据由词频决定的相关度分值来对结果文档进行排名。索引装置会计算每一个份文档中每一个词出现的次数,并且对文本进行最轻微的修改,以移除内容中跟搜索无关的一些文本语法特性。例如,它可能会提取出“-ing”,将元音部分变更为一般的表示形式。如果一个词语频繁出现在整个文档集中,索引装置会自动将其识别为不那么重要的关键词,而它对排名结果的影响将会被最小化。这同Google PageRank背后的基本概念是不同的,后者是基于一个引征图来提升文档排名的。
大多数数据库软件都提供了对全文索引的支持,但如果是大规模安装的话,通常会使用功能更加强大的工具来进行处理。开源产品中主要是Solr/Lucene,Solr是围绕Lucene库封装的一个web应用。它们都是用Java编写的。
构造一个Javascript全文索引装置使得搜索在诸如Phonegap引用,终端用户机或者加密存储的用户数据这些之前很难实现搜索功能的地方成为可能。有一整个领域只研究加密的搜索指数,而在客户机上对数据进行索引和加密看上去像是围绕这个天生具有挑战性的问题想出的一个好办法。
为了测试这个处理管道,我们首先来看看如何从PDF中提取文本,这些文本将在稍后被插入到一个全文索引中。pdf.js的代码是很有启发性的,其中Mozilla的开发者们使用了一些并不常用的浏览器特性,举个例子,Web工作者,会要你设置后台的处理线程。
pdf.js 的 API大量使用约定来持有代码中未完成操作的引用。你会使用回调来对它们进行操作:
var pdf = PDFJS.getDocument('http://www.pacer.gov/documents/pacermanual.pdf');
var pdf = PDFJS.getDocument('pacermanual.pdf');
pdf.then(function(pdf) {
// this code is called once the PDF is ready
});
这样的API看起还不怎么成熟——理想情况下你应该能够写出 promise.then(f(x)).then(g(x)).then(h(x)) 等等代码,但现在那还是不可用的。
约定模式在渲染PDF方面起了很大的作用,因为它为并行的渲染处理留下了空间。对于只是从一份PDF中提取出文本感觉上好像有大量的工作要做——你必须相信你的回调会按照秩序运行并且跟踪到哪个是在最后。
下面的示例代码演示了提取PDF内容,并在浏览器中控制台日志中输出:
‘use strict’;
var pdf = PDFJS.getDocument('http://www.pacer.gov/documents/pacermanual.pdf');
var pdf = PDFJS.getDocument('pacermanual.pdf');
pdf.then(function(pdf) {
var maxPages = pdf.pdfInfo.numPages;
for (var j = 1; j <= maxPages; j++) {
var page = pdf.getPage(j);
// the callback function - we create one per page
var processPageText = function processPageText(pageIndex) {
return function(pageData, content) {
return function(text) {
// bidiTexts has a property identifying whether this
// text is left-to-right or right-to-left
for (var i = 0; i < text.bidiTexts.length; i++) {
str += text.bidiTexts[i].str;
}
if (pageData.pageInfo.pageIndex ===
maxPages - 1) {
// later this will insert into an index
console.log(str);
}
}
}
}(j);
var processPage = function processPage(pageData) {
var content = pageData.getTextContent();
content.then(processPageText(pageData, content));
}
page.then(processPage);
}
});
这并不会识别页眉和图片.如何识别这些内容需要使用渲染代码,需要非常理解PDF命令(PDF可能使用流渲染命令,类似于RTF)
Lunr
创建一个Lunr函数直接添加字段-所有的API都使用JSON类型,以下是一个简单的AIP示例
doc1 = {
id: 1,
title: 'Foo',
body: 'Foo foo foo!'
};
doc2 = {
id: 2,
title: 'Bar',
body: 'Bar bar bar!'
}
doc3 = {
id: 3,
title: 'gary',
body: 'Foo Bar bar bar!'
}
index = lunr(function () {
this.field('title', {boost: 10})
this.field('body')
this.ref('id')
})
// Add documents to the index
index.add(doc1)
index.add(doc2)
index.add(doc3)
推荐阅读: