可搜索的数千个文档存档的最佳实践(pdf和/或xml)

Mel*_*emi 44 xml pdf lucene full-text-search elasticsearch

重新审视一个停滞不前的项目,并寻求建议,使数千个"旧"文档现代化,并通过网络提供.

文档以各种格式存在,有些已过时:(.doc,PageMaker,硬拷贝(OCR),PDF等).资金可用于将文档迁移到"现代"格式,并且许多硬拷贝已经被OCR转换为PDF格式 - 我们原先假设PDF将是最终格式,但我们愿意接受建议(XML?) .

一旦所有文档都采用通用格式,我们希望通过Web界面提供其内容并进行搜索.我们希望能够灵活地返回整个文档中找到搜索"命中"的部分(页面?)(我相信Lucene/elasticsearch使这成为可能吗?!?)如果内容全部是XML,那么它可能更灵活吗?如果是这样,如何/在哪里存储XML?直接在数据库中,还是作为文件系统中的离散文件?如何在文档中嵌入图像/图形?

好奇其他人如何接近这一点.没有"错误"的答案我只是在寻找尽可能多的输入来帮助我们继续前进.

谢谢你的建议.

DrT*_*ech 115

总结:我将推荐ElasticSearch,但让我们解决问题,并讨论如何实现它:

这有几个部分:

  1. 从文档中提取文本以使其可索引
  2. 将此文本作为全文搜索提供
  3. 返回文档的突出显示的片段
  4. 知道文档中的哪些片段被发现允许分页
  5. 返回完整的文档

ElasticSearch可以提供什么:

  1. ElasticSearch(如Solr)使用Tika从各种doc 格式中提取文本和元数据
  2. 很明显,它提供了强大的全文搜索功能.它可以配置为使用适当的语言分析每个文档,阻止,提高某些字段的相关性(例如标题比内容更重要),ngrams等,即标准Lucene的东西
  3. 它可以为每个搜索结果返回突出显示的片段
  4. 它不知道你的文档中出现这些片段的位置
  5. 它可以将原始文档存储为附件,也可以存储和返回提取的文本.但它会返回整个文档,而不是页面.

您可以将整个文档作为附件发送到ElasticSearch,然后您将获得全文搜索.但关键点在于上面的(4)和(5):知道你在doc中的位置,以及返回doc的部分内容.

存储单个页面可能足以满足您的"我在哪里"的目的(尽管您可以同样地进入段落级别),但是您希望它们按照在搜索结果中返回文档的方式进行分组,即使搜索关键字出现在不同的页面上.

首先是索引部分:将您的文档存储在ElasticSearch中:

  1. 使用Tika(或任何您熟悉的)从每个文档中提取文本.将其保留为纯文本或HTML格式以保留一些格式.(忘记XML,不需要它).
  2. 还提取每个文档的元数据:标题,作者,章节,语言,日期等
  3. 将原始文档存储在文件系统中,并记录路径以便以后可以提供
  4. 在ElasticSearch中,索引一个"doc"doc,其中包含所有元数据,可能还包含章节列表
  5. 将每个页面索引为"页面"文档,其中包含:

    • 父字段,其包含的"doc"文档的ID(见"父-子关系"下方)
    • 文本
    • 页码
    • 也许是章节标题或编号
    • 您想要搜索的任何元数据

现在搜索.如何执行此操作取决于您希望如何按页面显示结果,或按文档分组.

页面结果很容易.此查询返回匹配页面的列表(每个页面完整返回)以及页面中突出显示的片段列表:

curl -XGET 'http://127.0.0.1:9200/my_index/page/_search?pretty=1'  -d '
{
   "query" : {
      "text" : {
         "text" : "interesting keywords"
      }
   },
   "highlight" : {
      "fields" : {
         "text" : {}
      }
   }
}
'
Run Code Online (Sandbox Code Playgroud)

显示按"doc"分组的结果和文本中的突出显示有点棘手.它不能通过单个查询完成,但是一个小客户端分组将帮助您.一种方法可能是:

步骤1:执行top-children-query以查找其子项("page")与查询最匹配的父项("doc"):

curl -XGET 'http://127.0.0.1:9200/my_index/doc/_search?pretty=1'  -d '
{
   "query" : {
      "top_children" : {
         "query" : {
            "text" : {
               "text" : "interesting keywords"
            }
         },
         "score" : "sum",
         "type" : "page",
         "factor" : "5"
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

第2步:从上面的查询中收集"doc"ID并发出新查询以从匹配的"页面"文档中获取片段:

curl -XGET 'http://127.0.0.1:9200/my_index/page/_search?pretty=1'  -d '
{
   "query" : {
      "filtered" : {
         "query" : {
            "text" : {
               "text" : "interesting keywords"
            }
         },
         "filter" : {
            "terms" : {
               "doc_id" : [ 1,2,3],
            }
         }
      }
   },
   "highlight" : {
      "fields" : {
         "text" : {}
      }
   }
}
'
Run Code Online (Sandbox Code Playgroud)

第3步:在您的应用中,按文档对上述查询的结果进行分组并显示它们.

使用第二个查询的搜索结果,您已经拥有了可以显示的页面的全文.要移至下一页,您只需搜索它:

curl -XGET 'http://127.0.0.1:9200/my_index/page/_search?pretty=1'  -d '
{
   "query" : {
      "constant_score" : {
         "filter" : {
            "and" : [
               {
                  "term" : {
                     "doc_id" : 1
                  }
               },
               {
                  "term" : {
                     "page" : 2
                  }
               }
            ]
         }
      }
   },
   "size" : 1
}
'
Run Code Online (Sandbox Code Playgroud)

或者,给"页面"文档一个由$doc_id _ $page_num(例如123_2)组成的ID,然后您可以只检索该页面:

curl -XGET 'http://127.0.0.1:9200/my_index/page/123_2
Run Code Online (Sandbox Code Playgroud)

亲子关系:

通常,在ES(和大多数NoSQL解决方案)中,每个doc/object都是独立的 - 没有真正的关系.通过在"doc"和"page"之间建立父子关系,ElasticSearch确保子文档(即"页面")与父文档("doc")存储在同一个分片上.

这使您可以运行top-children-query,它将根据"pages"的内容找到最匹配的"doc".

  • 好的,我会说:**"DrTech for President!"**;-)很棒的答案!希望我能更多地投票.谢谢! (10认同)
  • :)有趣的是,毕竟我的名字是克林顿:) (4认同)
  • 如果您按页面拆分,那么您也可能无法找到跨多个页面拆分的短语,不是吗? (3认同)