2019 年 4 月

第 34 卷,第 4 期

[.NET]

實作專屬企業搜尋

藉由Xavier Morera

您可能需要搜尋,授與。您使用它每日執行所有種類的工作,從探索您下一步] 的車程,若要尋找您需要為您的作業資訊的空間。適當實作的搜尋可協助您節省成本 — 或賺錢。即使在絕佳的應用程式中,不正確的搜尋仍建立令人滿意的 ux。

問題是,儘管其重要性,搜尋中最常被誤解的功能的其中一個 IT、 遺失或損毀時,才注意到。但是,搜尋不需要是難以理解的功能,很難實作。在本文中我要說明如何開發企業搜尋 API,在C#。

若要了解搜尋,您需要的搜尋引擎。有許多選項,從開放原始碼商業和之間的所有內容中,而其中有許多在內部利用 Lucene — 選擇的資訊擷取文件庫。這包括 Azure 搜尋服務、 ElasticSearch 和 Solr。今天我要使用 Solr。為什麼會這樣?已解決長的時間,有很好的文件、 活躍的社群、 許多值得注意的使用者,以及我個人利用它來實作搜尋,在許多應用程式,從小型到大型企業的站台。

取得 Solr

安裝搜尋引擎可能聽起來複雜的工作,而且事實上,如果您設定需要支援大量每秒查詢 (QPS),然後是生產 Solr 執行個體,它可能很複雜。(QPS 是常用的準則,用來搜尋工作負載,請參閱)。

這些步驟所述的長度 Solr 文件的 「 安裝 Solr 」 一節 (lucene.apache.org/solr),如 thing 」 Well-Configured Solr 執行個體 」 一節中 「 Apache Solr 參考指南 》 (bit.ly/2IK7mqY)。但我只需要開發 Solr 的執行個體,因此我只需前往bit.ly/2tEXqoo並取得二進位版本; solr 7.7.0.zip 會運作。

我下載.zip 檔案解壓縮,開啟命令列,將目錄變更為我用來解壓縮檔案的資料夾的根目錄。然後我會發出下列命令:

> bin\solr.cmd start

就這樣。我只需要瀏覽至 http://localhost:8983,我要在其中出現 Solr 系統管理 UI 中顯示**[圖 1**。

向上搜尋引擎並執行
[圖 1] 搜尋引擎,啟動並執行

取得資料

接下來,我需要的資料。有許多資料集有感興趣的資料,但每天上千名開發人員最後到達 StackOverflow 因為它們很不記得如何寫入至檔案。在不離開 VIM;或者它們需要C#可解決特定問題的程式碼片段。好消息是,StackOverflow 資料可從包含大約 10 萬個問題和解答,標記徽章、 匿名的使用者資訊和更多功能的 XML 傾印 (bit.ly/1GsHll6)。

更棒的是,我可以選取具有相同格式的資料集,從較小的 StackExchange 站台,包含少數的數千個問題。我可以在第一次,測試與較小的資料集,並稍後強化的基礎結構,以處理更多資料。

我會先將資料從datascience.stackexchange.com,其中包含大約 25 一千個貼文。檔案命名為 datascience.stackexchange.com.7z。我可以下載並解壓縮 Posts.xml。

幾個必要的概念

我有搜尋引擎和資料,因此若要檢閱的一些重要概念的好時機。如果您習慣使用關聯式資料庫,它們可能會很熟悉。

索引是搜尋引擎儲存針對搜尋所收集的所有資料的位置。概括而言,Solr 會將資料儲存在所謂的反向的索引。在反向索引中,字詞 (或權杖) 指向特定的文件。當您搜尋特定單字時,這會是查詢。如果找到一字 (叫用或相符項目),索引會指出哪些文件包含這個字和位置。

結構描述是您指定的資料結構的方式。每一筆記錄會呼叫文件,就如同您會定義資料庫的資料行,您在搜尋引擎中指定的文件欄位。

您建構結構描述編輯 XML 檔案,稱為 「 schema.xml,使用它們的型別定義的欄位。不過,您也可以使用動態欄位,在其中新增欄位時自動建立未知的欄位加入。這可以是適用於當您不太確定資料中出現的所有欄位。

