全文檢索技術
Lucene&Solr
Part2
1 課程計划
1、索引庫的維護
a) 添加文檔
b) 刪除文檔
c) 修改文檔
2、Lucene的查詢
a) 使用Query的子類查詢
- MatchAllDocsQuery
- TermQuery
- NumericRangeQuery
- BooleanQuery
b) 使用QueryParser
- QueryParser
- MulitFieldQueryParser
3、相關度排序
4、什么是solr
5、Solr的安裝及配置
6、Solr后台的使用
7、使用solrj維護索引庫
2 索引庫的維護
2.1 索引庫的添加
2.1.1 步驟
向索引庫中添加document對象。
第一步:先創建一個indexwriter對象
第二步:創建一個document對象
第三步:把document對象寫入索引庫
第四步:關閉indexwriter。
2.1.2 代碼實現
//添加索引 @Test public void addDocument() throws Exception { //索引庫存放路徑 Directory directory = FSDirectory.open(new File("D:\\temp\\0108\\index"));
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer()); //創建一個indexwriter對象 IndexWriter indexWriter = new IndexWriter(directory, config); //創建一個Document對象 Document document = new Document(); //向document對象中添加域。 //不同的document可以有不同的域,同一個document可以有相同的域。 document.add(new TextField("filename", "新添加的文檔", Store.YES)); document.add(new TextField("content", "新添加的文檔的內容", Store.NO)); document.add(new TextField("content", "新添加的文檔的內容第二個content", Store.YES)); document.add(new TextField("content1", "新添加的文檔的內容要能看到", Store.YES)); //添加文檔到索引庫 indexWriter.addDocument(document); //關閉indexwriter indexWriter.close();
} |
2.2 Field域的屬性
是否分析:是否對域的內容進行分詞處理。前提是我們要對域的內容進行查詢。
是否索引:將Field分析后的詞或整個Field值進行索引,只有索引方可搜索到。
比如:商品名稱、商品簡介分析后進行索引,訂單號、身份證號不用分析但也要索引,這些將來都要作為查詢條件。
是否存儲:將Field值存儲在文檔中,存儲在文檔中的Field才可以從Document中獲取
比如:商品名稱、訂單號,凡是將來要從Document中獲取的Field都要存儲。
是否存儲的標准:是否要將內容展示給用戶
Field類 |
數據類型 |
Analyzed 是否分析 |
Indexed 是否索引 |
Stored 是否存儲 |
說明 |
StringField(FieldName, FieldValue,Store.YES)) |
字符串 |
N |
Y |
Y或N |
這個Field用來構建一個字符串Field,但是不會進行分析,會將整個串存儲在索引中,比如(訂單號,姓名等) 是否存儲在文檔中用Store.YES或Store.NO決定 |
LongField(FieldName, FieldValue,Store.YES) |
Long型 |
Y |
Y |
Y或N |
這個Field用來構建一個Long數字型Field,進行分析和索引,比如(價格) 是否存儲在文檔中用Store.YES或Store.NO決定 |
StoredField(FieldName, FieldValue) |
重載方法,支持多種類型 |
N |
N |
Y |
這個Field用來構建不同類型Field 不分析,不索引,但要Field存儲在文檔中 |
TextField(FieldName, FieldValue, Store.NO) 或 TextField(FieldName, reader)
|
字符串 或 流 |
Y |
Y |
Y或N |
如果是一個Reader, lucene猜測內容比較多,會采用Unstored的策略. |
2.3 索引庫刪除
2.3.1 刪除全部
//刪除全部索引 @Test public void deleteAllIndex() throws Exception { IndexWriter indexWriter = getIndexWriter(); //刪除全部索引 indexWriter.deleteAll(); //關閉indexwriter indexWriter.close(); } |
說明:將索引目錄的索引信息全部刪除,直接徹底刪除,無法恢復。
此方法慎用!!
2.3.2 指定查詢條件刪除
//根據查詢條件刪除索引 @Test public void deleteIndexByQuery() throws Exception { IndexWriter indexWriter = getIndexWriter(); //創建一個查詢條件 Query query = new TermQuery(new Term("filename", "apache")); //根據查詢條件刪除 indexWriter.deleteDocuments(query); //關閉indexwriter indexWriter.close(); } |
2.4 索引庫的修改
原理就是先刪除后添加。
//修改索引庫 @Test public void updateIndex() throws Exception { IndexWriter indexWriter = getIndexWriter(); //創建一個Document對象 Document document = new Document(); //向document對象中添加域。 //不同的document可以有不同的域,同一個document可以有相同的域。 document.add(new TextField("filename", "要更新的文檔", Store.YES)); document.add(new TextField("content", "2013年11月18日 - Lucene 簡介 Lucene 是一個基於 Java 的全文信息檢索工具包,它不是一個完整的搜索應用程序,而是為你的應用程序提供索引和搜索功能。", Store.YES)); indexWriter.updateDocument(new Term("content", "java"), document); //關閉indexWriter indexWriter.close(); } |
3 Lucene索引庫查詢(重點)
對要搜索的信息創建Query查詢對象,Lucene會根據Query查詢對象生成最終的查詢語法,類似關系數據庫Sql語法一樣Lucene也有自己的查詢語法,比如:“name:lucene”表示查詢Field的name為“lucene”的文檔信息。
可通過兩種方法創建查詢對象:
1)使用Lucene提供Query子類
Query是一個抽象類,lucene提供了很多查詢對象,比如TermQuery項精確查詢,NumericRangeQuery數字范圍查詢等。
如下代碼:
Query query = new TermQuery(new Term("name", "lucene"));
2)使用QueryParse解析查詢表達式
QueryParse會將用戶輸入的查詢表達式解析成Query對象實例。
如下代碼:
QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
Query query = queryParser.parse("name:lucene");
3.1 使用query的子類查詢
3.1.1 MatchAllDocsQuery
使用MatchAllDocsQuery查詢索引目錄中的所有文檔
@Test public void testMatchAllDocsQuery() throws Exception { IndexSearcher indexSearcher = getIndexSearcher(); //創建查詢條件 Query query = new MatchAllDocsQuery(); //執行查詢 printResult(query, indexSearcher); } |
3.1.2 TermQuery
TermQuery,通過項查詢,TermQuery不使用分析器所以建議匹配不分詞的Field域查詢,比如訂單號、分類ID號等。
指定要查詢的域和要查詢的關鍵詞。
//使用Termquery查詢 @Test public void testTermQuery() throws Exception { IndexSearcher indexSearcher = getIndexSearcher(); //創建查詢對象 Query query = new TermQuery(new Term("content", "lucene")); //執行查詢 TopDocs topDocs = indexSearcher.search(query, 10); //共查詢到的document個數 System.out.println("查詢結果總數量:" + topDocs.totalHits); //遍歷查詢結果 for (ScoreDoc scoreDoc : topDocs.scoreDocs) { Document document = indexSearcher.doc(scoreDoc.doc); System.out.println(document.get("filename")); //System.out.println(document.get("content")); System.out.println(document.get("path")); System.out.println(document.get("size")); } //關閉indexreader indexSearcher.getIndexReader().close(); } |
3.1.3 NumericRangeQuery
可以根據數值范圍查詢。
//數值范圍查詢 @Test public void testNumericRangeQuery() throws Exception { IndexSearcher indexSearcher = getIndexSearcher(); //創建查詢 //參數: //1.域名 //2.最小值 //3.最大值 //4.是否包含最小值 //5.是否包含最大值 Query query = NumericRangeQuery.newLongRange("size", 1l, 1000l, true, true); //執行查詢 printResult(query, indexSearcher); } |
3.1.4 BooleanQuery
可以組合查詢條件。
//組合條件查詢 @Test public void testBooleanQuery() throws Exception { IndexSearcher indexSearcher = getIndexSearcher(); //創建一個布爾查詢對象 BooleanQuery query = new BooleanQuery(); //創建第一個查詢條件 Query query1 = new TermQuery(new Term("filename", "apache")); Query query2 = new TermQuery(new Term("content", "apache")); //組合查詢條件 query.add(query1, Occur.MUST); query.add(query2, Occur.MUST); //執行查詢 printResult(query, indexSearcher); } |
Occur.MUST:必須滿足此條件,相當於and
Occur.SHOULD:應該滿足,但是不滿足也可以,相當於or
Occur.MUST_NOT:必須不滿足。相當於not
3.2 使用queryparser查詢
通過QueryParser也可以創建Query,QueryParser提供一個Parse方法,此方法可以直接根據查詢語法來查詢。Query對象執行的查詢語法可通過System.out.println(query);查詢。
需要使用到分析器。建議創建索引時使用的分析器和查詢索引時使用的分析器要一致。
3.2.1 QueryParser
需要加入queryParser依賴的jar包。
3.2.1.1 程序實現
@Test public void testQueryParser() throws Exception { IndexSearcher indexSearcher = getIndexSearcher(); //創建queryparser對象 //第一個參數默認搜索的域 //第二個參數就是分析器對象 QueryParser queryParser = new QueryParser("content", new IKAnalyzer()); Query query = queryParser.parse("Lucene是java開發的"); //執行查詢 printResult(query, indexSearcher); } |
3.2.1.2 查詢語法
1、基礎的查詢語法,關鍵詞查詢:
域名+“:”+搜索的關鍵字
例如:content:java
2、范圍查詢
域名+“:”+[最小值 TO 最大值]
例如:size:[1 TO 1000]
范圍查詢在lucene中不支持數值類型,支持字符串類型。在solr中支持數值類型。
3、組合條件查詢
1)+條件1 +條件2:兩個條件之間是並且的關系and
例如:+filename:apache +content:apache
2)+條件1 條件2:必須滿足第一個條件,應該滿足第二個條件
例如:+filename:apache content:apache
3)條件1 條件2:兩個條件滿足其一即可。
例如:filename:apache content:apache
4)-條件1 條件2:必須不滿足條件1,要滿足條件2
例如:-filename:apache content:apache
Occur.MUST 查詢條件必須滿足,相當於and |
+(加號) |
Occur.SHOULD 查詢條件可選,相當於or
|
空(不用符號) |
Occur.MUST_NOT 查詢條件不能滿足,相當於not非 |
-(減號) |
第二種寫法:
條件1 AND 條件2
條件1 OR 條件2
條件1 NOT 條件2
3.2.2 MulitFieldQueryParser
可以指定多個默認搜索域
@Test public void testMultiFiledQueryParser() throws Exception { IndexSearcher indexSearcher = getIndexSearcher(); //可以指定默認搜索的域是多個 String[] fields = {"filename", "content"}; //創建一個MulitFiledQueryParser對象 MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new IKAnalyzer()); Query query = queryParser.parse("java and apache"); System.out.println(query); //執行查詢 printResult(query, indexSearcher);
} |
4 相關度排序
4.1 什么是相關度排序
相關度排序是查詢結果按照與查詢關鍵字的相關性進行排序,越相關的越靠前。比如搜索“Lucene”關鍵字,與該關鍵字最相關的文章應該排在前邊。
4.2 相關度打分
Lucene對查詢關鍵字和索引文檔的相關度進行打分,得分高的就排在前邊。如何打分呢?Lucene是在用戶進行檢索時實時根據搜索的關鍵字計算出來的,分兩步:
1)計算出詞(Term)的權重
2)根據詞的權重值,采用空間向量模型算法計算文檔相關度得分。
什么是詞的權重?
通過索引部分的學習明確索引的最小單位是一個Term(索引詞典中的一個詞),搜索也是要從Term中搜索,再根據Term找到文檔,Term對文檔的重要性稱為權重,影響Term權重有兩個因素:
l Term Frequency (tf):
指此Term在此文檔中出現了多少次。tf 越大說明越重要。
詞(Term)在文檔中出現的次數越多,說明此詞(Term)對該文檔越重要,如“Lucene”這個詞,在文檔中出現的次數很多,說明該文檔主要就是講Lucene技術的。
l Document Frequency (df)
即有多少文檔包含次Term。df 越大說明越不重要。
比如,在一篇英語文檔中,this出現的次數更多,就說明越重要嗎?不是的,有越多的文檔包含此詞(Term), 說明此詞(Term)太普通,不足以區分這些文檔,因而重要性越低。
4.3 設置boost影響打分結果
boost是一個加權值(默認加權值為1.0f),它可以影響權重的計算。
在索引時對某個文檔的Field域設置加權值高,在搜索時匹配到這個Field就可能排在前邊。lucene在執行搜索時對某個域進行加權,在進行組合域查詢時,匹配到加權值高的域最后計算的相關度得分就高。
如果希望某些文檔更重要,當此文檔中包含所要查詢的詞則應該得分較高,這樣相關度排序可以排在前邊,可以在創建索引時設定文檔中某些域(Field)的boost值來實現,如果不進行設定,則Field Boost默認為1.0f。一旦設定,除非刪除此文檔,否則無法改變。
代碼:
field. setBoost(XXXf); XXX即權值。
測試:
可以將springmvc.txt的file_content加權值設置為10.0f,結果搜索spring時如果內容可以匹配到關鍵字就可以把springmvc.txt文件排在前邊。
代碼:
索引時設置boost加權值:
//設置加權值
if(file_name.equals("springmvc.txt")){
//設置比默認值 1.0大的
field_file_content.setBoost(20.0f);
}
if(file_name.equals("spring_README.txt")){
//設置比默認值 1.0大的
field_file_content.setBoost(30.0f);
}
//向文檔中添加Field
document.add(field_file_content);
搜索時:
// 設置組合查詢域,如果匹配到一個域就返回記錄
String[] fields = { "file_content" };
//設置評分,文件名稱中包括關鍵字的評分高
/*Map<String,Float> boosts = new HashMap<String,Float>();
boosts.put("file_content", 3.0f);*/
// 創建查詢解析器
QueryParser queryParser = new MultiFieldQueryParser(fields,
new StandardAnalyzer());
// 查詢文件名、文件內容中包括“java”關鍵字的文檔
Query query = queryParser.parse("spring");
TopDocs topDocs = indexSearcher.search(query, 100);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
結果:
springmvc.txt排在最前邊
5 什么是solr
Solr 是Apache下的一個頂級開源項目,采用Java開發,它是基於Lucene的全文搜索服務器。Solr提供了比Lucene更為豐富的查詢語言,同時實現了可配置、可擴展,並對索引、搜索性能進行了優化。
Solr可以獨立運行,運行在Jetty、Tomcat等這些Servlet容器中,Solr 索引的實現方法很簡單,用 POST 方法向 Solr 服務器發送一個描述 Field 及其內容的 XML 文檔,Solr根據xml文檔添加、刪除、更新索引 。Solr 搜索只需要發送 HTTP GET 請求,然后對 Solr 返回Xml、json等格式的查詢結果進行解析,組織頁面布局。Solr不提供構建UI的功能,Solr提供了一個管理界面,通過管理界面可以查詢Solr的配置和運行情況。
Solr與Lucene的區別:
Lucene是一個開放源代碼的全文檢索引擎工具包,它不是一個完整的全文檢索引擎,Lucene提供了完整的查詢引擎和索引引擎,目的是為軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者以Lucene為基礎構建全文檢索引擎。
Solr的目標是打造一款企業級的搜索引擎系統,它是一個搜索引擎服務,可以獨立運行,通過Solr可以非常快速的構建企業的搜索引擎,通過Solr也可以高效的完成站內搜索功能。
6 Solr安裝及配置
6.1 Solr的下載
從Solr官方網站(http://lucene.apache.org/solr/ )下載Solr4.10.3,根據Solr的運行環境,Linux下需要下載lucene-4.10.3.tgz,windows下需要下載lucene-4.10.3.zip。
Solr使用指南可參考:https://wiki.apache.org/solr/FrontPage。
6.2 Solr的文件夾結構
將solr-4.10.3.zip解壓:
bin:solr的運行腳本
contrib:solr的一些貢獻軟件/插件,用於增強solr的功能。
dist:該目錄包含build過程中產生的war和jar文件,以及相關的依賴文件。
docs:solr的API文檔
example:solr工程的例子目錄:
l example/solr:
該目錄是一個包含了默認配置信息的Solr的Core目錄。
l example/multicore:
該目錄包含了在Solr的multicore中設置的多個Core目錄。
l example/webapps:
該目錄中包括一個solr.war,該war可作為solr的運行實例工程。
licenses:solr相關的一些許可信息
6.3 運行環境
solr 需要運行在一個Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默認提供Jetty(java寫的Servlet容器),本教程使用Tocmat作為Servlet容器,環境如下:
Solr:Solr4.10.3
Jdk:jdk1.7.0_72
Tomcat:apache-tomcat-7.0.53
6.4 Solr整合tomcat
6.4.1 Solr Home與SolrCore
創建一個Solr home目錄,SolrHome是Solr運行的主目錄,目錄中包括了運行Solr實例所有的配置文件和數據文件,Solr實例就是SolrCore,一個SolrHome可以包括多個SolrCore(Solr實例),每個SolrCore提供單獨的搜索和索引服務。
example\solr是一個solr home目錄結構,如下:
上圖中“collection1”是一個SolrCore(Solr實例)目錄 ,目錄內容如下所示:
說明:
collection1:叫做一個Solr運行實例SolrCore,SolrCore名稱不固定,一個solr運行實例對外單獨提供索引和搜索接口。
solrHome中可以創建多個solr運行實例SolrCore。
一個solr的運行實例對應一個索引目錄。
conf是SolrCore的配置文件目錄 。
data目錄存放索引文件需要創建
6.4.2 整合步驟
第一步:安裝tomcat。D:\temp\apache-tomcat-7.0.53
第二步:把solr的war包復制到tomcat 的webapp目錄下。
把\solr-4.10.3\dist\solr-4.10.3.war復制到D:\temp\apache-tomcat-7.0.53\webapps下。
改名為solr.war
第三步:solr.war解壓。使用壓縮工具解壓或者啟動tomcat自動解壓。解壓之后刪除solr.war
第四步:把\solr-4.10.3\example\lib\ext目錄下的所有的jar包添加到solr工程中
第五步:配置solrHome和solrCore。
1)創建一個solrhome(存放solr所有配置文件的一個文件夾)。\solr-4.10.3\example\solr目錄就是一個標准的solrhome。
2)把\solr-4.10.3\example\solr文件夾復制到D:\temp\0108路徑下,改名為solrhome,改名不是必須的,是為了便於理解。
3)在solrhome下有一個文件夾叫做collection1這就是一個solrcore。就是一個solr的實例。一個solrcore相當於mysql中一個數據庫。Solrcore之間是相互隔離。
- 在solrcore中有一個文件夾叫做conf,包含了索引solr實例的配置信息。
- 在conf文件夾下有一個solrconfig.xml。配置實例的相關信息。如果使用默認配置可以不用做任何修改。
Xml的配置信息:
Lib:solr服務依賴的擴展包,默認的路徑是collection1\lib文件夾,如果沒有 就創建一個
dataDir:配置了索引庫的存放路徑。默認路徑是collection1\data文件夾,如 果data文件夾,會自動創建。
requestHandler:
第六步:告訴solr服務器配置文件也就是solrHome的位置。修改web.xml使用jndi的方式告訴solr服務器。
Solr/home名稱必須是固定的。
第七步:啟動tomcat
第八步:訪問http://localhost:8080/solr/
6.5 Solr后台管理
6.5.1 管理界面
6.5.2 Dashboard
儀表盤,顯示了該Solr實例開始啟動運行的時間、版本、系統資源、jvm等信息。
6.5.3 Logging
Solr運行日志信息
6.5.4 Core Admin
Solr Core的管理界面。Solr Core 是Solr的一個獨立運行實例單位,它可以對外提供索引和搜索服務,一個Solr工程可以運行多個SolrCore(Solr實例),一個Core對應一個索引目錄。
添加solrcore:
第一步:復制collection1改名為collection2
第二步:修改core.properties。name=collection2
第三步:重啟tomcat
6.5.5 java properties
Solr在JVM 運行環境中的屬性信息,包括類路徑、文件編碼、jvm內存設置等信息。
6.5.6 Tread Dump
顯示Solr Server中當前活躍線程信息,同時也可以跟蹤線程運行棧信息。
6.5.7 Core selector
選擇一個SolrCore進行詳細操作,如下:
6.5.8 Analysis
通過此界面可以測試索引分析器和搜索分析器的執行情況。
6.5.9 Dataimport
可以定義數據導入處理器,從關系數據庫將數據導入 到Solr索引庫中。
6.5.10 Document
通過此菜單可以創建索引、更新索引、刪除索引等操作,界面如下:
/update表示更新索引,solr默認根據id(唯一約束)域來更新Document的內容,如果根據id值搜索不到id域則會執行添加操作,如果找到則更新。
6.5.11 Query
通過/select執行搜索索引,必須指定“q”查詢條件方可搜索。
7 Solr管理索引庫
7.1 添加/更新文檔
7.2 刪除文檔
刪除索引格式如下:
1) 刪除制定ID的索引
<delete>
<id>8</id>
</delete>
<commit/>
2) 刪除查詢到的索引數據
<delete>
<query>product_catalog_name:幽默雜貨</query>
</delete>
<commit/>
3) 刪除所有索引數據
<delete>
<query>*:*</query>
</delete>
<commit/>
7.3 查詢索引
8 使用SolrJ管理索引庫
8.1 什么是solrJ
solrj是訪問Solr服務的java客戶端,提供索引和搜索的請求方法,SolrJ通常在嵌入在業務系統中,通過SolrJ的API接口操作Solr服務,如下圖:
8.2 依賴的jar包
8.3 添加文檔
8.3.1 實現步驟
第一步:創建一個java工程
第二步:導入jar包。包括solrJ的jar包。還需要
第三步:和Solr服務器建立連接。HttpSolrServer對象建立連接。
第四步:創建一個SolrInputDocument對象,然后添加域。
第五步:將SolrInputDocument添加到索引庫。
第六步:提交。
8.3.2 代碼實現
//向索引庫中添加索引 @Test public void addDocument() throws Exception { //和solr服務器創建連接 //參數:solr服務器的地址 SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr"); //創建一個文檔對象 SolrInputDocument document = new SolrInputDocument(); //向文檔中添加域 //第一個參數:域的名稱,域的名稱必須是在schema.xml中定義的 //第二個參數:域的值 document.addField("id", "c0001"); document.addField("title_ik", "使用solrJ添加的文檔"); document.addField("content_ik", "文檔的內容"); document.addField("product_name", "商品名稱"); //把document對象添加到索引庫中 solrServer.add(document); //提交修改 solrServer.commit();
} |
8.4 刪除文檔
8.4.1 根據id刪除
//刪除文檔,根據id刪除 @Test public void deleteDocumentByid() throws Exception { //創建連接 SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr"); //根據id刪除文檔 solrServer.deleteById("c0001"); //提交修改 solrServer.commit(); } |
8.4.2 根據查詢刪除
查詢語法完全支持Lucene的查詢語法。
//根據查詢條件刪除文檔 @Test public void deleteDocumentByQuery() throws Exception { //創建連接 SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr"); //根據查詢條件刪除文檔 solrServer.deleteByQuery("*:*"); //提交修改 solrServer.commit(); } |
8.5 修改文檔
在solrJ中修改沒有對應的update方法,只有add方法,只需要添加一條新的文檔,和被修改的文檔id一致就,可以修改了。本質上就是先刪除后添加。
8.6 查詢文檔
//查詢索引 @Test public void queryIndex() throws Exception { //創建連接 SolrServer solrServer = new HttpSolrServer("http://localhost:8080/solr"); //創建一個query對象 SolrQuery query = new SolrQuery(); //設置查詢條件 query.setQuery("*:*"); //執行查詢 QueryResponse queryResponse = solrServer.query(query); //取查詢結果 SolrDocumentList solrDocumentList = queryResponse.getResults(); //共查詢到商品數量 System.out.println("共查詢到商品數量:" + solrDocumentList.getNumFound()); //遍歷查詢的結果 for (SolrDocument solrDocument : solrDocumentList) { System.out.println(solrDocument.get("id")); System.out.println(solrDocument.get("product_name")); System.out.println(solrDocument.get("product_price")); System.out.println(solrDocument.get("product_catalog_name")); System.out.println(solrDocument.get("product_picture"));
} } |