使用 Solr Cell 和 Apache Tika 进行索引

如果需要索引的文档是二进制格式,例如 Word、Excel、PDF 等,Solr 包含一个使用 Apache Tika 提取文本以索引到 Solr 的请求处理程序。

Solr 使用 Tika 项目中的代码来提供一个框架,用于将许多不同的文件格式解析器(例如 Apache PDFBoxApache POI)合并到 Solr 本身中。

通过使用此框架,Solr 的 ExtractingRequestHandler 在内部使用 Tika 来支持上传二进制文件以进行数据提取和索引。使用 Solr Cell 不需要下载 Tika。

当此框架正在开发中时,它被称为 Solr *内容提取库* 或 *CEL*;由此缩写得出了此框架的名称:Solr Cell。名称 Solr Cell 和 ExtractingRequestHandler 在此功能中可以互换使用。

Solr Cell 的关键概念

使用 Solr Cell 框架时,请记住以下几点:

  • Tika 将自动尝试确定输入文档类型(例如,Word、PDF、HTML)并相应地提取内容。如果需要,可以使用 stream.type 参数为 Tika 显式指定 MIME 类型。有关支持的文件类型,请参见 http://tika.apache.org/1.28.5/formats.html

  • 简而言之,Tika 内部的工作原理是,从解析文档的核心内容合成一个 XHTML 文档,该文档传递给由 Solr Cell 提供的配置的 SAX ContentHandler。Solr 响应 Tika 的 SAX 事件,以从内容创建一个或多个文本字段。Tika 还公开了文档元数据(除了 XHTML)。

  • Tika 会根据 DublinCore 等规范生成标题、主题和作者等元数据。可用的元数据高度依赖于文件类型及其包含的内容。在下面的 Tika 创建的元数据部分中描述了一些常见的元数据。Solr Cell 也提供一些自己的元数据。

  • Solr Cell 将内部 XHTML 中的文本连接到 content 字段中。您可以配置应包含/忽略哪些元素,以及哪些元素应映射到另一个字段。

  • Solr Cell 将每个元数据片段映射到字段。默认情况下,它映射到相同的名称,但有几个参数控制如何执行此操作。

  • 当 Solr Cell 完成创建内部 SolrInputDocument 时,索引堆栈的其余部分将接管。任何更新处理程序之后的下一步是 更新请求处理器 链。

模块

这是通过 extraction Solr 模块提供的,使用前需要启用该模块。

Solr 自带的 "techproducts" 示例已预先配置了 Solr Cell。如果您没有使用该示例,则需要注意下面的solrconfig.xml 配置部分。

Solr Cell 性能影响

富文档格式通常没有很好的文档记录,即使存在格式文档,也不是所有创建文档的人都会忠实地遵循规范。

这导致了一种情况,即 Tika 可能会遇到一些它根本无法优雅处理的问题,尽管它竭尽全力支持尽可能多的格式。PDF 文件尤其有问题,这主要是由于 PDF 格式本身造成的。

如果处理任何文件失败,ExtractingRequestHandler 没有辅助机制来尝试从文件中提取一些文本;它会抛出异常并失败。

如果任何异常导致 ExtractingRequestHandler 和/或 Tika 崩溃,Solr 整体也会崩溃,因为请求处理程序与 Solr 用于其他操作的 JVM 运行在同一个 JVM 中。

索引也可能消耗所有可用的 Solr 资源,特别是对于大型 PDF、演示文稿或其他嵌入大量富媒体的文件。

由于这些原因,不建议在生产系统中使用 Solr Cell。

最佳实践是在开发期间使用 Solr Cell 作为概念验证工具,然后将 Tika 作为外部进程运行,该进程将提取的文档发送到 Solr(通过SolrJ)进行索引。这样,发生的任何提取失败都会与 Solr 本身隔离,并且可以优雅地处理。

有关如何实现此操作的一些示例,请参阅 Erick Erickson 的这篇博客文章,使用 SolrJ 进行索引

尝试使用 Solr Cell

您可以使用 Solr 中包含的 schemaless 示例来试用 Tika 框架。

此命令将启动 Solr,创建一个名为 gettingstarted 的核心/集合,并使用 _default configset,并启用提取模块。然后,将 /update/extract 处理程序添加到 gettingstarted 核心/集合,以启用 Solr Cell。

bin/solr start -e schemaless -Dsolr.modules=extraction

curl -X POST -H 'Content-type:application/json' -d '{
  "add-requesthandler": {
    "name": "/update/extract",
    "class": "solr.extraction.ExtractingRequestHandler",
    "defaults":{ "lowernames": "true", "captureAttr":"true"}
  }
}' 'https://127.0.0.1:8983/solr/gettingstarted/config'