它可能已經發生了您 Solr 是 NoSQL 文件存放區中,非常類似,因為它可以包含在文件集合都是反正規化並不一定是一致的欄位具有文件。

在索引中的資料模型化的另一種無結構描述的模式。在此情況下,您不要明確地告訴 Solr 您將建立索引的欄位。相反地,您只需將資料加入和 Solr 建構結構描述新增至索引的資料型別為基礎。若要使用無結構描述的模式,您必須使用受管理的結構描述,這表示您無法手動編輯欄位。這是特別適用於資料探勘階段或概念證明。

我將使用手動編輯的結構描述,也就是建議用於生產環境的方法,因為它會產生更好的控制。

編製索引是將資料加入至索引的程序。進行索引期間,應用程式會讀取來自不同來源,並準備資料進行擷取。這是用來加入文件,word您也可能會聽到詞彙饋送。

一旦文件編製索引,就能夠搜尋,請在頂端位置希望與特定的搜尋最相關的文件傳回。 

相關性排名是最適當的結果傳回位於頂端的方式。這是為了說明簡單的概念。在最佳的情況下,您執行查詢,搜尋引擎的 「 讀取 」 您的心智,傳回完全您要尋找的文件。

與重新叫用精確度:有效位數是指您的結果有多少相關 (結果的品質),雖然重新叫用表示的總多少傳回結果是相關 (數量或完整的結果)。 

分析器、 tokenizers 和篩選器:當資料編製索引或搜尋時,分析器會檢查文字,並產生語彙基元資料流。透過篩選器,以嘗試比對索引資料的查詢會接著套用轉換。您需要了解這些,如果您想搜尋引擎技術更深入掌握。幸運的是,您可以開始建立應用程式只是整體概觀。

了解資料

我需要了解資料,才能建立索引的模型。為此,我會開啟 Posts.xml 並加以分析。 

以下是資料的外觀。[文章] 節點包含許多資料列的子節點。每個子節點會對應至一筆記錄,匯出每一個子節點中的屬性為每個欄位。資料是非常乾淨可將擷取至搜尋引擎,這是很好的:

<posts>
  <row Id=”5” PostTypeId=”1” CreationDate=”2014-05-13T23:58:30.457”
    Score=”9” ViewCount=”448” Body=”<Contains the body of the question or answer>”
      OwnerUserId=”5” LastActivityDate=”2014-05-14T00:36:31.077”
        Title=”How can I do simple machine learning without hard-coding behavior?”
          Tags=”<machine-learning,artificial-intelligence>;” AnswerCount=”4”
            CommentCount=”5” FavoriteCount=”1”
              ClosedDate=”2014-05-14T14:40:25.950” />
  ...
</posts>

看一眼,我可以快速地觀察如何有不同類型的欄位。沒有唯一識別項、 少數的日期、 幾個數值欄位、 部分完整和一些簡短的文字欄位和一些中繼資料欄位。為求簡潔,我編輯出本文,也就是大型文字欄位,但所有其他項目處於其原始狀態。

設定要使用傳統的結構描述的 Solr

有幾種方式來指定索引的資料結構。根據預設,Solr,請使用受管理的結構描述,這表示它會使用無結構描述的模式。但我想要以手動方式建構結構描述,所謂的典型的結構描述,因此我必須進行一些組態變更。首先,我將建立資料夾來存放我稱 msdnarticledemo 的索引組態。資料夾是位於 < solr > \server\solr\,< solr > 其中是我用來解壓縮 Solr 的資料夾。

接下來,我建立的文字檔稱為 core.properties,必須只下面這一行加入此資料夾的根目錄: 名稱 = msdnarticledemo。這個檔案用來建立 Solr 核心,也就是剛才的 Lucene 索引的執行個體。您可能會聽到的字集合,也可以有不同的意義,視內容而定。我目前的意圖和用途,核心會是相當於索引。

我現在要複製範例全新索引做為基底所使用的內容。Solr 可包含一個 < solr > \server\solr\configsets\_default 中。我將 conf 資料夾複製到 msdnarticledemo 時。

