우선! https://github.com/macluq/HelloLucene/blob/master/pom.xml
pom.xml을 보시면 루씬 core말고도 여러가지가 있습니다! 일단은 그냥 복사해서 붙여넣기를 해봅시다!
차근차근! 알아가보도록 하겠습니다^-^/
사이트에 가서 보기 싫으신 분은 아래를 클릭!
우선은 아래와 같이 테스트 코드를 하나 만들어봅니다. 빨간색 부분이 무엇인지 파악해야 할 친구 입니다. ㅎㅎ
소스는 루씬 튜토리얼 소스라고 하네요 ver은 4.0 입니다!
https://github.com/macluq/HelloLucene
public class IndexTest {
@Test
public void makeIndexTest() {
// 0. Specify the analyzer for tokenizing text.
// The same analyzer should be used for indexing and searching
StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_40);
}
StandardAnalyzer 이 친구는 아래와 같이 lucene-analyzers-common에 포함이 되어있습니다.
무엇을 하는 친구인지~~api를 살펴보겠습니다.^-^/
http://lucene.apache.org/core/4_0_0/analyzers-common/
위의 그림을 보시면 StopwordAnalyzerBase를 extends 받아서 구현을 한 것을 알 수 있습니다.
음..이부분을 이해하려면 먼저 색인에 대해서 조금 더 학습을 해야 할 것 같습니다.
용어를 대충 보면 Tokenizer, Filter 등등이 보이네요 그리고 Stopword가 확~~눈에 띄이네요 불용어??
그러면 생각해보면...
무엇을 Tokenizer하고 Filter한다는 걸까요????
제가 보기에는...수집(Crawler)되어 나온 SCD(전형화된 문서)를 Parsing 하고 그것을 형태소분석을 통해 색인파일을 만드는 것 같습니다.
제가 생각하는 것이 맞는지는 소스를 차근차근 따라해보면 알 수 있을 것 같네요 ㅎㅎㅎ
테스트를 하면서 정리를 하는 것이라서..끝까지 읽어주세요 ㅋㅋㅋ;;;
2012/08/28 - [OpenSource/Lucene] - [Study_1회차] Lucene이란??
에 보시면..아래와 같이 주요 클래스들이 있습니다. 오래된 루씬 책에서 발췌한 것이라 틀릴수도있겠지만 검색엔진의 틀은 변하지 않을테니
그냥 요정도로 봐도 무관 할 것 같습니다. ㅎㅎㅎ
색인 주요 클래스 |
검색 주요 클래스 |
IndexWriter |
IndexSearcher |
Directory |
Term |
Analyzer |
Query |
Document |
TermQuery |
Field |
Hits |
색인 주요 클래스에 대해서 알아보도록 하겠습니다.
1) IndexWriter : 이 친구를 사용해야 새로운 색인을 만들 수 있고, 색인에 문서를 추가하는 작업을 합니다.
즉, 루씬의 색인에 문서를 추가하는 기능을 전담하는 친구입죠! 소중한 친구네요 ㅎㅎ; 더욱 친해져야겠어요ㅋㅋ
2) Directory : 색인도 저장이 되어야 합니다. 그래서 이 친구의 역할은 루씬의 색인 파일 저장을 책임 집니다.
루씬은 기본적으로 RAMDirectory와 FSDirectory를 지원 합니다.
ㄴ RAMDirectory : 컴퓨터의 메인 메모리를 색인 저장소로 사용 합니다.
ㄴ FSDirectory : 디스크의 파일 시스템에 색인을 저장 합니다.
위의 2가지는 기본적으로 제공되어지는 것이며 DB를 색인저장소로 할 수도있습니다.
하둡 교육을 들으면서 검색 생각이 났었습니다. 하둡같은 분산저장소에 수집 또는 색인 내용을 저장하지 않을까..라는 생각을 해보네요
혹은..Redis같은곳에 올릴수 있지 않을까요?? 음..해보고 싶은 것 해봐야할 것들이 생각나네요+ㅁ+/
아무튼..차근차근 해보도록 하겠습니다! ^-^/ 재밌는 개발 ㄱ ㄱ ㄱ ~~
3) Analyzer : 루씬은 문자열 즉, 일반 텍스트만을 색인하며, 분석하도록 지정한 모든 텍스트는 분석기(Analyzer)를 거칩니다.
색인이라는 것이 형태소분석기를 통해 나온 파일 이기때문이죠! 여기서 텍스트라는 것은 분석할 대상인 것 입니다.
예를 들어서 텍스트 즉, 수집된 내용이 <title>박태하 천재</titie> 에서 파싱을 통해 텍스트를 뽑아내 박태하 천재를
형태소분석(ex. 복합명사 추출)을 해보면 박태하, 천재, 박태하 천재 이런식으로 분석기를 통해 뽑아 낼수 있습니다.
4) Document : 루씬은 색인에 추가할 데이터는 모두 문서, 즉 Document 단위로 처리 합니다.
간단히 말해 루씬의 색인에 데이터를 추가하는 방법은 Document 클래스를 사용하는 방법밖에 없다는 말입니다.
5) Field : IndexWriter를 통해 색인에 추가하려는 내용은 Document 인스턴스에 Field의 형태로 추가해야 한다.
검색을 할경우에도 어느 필드에서 찾을지 지정하도록 되어있다.
4가지의 필드를 제공한다.
ㄴ Keyword, UnIndexed, UnStored, Text
필드 생성 메소드 |
분석 여부 |
검색 가능 여부 |
저장 여부 |
Field.Keyword(String, String) |
N |
Y |
Y |
Field.Keyword(String, Date) |
N |
Y |
Y |
Field.UnIndexed(String, String) |
N |
N |
Y |
Field.UnStored(String, String) |
Y |
Y |
N |
Field.Text(String, String) |
Y |
Y |
Y |
Field.Text(String, Reader) |
Y |
Y |
N |
수집 된 어느 내용들(ex. title, contents, date, name, id) 중에 어떤 Field들을 색인 또는 검색 대상으로 잡아야할지를 결정해주는 역할을 한다.
우리가 테스트 해볼 소스는 인덱스 및 검색까지 하는 소스 입니다. 그러므로 검색(Search) 부분도 살펴보도록 하겠습니다.
검색 주요 클래스
1) IndexSearcher : IndexWriter로 만들어둔 색인file을 이용해 검색하려면 이 친구를 이용하면 됩니다!
이 친구는 검색만을 담당하기 때문에 색인을 '읽기 전용' 으로 사용 합니다. 즉, 검색 중에도 IndexWriter를 통해
색인에 문서를 얼마든지 추가할 수 있습니다.
가장 간단한 검색메소드는 질의(Query)를 입력 받아 결과를 도출하는 메소드 입니다.
2) Term : 텀..이 친구는 색인의 내부에서 단어를 의미하는 가장 기본적인 요소 입니다.
하나의 텀은 하나의 이름과 하나의 단어로 이루어지며, 색인 과정에서 Analyzer가 분석해준 토큰을 이용해
IndexerWriter가 텀을 만들어낸다. 색인파일 내부구조에서 사용되어짐을 알 수있다.
예를들어 어떤 단어를 검색하려고 했을 때, 다음과 같이 텀을 하나 만들어서 TermQuery로 IndexSearcher에서
검색할 수 있다.
Query q = new TermQuery(new Term("contents", "parktaeha"));
Hits hits = is.search(q);
위의 소스를 잠시보면 "parktaeha" 요녀석이 검색어가 되겠다.
&q=%EB%B0%95%ED%83%9C%ED%95%98 이런 형식으로다가 올 것이다.
그리고 contents라는 필드에 "parktaeha"라는 단어가 포함하는 모든 문서를 검색하라는뜻이다.
3) Query : IndexSearcher를 통해 색인을 검색하려면 검색어가 필요하다. 루씬에서 검색어를 지정할 때는 Query를 사용한다.
차근차근 더 학습하면서 알아보도록 하겠습니다!
4) TermQuery : TermQuery는 루씬이 지원하는 Query의 하위 클래스 중 다른 모든 것들의 기본인 클래스이다.
이 친구는 특정 이름의 필드에 지정한 단어가 포함되어 있는 문서를 찾을 때 사용한다.
5) Hits : IndexSearcher에서 Query를 통해 검색한 결과는 Hits로 수집할 수 있다. Hits는 결과문서의 본문 내용을
모두 포함하지는 않으며, 결과 문서에 대한 ID 목록만을 갖는다. 즉, 필요할 때 결과에 해당하는 문서를 가져오는게
훨씬 효과적이기 때문이다.
여기까지 이론적으로 살펴 보았습니다.
소스를 직접 보고 고쳐보면서 더욱 더 자세히 보겠습니다.
Junit으로 돌리기는 하지만 유닛테스트는 아닙니다..^^;;; 우선은 설계를 하지 않고 그냥 소스 가져온것을 돌려보겠습니다.
lucene 패키지를 하나 만드시고~아래의 소스를 돌려보도록 하죠!
package lucene; import static org.junit.Assert.*; import java.io.IOException; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.store.LockObtainFailedException; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.Version; import org.junit.Test; public class IndexTest { @Test public void makeIndexTest() throws CorruptIndexException, LockObtainFailedException, IOException { // 0. Specify the analyzer for tokenizing text. // The same analyzer should be used for indexing and searching StandardAnalyzer analyzer = new StandardAnalyzer(Version.LUCENE_40); // 1. create the index Directory index = new RAMDirectory(); IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_40, analyzer); IndexWriter w = new IndexWriter(index, config); addDoc(w, "Lucene in Action", "193398817"); addDoc(w, "Lucene for Dummies", "55320055Z"); addDoc(w, "Managing Gigabytes", "55063554A"); addDoc(w, "The Art of Computer Science", "9900333X"); w.close(); // 2. query String querystr = "Lucene"; // the "title" arg specifies the default field to use // when no field is explicitly specified in the query. Query q = null; try { q = new QueryParser(Version.LUCENE_40, "title", analyzer).parse(querystr); } catch (org.apache.lucene.queryparser.classic.ParseException e) { e.printStackTrace(); } // 3. search int hitsPerPage = 10; IndexReader reader = DirectoryReader.open(index); IndexSearcher searcher = new IndexSearcher(reader); TopScoreDocCollector collector = TopScoreDocCollector.create(hitsPerPage, true); searcher.search(q, collector); ScoreDoc[] hits = collector.topDocs().scoreDocs; // 4. display results System.out.println("Found " + hits.length + " hits."); for (int i = 0; i < hits.length; ++i) { int docId = hits[i].doc; Document d = searcher.doc(docId); System.out.println((i + 1) + ". " + d.get("title") + "\t" + d.get("title")); } // reader can only be closed when there // is no need to access the documents any more. reader.close();
}
private static void addDoc(IndexWriter w, String title, String isbn) throws IOException { Document doc = new Document(); doc.add(new TextField("title", title, Field.Store.YES)); // use a string field for isbn because we don't want it tokenized doc.add(new StringField("isbn", isbn, Field.Store.YES)); w.addDocument(doc); } } |
디렉토리 구조 - 아래와 같이 IndexTest.java를 Junit 기반으로 만들어 줍니다.
결과는 아래와 같습니다.
이제~~대망의 소스를 보도록 하겠습니다!!
소스를 그냥 보는것이 아니라~ main쪽으로 소스를 분리 해보겠습니다.
1) 테스트 소스
package lucene; import org.apache.lucene.search.Query; import org.junit.Assert; import org.junit.Test; import kr.pe.constr.lucene.Indexer; public class IndexTest {
@Test public void makeIndexerTest(){ Indexer idx = new Indexer(); Query q = idx.queryExcute(); Assert.assertNotNull(idx); Assert.assertNotNull(q); System.out.println("query=>"+q.toString());
idx.search(q);
}
} |
2) 메인쪽 소스 - 예외처리 요런것은 일단 패스~~
package kr.pe.constr.lucene; import java.io.IOException; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.StringField; import org.apache.lucene.document.TextField; import org.apache.lucene.index.CorruptIndexException; import org.apache.lucene.index.DirectoryReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.queryparser.classic.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TopScoreDocCollector; import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.util.Version; public class Indexer { private StandardAnalyzer analyzer;
private Directory index;
// Step 01.
public Indexer(){
IndexWriter w = null;
IndexWriterConfig config;
try {
// The same analyzer should be used for indexing and searching
analyzer = new StandardAnalyzer(Version.LUCENE_40);
// 1. create the index
index = new RAMDirectory();
config = new IndexWriterConfig(Version.LUCENE_40, analyzer);
w = new IndexWriter(index, config);
addDoc(w, "Lucene in Action", "193398817");
addDoc(w, "Lucene for Dummies", "55320055Z");
addDoc(w, "Managing Gigabytes", "55063554A");
addDoc(w, "The Art of Computer Science", "9900333X");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
w.close();
} catch (CorruptIndexException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private static void addDoc(IndexWriter w, String title, String isbn) throws IOException {
Document doc = new Document();
doc.add(new TextField("title", title, Field.Store.YES));
// use a string field for isbn because we don't want it tokenized
doc.add(new StringField("isbn", isbn, Field.Store.YES));
w.addDocument(doc);
}
// step 02.
public Query queryExcute(){
// 2. query
String querystr = "Lucene";
// the "title" arg specifies the default field to use
// when no field is explicitly specified in the query.
Query q = null;
try {
q = new QueryParser(Version.LUCENE_40, "title", analyzer).parse(querystr);
} catch (org.apache.lucene.queryparser.classic.ParseException e) {
e.printStackTrace();
}
return q;
}
// step 03.
public void search(Query q){
// 3. search
int hitsPerPage = 10;
IndexReader reader = null;
ScoreDoc[] hits = null;
IndexSearcher searcher;
TopScoreDocCollector collector;
try {
reader = DirectoryReader.open(index);
searcher = new IndexSearcher(reader);
collector = TopScoreDocCollector.create(hitsPerPage, true);
searcher.search(q, collector);
hits = collector.topDocs().scoreDocs;
display(hits, searcher);
} catch (CorruptIndexException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private void display(ScoreDoc[] hits, IndexSearcher searcher){
// 4. display results
System.out.println("Found " + hits.length + " hits.");
for (int i = 0; i < hits.length; ++i) {
int docId = hits[i].doc;
Document d;
try {
d = searcher.doc(docId);
System.out.println((i + 1) + ". " + d.get("title") + "\t" + d.get("isbn"));
} catch (CorruptIndexException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} |
다시 JUnit을 돌려봤을 때 아래와 같이 녹색불이 뜨며 결과는 같아야 하겠죵~!
결과
이제 소스는 색인과 검색 둘다 있지만 우선! 색인에 대해서 알아보도록 합니다~
TEST소스에서 Indexer idx = new Indexer();를 수행하면 생성자가 발동하겠죠!
색인을 생성하는 부분 입니다. 굳이 생성자에서 할 필요는 없죵~
public Indexer(){
IndexWriter w = null; IndexWriterConfig config;
try {
// The same analyzer should be used for indexing and searching analyzer = new StandardAnalyzer(Version.LUCENE_40); // Filters StandardTokenizer with StandardFilter, LowerCaseFilter and StopFilter, using a list of English stop words. // 1. create the index index = new RAMDirectory();
config = new IndexWriterConfig(Version.LUCENE_40, analyzer);
w = new IndexWriter(index, config);
addDoc(w, "Lucene in Action", "193398817"); addDoc(w, "Lucene for Dummies", "55320055Z"); addDoc(w, "Managing Gigabytes", "55063554A"); addDoc(w, "The Art of Computer Science", "9900333X");
} catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { w.close(); } catch (CorruptIndexException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
}
} // 색인에 추가할 데이터 Document단위로 넣어주기 private static void addDoc(IndexWriter w, String title, String isbn) throws IOException { Document doc = new Document(); System.out.println("title->"+title+"=== isbn->"+isbn); doc.add(new TextField("title", title, Field.Store.YES)); // use a string field for isbn because we don't want it tokenized doc.add(new StringField("isbn", isbn, Field.Store.YES)); w.addDocument(doc); } |
// step 02. 질의분석!!
public Query queryExcute(){ // 2. query String querystr = "Lucene"; // the "title" arg specifies the default field to use // when no field is explicitly specified in the query. Query q = null; try { q = new QueryParser(Version.LUCENE_40, "title", analyzer).parse(querystr); // 질의 분석 System.out.println("q="+q); } catch (org.apache.lucene.queryparser.classic.ParseException e) { e.printStackTrace(); } return q; } |
// step 03. 색인을 가져와 검색하기
public void search(Query q){ // 3. search int hitsPerPage = 10; IndexReader reader = null; ScoreDoc[] hits = null; IndexSearcher searcher; TopScoreDocCollector collector; try { reader = DirectoryReader.open(index); // index reading searcher = new IndexSearcher(reader); // Creates a searcher searching the provided index. collector = TopScoreDocCollector.create(hitsPerPage, true); // 어떻게 가져올 것인가? searcher.search(q, collector); // 색인에 대해 search(), Lower-level search API. return void hits = collector.topDocs().scoreDocs; // docId를 꺼내 hits[]에 넣어준다. display(hits, searcher); } catch (CorruptIndexException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ try { reader.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
} |
// 출력하기
private void display(ScoreDoc[] hits, IndexSearcher searcher){ // 4. display results System.out.println("Found " + hits.length + " hits."); for (int i = 0; i < hits.length; ++i) { int docId = hits[i].doc; // return docId number System.out.println("docId="+docId); Document d; try { d = searcher.doc(docId); // return 실제 문서 document object return System.out.println((i + 1) + ". " + d.get("title") + "\t" + d.get("isbn")); } catch (CorruptIndexException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
} } |
주석으로 설명을 하기는 하였으나..더욱 더 자세히 알아보시려면 직접 api를 찾아가면서 테스트를 해보시는게 좋을 것 같습니다^-^
여기까지~~~해보고 다음편으로! 색인 저장소를 파일시스템으로 바꿔보겠습니다. 배웠으니 이제는 익힐 차례!!! ㅎㅎㅎ
다음편에 뵙겠습니다 (- - (_ _ * 꾸벅~
소스는 github에 올려놨습니다~
https://github.com/acetaeha/rndStartService/tree/luceneBasicSource01
참고 URL :
http://lucene.apache.org/core/4_0_0/
http://lucene.apache.org/core/4_0_0/core/org/apache/lucene/search/class-use/ScoreDoc.html
http://lucene.apache.org/core/4_0_0/core/org/apache/lucene/search/IndexSearcher.html#doc(int)
http://lucene.apache.org/core/4_0_0/core/org/apache/lucene/search/Collector.html
http://lucene.apache.org/core/4_0_0/core/org/apache/lucene/index/IndexReader.html
http://lucene.apache.org/core/4_0_0/core/org/apache/lucene/document/StringField.html
http://lucene.apache.org/core/4_0_0/core/org/apache/lucene/document/TextField.html
http://lucene.apache.org/core/4_0_0/analyzers-common/
http://lucene.apache.org/core/
- END -
'OpenSource > Lucene' 카테고리의 다른 글
[색인&검색] 음.. (0) | 2014.06.19 |
---|---|
[색인&검색]루씬 - FSDirectory() 사용 (0) | 2014.06.16 |
다시 시작하는 루씬!!! (0) | 2014.06.03 |
[Lucene 7회 차] about index (2) | 2012.10.04 |
[Lecene 6회차] Welcome to New face & Analyze about Index. (0) | 2012.09.20 |