【Lucene】全文檢索簡介,Lucene實現流程,入門案例
1. 前言
Lucene 是 apache 軟件基金會的一個子項目,由 Doug Cutting 開發,是一個開放源代碼的全文檢索引擎工具包,但它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的庫,提供了完整的查詢引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語言)。Lucene 的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此爲基礎建立起完整的全文檢索引擎。Lucene 是一套用於全文檢索和搜尋的開源程式庫,由 Apache 軟件基金會支持和提供。
Lucene 提供了一個簡單卻強大的應用程式接口,能夠做全文索引和搜尋。在 Java 開發環境裏 Lucene 是一個成熟的免費開源工具。就其本身而言,Lucene 是當前以及最近幾年最受歡迎的免費 Java 信息檢索程序庫。人們經常提到信息檢索程序庫,雖然與搜索引擎有關,但不應該將信息檢索程序庫與搜索引擎相混淆。
最開始 Lucene 只由 java 開發,供 java 程序調用,隨着 python 越來越火,Lucene 官網也提供了 python 版本的 lucene 庫,供 python 程序調用,即 PyLucene。
2. 全文檢索簡介
簡單來說:全文檢索首先對要搜索的文檔進行分詞,然後形成索引,通過查詢索引來查詢文檔。
全文檢索就是先創建索引,然後根據索引來進行搜索的過程,就叫全文檢索。
比如:字典,
字典的偏旁部首頁,就類似於luence的索引
字典的具體內容,就類似於luence的文檔內容
2.1 數據分類
我們生活中的數據總體分爲兩種:結構化數據和非結構化數據。
結構化數據:指具有固定格式或有限長度的數據,如數據庫,元數據等。
非結構化數據:指不定長或無固定格式的數據,如郵件,word文檔等磁盤上的文件
2.2 結構化數據搜索
常見的結構化數據也就是數據庫中的數據。在數據庫中搜索很容易實現,通常都是使用sql語句進行查詢,而且能很快的得到查詢結果。
- 爲什麼數據庫搜索很容易?
因爲數據庫中的數據存儲是有規律的,有行有列而且數據格式、數據長度都是固定的。
2.3 非結構化數據查詢方法
2.3.1 順序掃描法(Serial Scanning)
所謂順序掃描,比如要找內容包含某一個字符串的文件,就是一個文檔一個文檔的看,對於每一個文檔,從頭看到尾,如果此文檔包含此字符串,則此文檔爲我們要找的文件,接着看下一個文件,直到掃描完所有的文件。如利用windows的搜索也可以搜索文件內容,只是相當的慢。
2.3.2 全文檢索(Full-text Search)
將非結構化數據中的一部分信息提取出來,重新組織,使其變得有一定結構,然後對此有一定結構的數據進行搜索,從而達到搜索相對較快的目的。這部分從非結構化數據中提取出的然後重新組織的信息,我們稱之索引。
例如:字典。字典的拼音表和部首檢字表就相當於字典的索引,對每一個字的解釋是非結構化的,如果字典沒有音節表和部首檢字表,在茫茫辭海中找一個字只能順序掃描。然而字的某些信息可以提取出來進行結構化處理,比如讀音,就比較結構化,分聲母和韻母,分別只有幾種可以一一列舉,於是將讀音拿出來按一定的順序排列,每一項讀音都指向此字的詳細解釋的頁數。我們搜索時按結構化的拼音搜到讀音,然後按其指向的頁數,便可找到我們的非結構化數據——也即對字的解釋。
這種先建立索引,再對索引進行搜索的過程就叫全文檢索(Full-text Search)。
雖然創建索引的過程也是非常耗時的,但是索引一旦創建就可以多次使用,全文檢索主要處理的是查詢,所以耗時間創建索引是值得的。
2.4 如何實現全文檢索
可以使用Lucene實現全文檢索。Lucene是apache下的一個開放源代碼的全文檢索引擎工具包。提供了完整的查詢引擎和索引引擎,部分文本分析引擎。Lucene的目的是爲軟件開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能。
2.5 全文檢索的應用場景
對於數據量大、數據結構不固定的數據可採用全文檢索方式搜索,比如百度、Google等搜索引擎、論壇站內搜索、電商網站站內搜索等。
3. Lucene實現流程
3.1 索引和搜索流程圖
1、綠色表示索引過程,對要搜索的原始內容進行索引構建一個索引庫,索引過程包括:
確定原始內容即要搜索的內容 -> 採集文檔 -> 創建文檔 -> 分析文檔 -> 索引文檔
2、紅色表示搜索過程,從索引庫中搜索內容,搜索過程包括:
用戶通過搜索界面 -> 創建查詢 -> 執行搜索,從索引庫搜索 -> 渲染搜索結果
3.2 創建索引
對文檔索引的過程,將用戶要搜索的文檔內容進行索引,索引存儲在索引庫(index)中。
這裏我們要搜索的文檔是磁盤上的文本文件,根據案例描述:凡是文件名或文件內容包括關鍵字的文件都要找出來,這裏要對文件名和文件內容創建索引。
3.2.1 獲得原始文檔
原始文檔是指要索引和搜索的內容。原始內容包括互聯網上的網頁、數據庫中的數據、磁盤上的文件等。
例如:
從互聯網上、數據庫、文件系統中等獲取需要搜索的原始信息,這個過程就是信息採集,信息採集的目的是爲了對原始內容進行索引。
在Internet上採集信息的軟件通常稱爲爬蟲或蜘蛛,也稱爲網絡機器人,爬蟲訪問互聯網上的每一個網頁,將獲取到的網頁內容存儲起來。
例子中我們要獲取磁盤上文件的內容,可以通過文件流來讀取文本文件的內容,對於pdf、doc、xls等文件可通過第三方提供的解析工具讀取文件內容,比如Apache POI讀取doc和xls的文件內容。
3.2.2 創建文檔對象
獲取原始內容的目的是爲了索引,在索引前需要將原始內容創建成文檔(Document),文檔中包括一個一個的域(Field),域中存儲內容。
這裏我們可以將磁盤上的一個文件當成一個document,Document中包括一些Field(file_name文件名稱、file_path文件路徑、file_size文件大小、file_content文件內容).
注意:每個Document可以有多個Field,不同的Document可以有不同的Field,同一個Document可以有相同的Field(域名和域值都相同)
每個文檔都有一個唯一的編號,就是文檔id。
3.2.3 分析文檔
將原始內容創建爲包含域(Field)的文檔(document),需要再對域中的內容進行分析,分析的過程是經過對原始文檔提取單詞、將字母轉爲小寫、去除標點符號、去除停用詞等過程生成最終的語彙單元,可以將語彙單元理解爲一個一個的單詞。
比如下邊的文檔經過分析如下:
原文檔內容:
Lucene is a Java full-text search engine. Lucene is not a complete
application, but rather a code library and API that can easily be used
to add search capabilities to applications.
分析後得到的語彙單元:
lucene、java、full、search、engine。。。。
每個單詞叫做一個Term,不同的域中拆分出來的相同的單詞是不同的term。term中包含兩部分一部分是文檔的域名,另一部分是單詞的內容。
例如:文件名中包含apache和文件內容中包含的apache是不同的term。
3.2.4 創建索引
對所有文檔分析得出的語彙單元進行索引,索引的目的是爲了搜索,最終要實現只搜索被索引的語彙單元從而找到Document(文檔)。
通過域:關鍵詞
找到相關文檔。
注意:創建索引是對語彙單元索引,通過詞語找文檔,這種索引的結構叫倒排索引結構。
傳統方法是根據文件找到該文件的內容,在文件內容中匹配搜索關鍵字,這種方法是順序掃描方法,數據量大、搜索慢。
倒排索引結構是根據內容(詞語)找文檔,如下圖:
倒排索引結構也叫反向索引結構,包括索引和文檔兩部分,索引即詞彙表,它的規模較小,而文檔集合較大。
3.3 查詢索引
查詢索引也是搜索的過程。搜索就是用戶輸入關鍵字,從索引(index)中進行搜索的過程。根據關鍵字搜索索引,根據索引找到對應的文檔,從而找到要搜索的內容(這裏指磁盤上的文件)。
3.3.1 用戶查詢接口
全文檢索系統提供用戶搜索的界面供用戶提交搜索的關鍵字,搜索完成展示搜索結果。
比如:
Lucene不提供製作用戶搜索界面的功能,需要根據自己的需求開發搜索界面。
3.3.2 創建查詢
用戶輸入查詢關鍵字執行搜索之前需要先構建一個查詢對象,查詢對象中可以指定查詢要搜索的Field文檔域、查詢關鍵字等,查詢對象會生成具體的查詢語法,
- 例如:
語法fileName:lucene
表示要搜索Field域的內容爲“lucene”的文檔
3.3.3 執行查詢
搜索索引過程:
根據查詢語法在倒排索引詞典表中分別找出對應搜索詞的索引,從而找到索引所鏈接的文檔鏈表。
比如搜索語法爲“fileName:lucene”表示搜索出fileName域中包含Lucene的文檔。
搜索過程就是在索引上查找域爲fileName,並且關鍵字爲Lucene的term,並根據term找到文檔id列表。
3.3.4 渲染結果
以一個友好的界面將查詢結果展示給用戶,用戶根據搜索結果找自己想要的信息,爲了幫助用戶很快找到自己的結果,提供了很多展示的效果,比如搜索結果中將關鍵字高亮顯示,百度提供的快照等。
4. Lucene入門案例
4.1 案例需求
實現一個文件的搜索功能,通過關鍵字搜索文件,凡是文件名或文件內容包括關鍵字的文件都需要找出來。還可以根據中文詞語進行查詢,並且需要支持多個條件查詢。
本案例中的原始內容就是磁盤上的文件,如下圖:
4.2 創建索引
導入依賴:
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>7.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-queryparser -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.7</version>
</dependency>
public void createIndex() throws IOException {
//1.創建一個Directory對象,指定索引庫保存的位置。
// 把索引庫保存在內存中
//Directory directory = new RAMDirectory();
// 把索引庫保存在磁盤
Directory directory = FSDirectory.open(new File("E:\\practice\\lucene\\directory").toPath());
//2.基於Directory對象創建一個IndexWriter對象
IndexWriter indexWriter = new IndexWriter(directory,new IndexWriterConfig());
//3.讀取磁盤上的文件,對應每個文件創建一個文檔對象。
File dir = new File("C:\\Users\\siyi\\Desktop\\searchsource");
File[] files = dir.listFiles();
for(File file : files){
//取文件名
String fileName = file.getName();
//文件的路徑
String filePath = file.getPath();
//文件的內容
String fileContent = FileUtils.readFileToString(file,"utf-8");
//文件的大小
long fileSize = FileUtils.sizeOf(file);
//4.向文檔對象中添加域
//創建Field
//參數1:域的名稱 參數2:域的內容 參數3:是否存儲
Field fieldName = new TextField("name", fileName, Field.Store.YES);
// Field fieldPath = new TextField("path", filePath, Field.Store.YES);
Field fieldPath = new StoredField("path", filePath);
Field fieldContent = new TextField("content",fileContent,Field.Store.YES);
// Field fieldSize = new TextField("size",fileSize+"",Field.Store.YES);
Field fieldSizeValue = new LongPoint("size",fileSize);
Field fieldSizeStore = new StoredField("size",fileSize);
//創建文檔對象
Document document = new Document();
document.add(fieldName);
document.add(fieldPath);
document.add(fieldContent);
document.add(fieldSizeValue);
document.add(fieldSizeStore);
//5.把文檔對象寫入索引庫
indexWriter.addDocument(document);
}
//6.關閉indexWriter對象
indexWriter.close();
}
運行結果:
4.2 查詢索引
4.2.1 使用Luke工具查看索引文件
使用Luke時注意版本,比如我們lucene使用的是什麼版本,那麼Luke也必須使用哪個版本。
4.2.2 使用程序進行查詢
public void searchIndex() throws Exception {
//1.創建一個Director對象,指定索引庫的位置
Directory directory = FSDirectory.open(new File("E:\\practice\\lucene\\directory").toPath());
//2.創建一個IndexReader對象
IndexReader indexReader = DirectoryReader.open(directory);
//3.創建一個IndexSearcher對象,構造方法中的參數indexReader對象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//4.創建一個Query對象,TermQuery
Query query = new TermQuery(new Term("content","spring"));
//5.執行查詢,得到一個TopDocs對象
//參數1:查詢對象 參數2:查詢結果返回的最大記錄數
TopDocs topDocs = indexSearcher.search(query, 10);
//6.取查詢結果的總記錄數
System.out.println("查詢總記錄數:"+topDocs.totalHits);
//7.取文檔列表
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
//8.打印文檔中的內容
for (ScoreDoc scoreDoc: scoreDocs){
//取文檔id
int docId = scoreDoc.doc;
//根據id取文檔對象
Document document = indexSearcher.doc(docId);
System.out.println(document.get("name"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println("------------------------------------");
}
//9.關閉IndexReader對象
indexReader.close();
}