在非常重要的下一個步驟中,我告訴 Solr 我想要使用傳統的結構描述;也就是說,我將以手動方式編輯我的結構描述。若要這樣做,我可以開啟 solrconfig.xml,並新增下面這一行:

<schemaFactory class=”ClassicIndexSchemaFactory”/>

此外,仍在此檔案中,我註解兩個節點,使用 updateRequestProcessorChain:

name=”add-unknown-fields-to-the-schema”
and updateProcessor with:
name=”add-schema-fields”

這兩項功能可讓在無結構描述的模式中的資料編製索引時加入新欄位的 Solr。我也會移除這個假設的 xml 節點內的註解"-"xml 註解中不允許。

最後,我將重新命名管理結構描述為 schema.xml。就這樣,Solr 已準備好使用手動建立的結構描述。

建立結構描述

下一個步驟是結構描述中定義的欄位,所以我開啟 schema.xml,然後向下捲動直到找到定義的識別碼、 _text_ 和 _root_。

這是定義每個欄位的方式,可為 < 欄位 > xml 節點,其中包含:

  • name:每個欄位的名稱。
  • 類型:欄位; 的類型您可以修改每個類型的處理方式從 schema.xml 中。
  • 編製索引:True 表示此欄位可以用來搜尋。
  • 儲存:True 表示會傳回此欄位顯示。
  • 必要:True 表示此欄位必須編製索引,否則會引發錯誤。
  • 多重值:True 表示此欄位可能包含多個值。

這可能是令人困惑,不過某些欄位可以顯示,但不是會搜尋和一些可供搜尋,但可能無法編製索引之後擷取。還有其他的進階的屬性,但是我不會在此時收到到其詳細資料。

