使用 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 内容处理程序。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 中运行。

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

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

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

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

尝试 Solr Cell

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

此命令将启动 Solr,创建一个名为 gettingstarted 的核心/集合,使用 _default 配置集,并启用提取模块。然后将 /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"}
  }
}' 'http://localhost:8983/solr/gettingstarted/config'

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

curl 'http://localhost: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"

现在您可以执行查询并使用类似 http://localhost:8983/solr/gettingstarted/select?q=pdf 的请求找到该文档。该文档将类似于以下内容

sample pdf query

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

您可能希望忽略它们,除了您指定的几个。为此,请使用 uprefix 参数将未知(对模式而言)元数据字段名称映射到实际上被忽略的模式字段名称。动态字段 ignored_* 非常适合此目的。

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

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

如果您想为 Solr 提供自己的ContentHandler,您可以扩展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 "http://localhost:8983/solr/gettingstarted/update/extract?&extractOnly=true" --data-binary @example/exampledocs/sample.html -H 'Content-type:text/html'

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

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

使用 Solr Cell 与 POST 请求

以下示例将文件作为 POST 的主体进行流式传输,因此不会向 Solr 提供有关文件名称的信息。

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

使用 Solr Cell 与 SolrJ

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("http://localhost: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 更新处理程序,以及发送到任何其他使用 ContentStream 进行更新的请求处理程序。