Solr 启动后,您可以使用 curl 通过 HTTP POST 发送 Solr 中包含的示例 PDF

curl 'https://127.0.0.1:8983/solr/gettingstarted/update/extract?literal.id=doc1&commit=true' -F "myfile=@example/exampledocs/solr-word.pdf"

上面的 URL 调用 ExtractingRequestHandler,上传文件 solr-word.pdf,并为其分配唯一 ID doc1。下面更详细地了解一下此命令的组成部分

  • literal.id=doc1 参数为正在索引的文档提供唯一 ID。如果没有此参数,ID 将设置为文件的绝对路径。

    还有其他替代方案,例如将元数据字段映射到 ID,生成新的 UUID,或者从内容的签名(哈希)生成 ID。

  • commit=true 参数使 Solr 在索引文档后执行提交,使其立即可以搜索。为了在加载大量文档时获得最佳性能,请在完成操作之前不要调用提交命令。

  • -F 标志指示 curl 使用 Content-Type multipart/form-data POST 数据,并支持上传二进制文件。@ 符号指示 curl 上传附加文件。

  • 参数 myfile=@example/exampledocs/solr-word.pdf 上传示例文件。请注意,这包括路径,因此如果上传不同的文件,请始终确保包含文件的相对或绝对路径。

您还可以使用 bin/solr post 执行相同的操作

$ bin/solr post -c gettingstarted example/exampledocs/solr-word.pdf --params "literal.id=doc1"

现在您可以执行查询,并通过诸如 https://127.0.0.1:8983/solr/gettingstarted/select?q=pdf 之类的请求找到该文档。该文档将如下所示

sample pdf query

您可能会注意到此文档有许多关联的元数据字段。Solr 的配置默认处于“无模式”(数据驱动)模式,因此所有提取的元数据字段都会获得自己的字段。

您可能希望忽略它们,除了您指定的少数几个。为此,请使用 uprefix 参数将未知(对于 schema)的元数据字段名称映射到有效忽略的 schema 字段名称。动态字段 ignored_* 非常适合此目的。

对于您要映射的字段,请使用 fmap.IN=OUT 显式设置它们,或者确保该字段在 schema 中定义。这是一个示例

$ bin/solr post -c gettingstarted example/exampledocs/solr-word.pdf --params "literal.id=doc1&uprefix=ignored_&fmap.last_modified=last_modified_dt"

如果您在索引文档一次或多次后运行上述示例,它将不会按预期工作。

之前我们在添加文档时没有使用这些参数,因此当时所有字段都已添加到索引中。uprefix 参数仅适用于未定义的字段,因此如果稍后重新索引文档,则不会为它们添加前缀。但是,您会看到新的 last_modified_dt 字段。

尝试 uprefix 参数的最简单方法是从新的集合重新开始。

ExtractingRequestHandler 参数和配置

Solr Cell 参数

ExtractingRequestHandler 接受以下参数。

这些参数可以为每个索引请求设置(作为请求参数),也可以通过在 solrconfig.xml 中定义它们来为对请求处理程序的所有请求设置。

capture

可选

默认值:无

捕获具有指定名称的 XHTML 元素,以便添加到 Solr 文档中。此参数对于将 XHTML 的块复制到单独的字段中很有用。例如,它可以用来获取段落 (<p>) 并将它们索引到单独的字段中。请注意,内容仍然会捕获到 content 字段中。

示例:capture=p(在请求中)或 <str name="capture">p</str>(在 solrconfig.xml 中)

输出:"p": {"这是我的文档中的一个段落。"}

此参数也可以与 fmap.source_field 参数一起使用,以将属性中的内容映射到新字段。

captureAttr

可选

默认值:false

将 Tika XHTML 元素的属性索引到单独的字段中,字段以元素命名。如果设置为 true,当从 HTML 中提取时,Tika 可以将 <a> 标记中的 href 属性作为名为“a”的字段返回。

示例:captureAttr=true

输出:"div": {"classname1", "classname2"}

commitWithin

可选

默认值:无

在指定的毫秒数内向索引发出提交。

示例:commitWithin=10000(10 秒)

defaultField

可选

默认值:无

如果未指定 uprefix 参数,并且无法以其他方式确定字段,则使用的默认字段。

示例:defaultField=_text_

extractOnly

可选

默认值:false

