使用 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 中執行。

索引編制也會耗用所有可用的 Solr 資源,尤其是在大型 PDF、簡報或其他嵌入大量多媒體的檔案時。

基於這些原因,不建議在生產系統中使用 Solr Cell。

最佳實務是在開發期間使用 Solr Cell 作為概念驗證工具,然後將 Tika 作為外部程序執行,並將提取的文件傳送到 Solr(透過 SolrJ)進行索引編制。這樣,發生的任何提取失敗都會與 Solr 本身隔離,並且可以順利處理。

如需了解如何執行此操作的範例,請參閱 Erick Erickson 的這篇部落格文章:使用 SolrJ 進行索引編制

試用 Solr Cell

您可以使用 Solr 中包含的 schemaless 範例來試用 Tika 框架。

此命令將啟動 Solr,建立一個名為 gettingstarted 的核心/集合,並使用 _default configset,並啟用 extraction 模組。然後,將 /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 在索引文件後執行提交,使其立即可以搜尋。在載入許多文件時,為了獲得最佳效能,請在完成之前不要呼叫 commit 命令。

  • -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 參數將未知的(綱要)中繼資料欄位名稱對應到一個實際上被忽略的綱要欄位名稱。動態欄位 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 KB

定義允許的文件大小 (以 KB 為單位)。如果您有非常大的文件,則應該增加此值,否則它們將會被拒絕。

範例: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

選用

預設:無

在 schema 中未定義的所有欄位加上指定的前置詞。當與動態欄位定義結合使用時,這非常有用。

範例:uprefix=ignored_ 會將 ignored_ 作為前置詞加到所有未知欄位。在這種情況下,您還可以額外在 Schema 中定義規則,以不索引這些欄位

<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 配置集中,已啟用無 schema 模式(又稱資料驅動或欄位猜測),它已執行各種此類處理。

如果您改為明確定義 schema 的欄位,則可以選擇性地指定所需的 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>

上面建議的清單取自以無 schema 模式作為一部分執行的 URP 清單,並提供其大部分功能。但是,無 schema 功能的主要部分從建議的清單中遺失,add-unknown-fields-to-the-schema,這是將欄位新增到 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 文件,以取得可以針對任何需要此控制層級的特定剖析器設定的設定參數。

索引加密文件

如果您在請求中或在 passwordsFile 檔案中提供密碼,則 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 進一步逸出),使用不同的輸出格式使其更具可讀性 (-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'

將 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("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,它會確保正確設定 ContentStreams。SolrJ 會處理其餘部分。

請注意,ContentStreamUpdateRequest 並非僅特定於 Solr Cell。您可以將 CSV 傳送到 CSV 更新處理常式以及任何其他適用於更新的內容串流的請求處理常式。