[圖 2顯示如何我定義的欄位結構描述中的文章。文字型別中,我有各式各樣,包括字串、 text_get_sort 和 text_general。文字搜尋會搜尋,因此的主要目標有不同的支援文字的類型。此外,我也會有日期、 整數、 浮點數或一個包含多個值,標記的欄位。

[圖 2 Schema.xml 中的欄位

<field name=”id” type=”string” indexed=”true” stored=”true”
  required=”true” multiValued=”false” />
<field name=”postTypeId” type=”pint” indexed=”true” stored=”true” />
<field name=”title” type=”text_gen_sort” indexed=”true”
  stored=”true” multiValued=”false”/
<field name=”body” type=”text_general” indexed=”false”
  stored=”true” multiValued=”false”/>
<field name=”tags” type=”string” indexed=”true” stored=”true”
  multiValued=”true”/>
<field name=”postScore” type=”pfloat” indexed=”true” stored=”true”/>
<field name=”ownerUserId” type=”pint” indexed=”true” stored=”true” />
<field name=”answerCount” type=”pint” indexed=”true” stored=”true” />
<field name=”commentCount” type=”pint” indexed=”true” stored=”true” />
<field name=”favoriteCount” type=”pint” indexed=”true” stored=”true” />
<field name=”viewCount” type=”pint” indexed=”true” stored=”true” />                
<field name=”creationDate” type=”pdate” indexed=”true” stored=”true” />
<field name=”lastActivityDate” type=”pdate” indexed=”true” stored=”true” />
<field name=”closedDate” type=”pdate” indexed=”true” stored=”true” />

將來會視實作而有所不同。目前,我有許多欄位,我可以指定開始和重要性該欄位是相對於其他欄位,我想要搜尋哪一個。

但若要開始,我可以使用全部擷取欄位 _text_ 我所有的欄位上執行的搜尋。我只要建立 copyField,並告訴 Solr 中的所有欄位的資料,應複製到我的預設欄位:

<copyField source=”*” dest=”_text_”/>

現在,當我執行搜尋,Solr 會尋找在此欄位,並傳回任何文件符合我的查詢。

接下來,我重新啟動 Solr 載入核心,並套用變更,執行下列命令:

> bin\solr.cmd restart

我現在已經準備好開始建立C#應用程式。                 

取得 SolrNet 和模型化資料

Solr 提供 REST 式 API 可供您輕鬆地從任何應用程式。更棒的是,還有一個稱為 SolrNet 程式庫 (bit.ly/2XwkROA),提供抽象概念,Solr,透過可讓您輕鬆地使用強型別物件,並附上豐富的功能,讓搜尋應用程式開發更快。

若要取得 SolrNet 最簡單的方式是從 NuGet 安裝 SolrNet 封裝。我會使用 Visual Studio 2017 建立新的主控台應用程式中包含的程式庫。您也可以下載其他套件,例如 SolrCloud,才能使用其他逆轉控制機制,以及額外的功能。

在主控台應用程式,我需要在我的索引中的資料模型。這是很簡單:我只要建立新的類別檔案,稱為 Post.cs,如中所示**[圖 3**。

[圖 3 張貼文件模型

class Post
{
  [SolrUniqueKey(“id”)]
  public string Id { get; set; }
  [SolrField(“postTypeId”)]
  public int PostTypeId { get; set; }
  [SolrField(“title”)]
  public string Title { get; set; }
  [SolrField(“body”)]
  public string Body { get; set; }
  [SolrField(“tags”)]
  public ICollection<string> Tags { get; set; } = new List<string>();
  [SolrField(“postScore”)]
  public float PostScore { get; set; }
  [SolrField(“ownerUserId”)]
  public int? OwnerUserId { get; set; }
  [SolrField(“answerCount”)]
  public int? AnswerCount { get; set; }
  [SolrField(“commentCount”)]
  public int CommentCount { get; set; }
  [SolrField(“favoriteCount”)]
  public int? FavoriteCount { get; set; }
  [SolrField(“viewCount”)]
  public int? ViewCount { get; set; }
  [SolrField(“creationDate”)]
  public DateTime CreationDate { get; set; }
  [SolrField(“lastActivityDate”)]
  public DateTime LastActivityDate { get; set; }
  [SolrField(“closedDate”)]
  public DateTime? ClosedDate { get; set; }
}

這是單純的純舊 CLR 物件 (POCO),表示每個個別的文件在我的索引,但會告訴的 SolrNet 每個屬性會對應到哪一個欄位的屬性。

建立搜尋服務應用程式

當您建立的搜尋應用程式時,通常會建立兩個不同的功能:

索引子:這是我必須先建立應用程式。非常簡單,若要搜尋的資料,我需要以該資料摘要至 Solr。這可能牽涉到讀取多個來源、 轉換與各種格式的詳細資訊,直到它最後準備搜尋中的資料。

搜尋應用程式:一旦我在我的索引中有資料,我就可以開始處理搜尋應用程式。

同時,使用第一個步驟會需要初始化 SolrNet,您可以在 [主控台應用程式中使用下面這一行 (請確定正在 Solr !):

Startup.Init<Post>(“http://localhost:8983/solr/msdnarticledemo”);

我將我的應用程式中建立每個功能的類別。

建置索引子

若要編製索引的文件,我先取得 SolrNet 服務執行個體,可讓我開始任何支援的作業:

var solr = ServiceLocator.Current.GetInstance<ISolrOperations<Post>>();

接下來,我需要 Posts.xml 的內容讀入 XMLDocument,這牽涉到逐一查看每個節點,建立新的 Post 物件,擷取自 XMLNode 的每個屬性,並將它指派至對應的屬性。

請注意,在資料欄位的資訊擷取或搜尋服務中,儲存反正規化。相反地,當您正在使用資料庫,您通常將正規化以避免重複的資料。而不是將某篇文章中新增擁有者的名稱,您會將整數識別碼,並建立個別的資料表,以符合識別碼的名稱。在搜尋中,不過,您的名稱新增為,使用 post 要求的一部分複製資料。為什麼會這樣?因為當資料已標準化,您需要執行聯結,以便擷取,也就是相當昂貴。但是,搜尋引擎的主要目標之一是速度。使用者應該按一個按鈕,並取得他們想要立即的結果。

現在回到 post 物件的建立。在 [ [圖 4,我要顯示只有三個欄位加入其他人就相當簡單。請注意如何標記是多重值,以及我想要檢查 null 的值,以避免例外狀況。  

[圖 4 填入欄位

Post post = new Post();
post.Id = node.Attributes[“Id”].Value;
if (node.Attributes[“Title”] != null)
{
  post.Title = node.Attributes[“Title”].Value;
}
if (node.Attributes[“Tags”] != null){
  post.Tags = node.Attributes[“Tags”].Value.Split(new char[] { ‘<’, ‘>’ })
    .Where(t => !string.IsNullOrEmpty(t)).ToList();}
// Add all other fields

一旦我已填入物件,我可以新增每個執行個體使用的 Add 方法:

solr.Add(post);

或者,我可以建立文章集合,並新增使用 AddRange 的批次中的文章:

solr.AddRange(post_list);

兩種方法雖然不錯,但我們已發現在新增每批 100 個文件通常會為了協助提高效能的許多生產環境部署。請注意,將文件不會使它可供搜尋。我需要認可:

solr.Commit();

現在我將會執行,並根據進行索引的資料量,而且在其執行的電腦,它可能會花費幾秒鐘到幾分鐘的時間。

完成此程序時,我可以瀏覽至 Solr 系統管理 UI、 中間保留,指出 [核心選取器下拉式清單中尋找並挑選 [我的核心 (msdnarticledemo)。從 [概觀] 索引標籤中,我可以看到我想我只編製索引的文件數量的統計資料。

在我的資料傾印我有 25,488 文章,會比對時,看到:

Statistics
  Last Modified: less than a minute ago
  Num Docs:25488
  Max Doc:25688

現在,我在我的索引中有資料,我已經準備好開始使用的搜尋服務端上。

在 Solr 中搜尋

才能跳回 Visual Studio 中,我想要示範從 Solr 的系統管理 UI 的 [快速搜尋,並解釋一些可用的參數。

在 msdnarticledemo core 中,我將按一下查詢,然後推送的藍色按鈕下方顯示執行查詢。取得上一步我的文件以 JSON 格式中所示**[圖 5**。

透過系統管理 UI 的 Solr 中的查詢
[圖 5] 在 Solr 透過系統管理 UI 中的查詢

因此完全我剛剛做了什麼,為什麼收到傳回的所有文件索引中?答案很簡單。看看參數 — 具有要求處理常式 (qt) 的資料行,在頂端。如您所見,有一個參數會標示為 q,和它的值為 *: *。  這是什麼帶出所有文件。事實上,我執行此查詢是要搜尋使用的索引鍵 / 值組的所有值的所有欄位。如果我只想要搜尋標題中的 Solr 的 q 值會是標題: Solr。 

這很適合用於建置更複雜的查詢,為每個欄位,這提供不同的加權。單字或片語,位於標題是個內容中更重要。比方說,如果文件標題中含有企業搜尋,則很有可能,整份文件將會關於企業搜尋。但是,如果找到此片語中的文件本文的任何部分,可能只會參考連相關的項目。

問: 參數可能是最重要的因為它會擷取依相似度排序的文件,藉由計算分數。但有一些您可以透過使用要求的處理常式來設定如何處理要求,solr,包括篩選查詢 (fq)、 排序、 欄位清單 (奧蘭多) 和許多您可以在 Solr 文件中找到的多個其他參數bit.ly/2GVmYGl.使用這些參數中,您可以開始建置更複雜的查詢。到 master,需要一些時間,但越您將了解,您會收到較佳的相關性排名。

請記住,系統管理 UI 不應用程式如何使用 Solr。基於這個目的,使用類似 REST 的介面。如果您看上面結果時,連結中沒有包含這個特定的查詢呼叫的灰色方塊。按一下它,就會開啟新視窗中,其中會保存回應。

這是我的查詢:

http://localhost:8983/solr/msdnarticledemo/select?q=*%3A*&wt=json

SolrNet 會在幕後,這類呼叫,但它會顯示我可以從我的.NET 應用程式使用的物件。現在我將建置的基本搜尋應用程式。

建置搜尋應用程式

在此案例中,我在我的資料集中,所有欄位中搜尋問題。假設資料包含問題和解答,我會篩選 PostTypeId,為"1"表示這是個問題。  若要這樣做,我使用篩選查詢 — fq 參數。

此外,我將會設定某些查詢選項,以一次傳回一頁結果,也就是個資料列,以指出幾個結果,且 StartOrCursor,以指定的位移 (啟動)。此外,當然,我要在其中設定查詢。

[圖 6顯示執行基本的搜尋,使用查詢,我要搜尋的文字所需的程式碼。

[圖 6 執行基本搜尋

QueryOptions query_options = new QueryOptions
{
  Rows = 10,
  StartOrCursor = new StartOrCursor.Start(0),
  FilterQueries = new ISolrQuery[] {
    new SolrQueryByField(“postTypeId”, “1”),
    }
};
// Construct the query
SolrQuery query = new SolrQuery(keywords);
// Run a basic keyword search, filtering for questions only
var posts = solr.Query(query, query_options);

執行查詢之後, 我取得貼文,SolrQueryResults 物件。它包含結果,以及提供其他功能的多個物件使用的許多屬性的集合。現在,我有這些結果,我可以向使用者顯示它們。

縮減結果

在許多情況下,原始的結果會很理想,但使用者可能想要縮小它們的特定中繼資料欄位。您可向下鑽研藉由使用 facet 的欄位。在 facet 中取得金鑰值組的清單。比方說,我所取得的標記每個標記和多少次每一個,就會發生。Facet 是通常用於數值、 日期或字串的欄位。文字欄位是棘手。

若要啟用 facet,我要加入新的查詢,Facet:  

Facet = new FacetParameters
{
  Queries = new[] {
    new SolrFacetFieldQuery(“tags”)
  }
}

現在我可以取得我的 facet 的文章。在結果集中,就會發生 FacetFields ["tags"],也就是包含每個特定的標記,以及多少次的每個標記的索引鍵 / 值組的集合。

然後我可以允許使用者選取哪一個標籤,向下鑽研到,減少使用篩選查詢的結果數目,在理想情況下傳回相關的文件。 

改善您的搜尋-後續步驟為何?

到目前為止,我所討論的實作中的基本搜尋 essentialsC#使用 Solr 和 SolrNet 使用其中一個 StackExchange 站台的問題。不過,這是只要啟動新的旅程圖的我可以在其中探討傳回相關結果使用 Solr 的圖案。

接下來的步驟包括使用不同的加權; 的個別欄位來搜尋提供要顯示的結果中反白顯示符合內容;套用結果相關,但不能包含完全相同的文字所搜尋; 傳回的同義字這是什麼減少增加重新叫用; 其基底的單字詞幹分析語音搜尋,可協助國際使用者;和更多功能。

總而言之,了解如何實作搜尋會是重要的技能,它可能會產生重要會傳回您身為開發人員的未來裡。

伴隨著這篇文章,我建立了基本搜尋專案中所示**[圖 7**,您可以下載以深入了解企業搜尋。設定搜尋的快樂 !

範例搜尋專案
[圖 7 A 範例搜尋專案


Xavier Morera可協助開發人員了解企業搜尋和巨量資料。在 [Pluralsight 和有時 Cloudera,他會建立課程。他在搜尋技術 (現在 Accenture 的一部分),處理搜尋實作工作多年。他現居美國哥斯大黎加,以及您可以找到他xaviermorera.com

感謝閱本篇文章的下列技術專家:Jose Arias (Accenture),Jonathan Gonzalez (Accenture)
Jose Arias 抱有熱忱有關搜尋和巨量資料與相關技術,尤其是那些用於資料分析。  他是資深的開發人員在 Accenture 搜尋與內容分析。https://www.linkedin.com/in/joseariasq/
Jonathan Gonzalez <(j.gonzalez.vindas@accenture.com)>
Jonathan Gonzalez 是經驗的資深管理架構設計人員 Accenture,18 年以上,在軟體開發和設計,專精於資訊擷取、 企業搜尋、 資料處理和分析內容的最後一個 13。https://www.linkedin.com/in/jonathan-gonzalez-vindas-ba3b1543/