如果为 true,则返回从 Tika 提取的内容,而不索引文档。这将返回提取的 XHTML 作为响应中的字符串。在屏幕上查看时,设置 extractFormat 参数为 XML 以外的响应格式,以帮助查看嵌入的 XHTML 标记可能会很有用。

示例:extractOnly=true

extractFormat

可选

默认值:xml

控制提取内容的序列化格式。选项为 xmltextxml 格式实际上是 XHTML,与将 -x 命令传递给 Tika 命令行应用程序的结果相同,而文本格式类似于 Tika 的 -t 命令生成的结果。

仅当 extractOnly 设置为 true 时,此参数才有效。

示例:extractFormat=text

输出:有关示例输出(以 XML 格式),请参阅 https://cwiki.apache.org/confluence/display/solr/TikaExtractOnlyExampleOutput

fmap.source_field

可选

默认值:无

将一个字段名称映射(移动)到另一个字段名称。source_field 必须是传入文档中的字段,并且该值是要映射到的 Solr 字段。

示例:fmap.content=text 会将 Tika 生成的 content 字段中的数据移动到 Solr 的 text 字段。

ignoreTikaException

可选

默认值:无

如果为 true,则将跳过处理过程中发现的异常。但是,任何可用的元数据都将被索引。

示例:ignoreTikaException=true

literal.fieldname

可选

默认值:无

使用为每个文档指定的给定值填充指定名称的字段。如果字段是多值的,则数据可以是多值的。

示例:literal.doc_status=published

输出:"doc_status": "published"

literalsOverride

可选

默认值:true

如果为 true,则字面字段值将覆盖具有相同字段名称的其他值。

如果为 false,则使用 literal.fieldname 定义的字面值将附加到已从 Tika 提取的字段中的数据。将 literalsOverride 设置为 false 时,该字段必须是多值的。

示例:literalsOverride=false

lowernames

可选

默认值:false

如果为 true,则所有字段名称都将映射为带有下划线的小写字母(如果需要)。

示例:lowernames=true

输出:假设输入为“Content-Type”,则文档中的结果将是一个字段 content_type

multipartUploadLimitInKB

可选

默认值:2048 千字节

定义允许的文档大小(以千字节为单位)。如果您有非常大的文档,则应增加此值,否则它们将被拒绝。

示例:multipartUploadLimitInKB=2048000

parseContext.config

可选

默认值:无

如果正在使用的 Tika 解析器允许参数,则可以通过创建解析器配置文件并将 Solr 指向该文件来将参数传递给 Tika。有关如何使用此参数的更多信息,请参阅特定于解析器的属性部分。

示例:parseContext.config=pdf-config.xml

passwordsFile

可选

默认值:无

定义文件路径和文件名,用于存储文件名到密码的映射。有关使用密码文件的更多信息,请参阅索引加密文档部分。

示例:passwordsFile=/path/to/passwords.txt

resource.name

可选

默认值:无

指定要索引的文件名。这是可选的,但 Tika 可以使用它作为检测文件 MIME 类型的提示。

示例:resource.name=mydoc.doc

resource.password

可选

默认值:无

定义用于受密码保护的 PDF 或 OOXML 文件的密码。有关使用此参数的更多信息,请参阅索引加密文档部分。

示例:resource.password=secret

tika.config

可选

默认值:无

定义自定义 Tika 配置文件的文件路径和名称。只有在您自定义了 Tika 实现时才需要此项。

示例:tika.config=/path/to/tika.config

uprefix

可选

默认值:无

在模式中未定义的所有字段添加给定前缀。当与动态字段定义结合使用时,这非常有用。

示例:uprefix=ignored_ 会将 ignored_ 作为前缀添加到所有未知字段。在这种情况下,您还可以在模式中定义一个规则,使其不索引这些字段。

<dynamicField name="ignored_*" type="ignored" />

xpath

可选

默认值:无

提取时,仅返回满足给定 XPath 表达式的 Tika XHTML 内容。有关 Tika XHTML 格式的详细信息,请参阅http://tika.apache.org/1.28.5/,它会随解析的格式而变化。另请参阅定义 XPath 表达式部分以获取示例。

solrconfig.xml 配置

如果您已使用提供的示例配置集之一启动了 Solr,则可能已默认配置了 ExtractingRequestHandler

首先,您必须启用模块。如果尚未配置 solrconfig.xml,则需要修改它以查找 ExtractingRequestHandler 及其依赖项。

  <lib dir="${solr.install.dir:../../..}/modules/extraction/lib" regex=".*\.jar" />

然后,您可以在 solrconfig.xml 中配置 ExtractingRequestHandler。以下是 Solr 的 sample_techproducts_configs 配置集中找到的默认配置,您可以根据需要进行修改。

<requestHandler name="/update/extract"
                startup="lazy"
                class="solr.extraction.ExtractingRequestHandler" >
  <lst name="defaults">
    <str name="lowernames">true</str>
    <str name="fmap.content">_text_</str>
  </lst>
</requestHandler>

在此设置中,所有字段名称都转换为小写(使用 lowernames 参数),并且 Tika 的 content 字段映射到 Solr 的 text 字段。

您可能需要配置更新请求处理器 (URP),这些处理器解析数字和日期,并对 Solr Cell 生成的元数据字段执行其他操作。

在 Solr 的 _default 配置集中,启用了无模式模式(也称为数据驱动或字段猜测),该模式已执行各种此类处理。

如果您改为显式定义模式的字段,则可以选择性地指定所需的 URP。指定此项的简单方法是将参数 processor(在 defaults 下)配置为 uuid,remove-blank,field-name-mutating,parse-boolean,parse-long,parse-double,parse-date。例如

<requestHandler name="/update/extract"
                startup="lazy"
                class="solr.extraction.ExtractingRequestHandler" >
  <lst name="defaults">
    <str name="lowernames">true</str>
    <str name="fmap.content">_text_</str>
    <str name="processor">uuid,remove-blank,field-name-mutating,parse-boolean,parse-long,parse-double,parse-date</str>
  </lst>
</requestHandler>

以上建议列表取自作为无模式模式一部分运行的 URP 列表,并提供了其大部分功能。但是,建议列表中缺少无模式功能的一个主要部分,即 add-unknown-fields-to-the-schema,这是将字段添加到模式的部分。因此,您可以使用其他 URP,而无需担心意外添加字段。

特定于解析器的属性

Tika 使用的解析器可能具有控制数据提取方式的特定属性。这些属性可以通过 Solr 传递以用于特殊解析情况。

例如,当从 Java 程序中使用 Tika 库时,PDFParserConfig 类具有一个方法 setSortByPosition(boolean),该方法可以提取垂直方向的文本。要通过 ExtractingRequestHandler 的配置访问该方法,可以将 parseContext.config 属性添加到 solrconfig.xml,然后在 Tika 的 PDFParserConfig 中设置属性,如下例所示。

<entries>
  <entry class="org.apache.tika.parser.pdf.PDFParserConfig" impl="org.apache.tika.parser.pdf.PDFParserConfig">
    <property name="extractInlineImages" value="true"/>
    <property name="sortByPosition" value="true"/>
  </entry>
  <entry>...</entry>
</entries>

请查阅 Tika Java API 文档,了解可以为需要此级别控制的任何特定解析器设置的配置参数。

索引加密文档

如果提供请求中的 resource.passwordpasswordsFile 文件中的密码,则 ExtractingRequestHandler 将解密加密文件并索引其内容。

对于 passwordsFile,所提供的文件必须进行格式化,以便每个规则一行。每个规则都包含一个文件名正则表达式,后跟“=”,然后是明文密码。由于密码是明文的,因此该文件应具有严格的访问限制。

# This is a comment
myFileName = myPassword
.*\.docx$ = myWordPassword
.*\.pdf$ = myPdfPassword

多核配置

对于多核配置,您可以在 solr.xml<solr/> 部分中指定 sharedLib='lib',并将所需的 jar 文件放置在该处。

扩展 ExtractingRequestHandler

如果您想提供自己的 ContentHandler 供 Solr 使用,则可以扩展 ExtractingRequestHandler 并覆盖 createFactory() 方法。此工厂负责构造与 Tika 交互的 SolrContentHandler,并允许文字覆盖 Tika 解析的值。将参数 literalsOverride(通常默认为 true)设置为 false,以将 Tika 解析的值追加到文字值。

Solr Cell 内部结构

Tika 创建的元数据

如前所述,Tika 生成有关文档的元数据。元数据描述文档的不同方面,例如作者姓名、页数、文件大小等。生成的元数据取决于提交的文档类型。例如,PDF 具有与 Word 文档不同的元数据。

Solr 添加的元数据

除了 Tika 的解析器添加的元数据之外,Solr 还添加了以下元数据

  • stream_name:上传到 Solr 的内容流的名称。根据文件上传方式,可能会也可能不会设置此项。

  • stream_source_info:有关流的任何源信息。

  • stream_size:流的大小(以字节为单位)。

  • stream_content_type:流的内容类型(如果可用)。

建议在索引之前使用 extractOnly 选项来发现 Solr 将为您的内容设置的这些元数据元素的值。

输入处理顺序

以下是 Solr Cell 框架处理其输入的顺序

  1. Tika 生成字段或将其作为 literal.<fieldname>=<value> 指定的文字传入。如果 literalsOverride=false,则文字将作为多值附加到 Tika 生成的字段。

  2. 如果 lowernames=true,则 Tika 会将字段映射为小写。

  3. Tika 应用由 fmap.source=target 参数指定的映射规则。

  4. 如果指定了 uprefix,则任何未知字段名称都将使用该值作为前缀;否则,如果指定了 defaultField,则任何未知字段都将复制到默认字段。

Solr Cell 示例

使用捕获和映射字段

以下命令分别捕获 <div> 标记(capture=div),然后将该字段的所有实例映射到名为 foo_t 的动态字段 (fmap.div=foo_t)。

$ bin/solr post -c gettingstarted example/exampledocs/sample.html --params "literal.id=doc2&captureAttr=true&defaultField=_text_&fmap.div=foo_t&capture=div"

使用文字定义自定义元数据

要添加您自己的元数据,请将文字参数与文件一起传入

$ bin/solr post -c gettingstarted --params "literal.id=doc4&captureAttr=true&defaultField=text&capture=div&fmap.div=foo_t&literal.blah_s=Bah" example/exampledocs/sample.html

参数 literal.blah_s=Bah 会将字段 blah_s 插入到每个文档中。文本的每个实例都将是“Bah”。

定义 XPath 表达式

以下示例传入一个 XPath 表达式,以限制 Tika 返回的 XHTML

$ bin/solr post -c gettingstarted --params "literal.id=doc5&captureAttr=true&defaultField=text&capture=div&fmap.div=foo_t&xpath=/xhtml:html/xhtml:body/xhtml:div//node()" example/exampledocs/sample.html

提取数据而不进行索引

Solr 允许您提取数据而不进行索引。如果您仅将 Solr 用作提取服务器,或者如果您有兴趣测试 Solr 提取,则可能需要这样做。

以下示例将 extractOnly=true 参数设置为提取数据而不对其进行索引。

curl "https://127.0.0.1:8983/solr/gettingstarted/update/extract?&extractOnly=true" --data-binary @example/exampledocs/sample.html -H 'Content-type:text/html'

输出包括 Tika 生成的 XML(以及由 Solr 的 XML 进一步转义的 XML),它使用不同的输出格式使其更具可读性 (-out yes 指示该工具将 Solr 的输出回显到控制台)

$ bin/solr post -c gettingstarted --params "extractOnly=true&wt=ruby&indent=true" --out yes example/exampledocs/sample.html

使用带有 POST 请求的 Solr Cell

以下示例将文件作为 POST 的正文流式传输,这样就不会向 Solr 提供有关文件名称的信息。

curl "https://127.0.0.1:8983/solr/gettingstarted/update/extract?literal.id=doc6&defaultField=text&commit=true" --data-binary @example/exampledocs/sample.html -H 'Content-type:text/html'

使用带有 SolrJ 的 Solr Cell

SolrJ 是一个 Java 客户端,可用于将文档添加到索引、更新索引或查询索引。您可以在 SolrJ 中找到有关 SolrJ 的更多信息。

以下是使用 Solr Cell 和 SolrJ 将文档添加到 Solr 索引的示例。

首先,让我们使用 SolrJ 创建一个新的 SolrClient,然后我们将构造一个包含 ContentStream(本质上是文件的包装器)的请求,并将其发送到 Solr

public class SolrCellRequestDemo {
  public static void main (String[] args) throws IOException, SolrServerException {
    SolrClient client = new HttpSolrClient.Builder("https://127.0.0.1:8983/solr/my_collection").build();
    ContentStreamUpdateRequest req = new ContentStreamUpdateRequest("/update/extract");
    req.addFile(new File("my-file.pdf"));
    req.setParam(ExtractingParams.EXTRACT_ONLY, "true");
    NamedList<Object> result = client.request(req);
    System.out.println("Result: " + result);
}

此操作将文件 my-file.pdf 流式传输到 my_collection 的 Solr 索引中。

上面的示例代码调用了 extract 命令,但是您可以轻松地替换 Solr Cell 支持的其他命令。要使用的关键类是 ContentStreamUpdateRequest,它可以确保正确设置 ContentStream。SolrJ 会处理其余的事情。

请注意,ContentStreamUpdateRequest 不仅特定于 Solr Cell。您可以将 CSV 发送到 CSV 更新处理程序,也可以发送到任何其他使用内容流进行更新的请求处理程序。