מבוא לאפאצ'י לוסין

1. סקירה כללית

Apache Lucene הוא מנוע חיפוש בטקסט מלא בו ניתן להשתמש משפות תכנות שונות.

במאמר זה ננסה להבין את מושגי הליבה של הספרייה וליצור יישום פשוט.

2. הגדרת Maven

כדי להתחיל, בואו הוסף תחילה תלות נחוצה:

 org.apache.lucene lucene-core 7.1.0 

הגרסה האחרונה תוכל למצוא כאן.

כמו כן, לצורך ניתוח שאילתות החיפוש שלנו, נצטרך:

 org.apache.lucene lucene-queryparser 7.1.0 

בדוק את הגרסה האחרונה כאן.

3. מושגי ליבה

3.1. אינדקס

במילים פשוטות, לוסין משתמשת ב"אינדקס הפוך "של נתונים - במקום למפות דפים למילות מפתח, הוא ממפה מילות מפתח לדפים ממש כמו מילון מונחים בסוף כל ספר.

זה מאפשר תגובות מהירות יותר לחיפוש, שכן הוא מחפש באינדקס, במקום לחפש טקסט ישירות.

3.2. מסמכים

כאן, מסמך הוא אוסף של שדות, ולכל שדה יש ​​ערך המשויך אליו.

מדדים מורכבים בדרך כלל ממסמך אחד או יותר, ותוצאות החיפוש הן קבוצות של מסמכים המתאימים ביותר.

זה לא תמיד מסמך טקסט רגיל, זה יכול להיות גם טבלת מסד נתונים או אוסף.

3.3. שדות

למסמכים יכולים להיות נתוני שדה, כאשר שדה הוא בדרך כלל מפתח המכיל ערך נתונים:

כותרת: טובת גוף התה: דיון על טובת שתיית תה צמחים ...

שימו לב שכאן כותרת ו גוּף הם שדות וניתן לחפש אותם יחד או בנפרד.

3.4. אָנָלִיזָה

ניתוח הוא המרת הטקסט הנתון ליחידות קטנות ומדויקות לצורך חיפוש קל.

הטקסט עובר פעולות שונות של חילוץ מילות מפתח, הסרת מילים ופיסוק נפוצים, שינוי מילים באותיות קטנות וכו '.

לשם כך, ישנם מנתחים מובנים מרובים:

  1. Analyzer סטנדרטי - מנתח על סמך דקדוק בסיסי, מסיר מילות עצירה כמו "a", "an" וכו 'גם ממיר באותיות קטנות
  2. SimpleAnalyzer - שובר את הטקסט על סמך תו ללא אותיות וממיר באותיות קטנות
  3. WhiteSpaceAnalyzer - שובר את הטקסט על בסיס רווחים לבנים

ישנם מנתחים נוספים שניתן להשתמש בהם ולהתאים אותם גם כן.

3.5. מחפש

לאחר בניית אינדקס, אנו יכולים לחפש את האינדקס באמצעות a שאילתא ו מחפש אינדקס. תוצאת החיפוש היא בדרך כלל קבוצת תוצאות המכילה את הנתונים שאוחזרו.

שים לב ש- אינדקס סופר אחראי על יצירת האינדקס ו- מחפש אינדקס לחיפוש באינדקס.

3.6. תחביר שאילתות

Lucene מספק תחביר של שאילתות דינמי מאוד וקל לכתיבה.

כדי לחפש טקסט חופשי, פשוט נשתמש בטקסט חוּט כמו השאילתה.

כדי לחפש טקסט בשדה מסוים, נשתמש ב:

שדה שם: טקסט למשל: כותרת: תה

חיפושי טווח:

חותמת זמן: [1509909322,1572981321] 

אנו יכולים גם לחפש באמצעות תווים כלליים:

לִשְׁתוֹת

היה מחפש דמות אחת במקום התו הכללי "?"

d * k

מחפש מילים המתחילות ב- "d" ומסתיימות ב- "k", עם תווים מרובים בין לבין.

חַד*

ימצא מילים המתחילות ב- "uni".

אנו עשויים גם לשלב שאילתות אלה וליצור שאילתות מורכבות יותר. וכלול מפעיל לוגי כמו AND, NOT, OR:

כותרת: "תה בארוחת הבוקר" ו- "קפה"

למידע נוסף על תחביר השאילתות כאן.

4. יישום פשוט

בואו ליצור יישום פשוט ונוסיף כמה מסמכים לאינדקס.

ראשית, ניצור אינדקס בזיכרון ונוסיף לו כמה מסמכים:

... Memory DirectoryIndex = RAMDirectory חדש (); מנתח StandardAnalyzer = StandardAnalyzer חדש (); IndexWriterConfig indexWriterConfig = IndexWriterConfig חדש (מנתח); כותב IndexWriter = IndexWriter חדש (memoryIndex, indexWriterConfig); מסמך מסמך = מסמך חדש (); document.add (TextField חדש ("כותרת", כותרת, Field.Store.YES)); document.add (TextField חדש ("גוף", גוף, Field.Store.YES)); writter.addDocument (מסמך); writter.close (); 

כאן, אנו יוצרים מסמך עם שדה טקסט והוסף אותם לאינדקס באמצעות ה- IndexWriter. הטיעון השלישי ב- שדה טקסט constructor מציין אם יש לאחסן את ערך השדה או לא.

מנתחים משמשים לפיצול הנתונים או הטקסט לגושים, ואז סינון מילות העצירה מהם. מילות עצירה הן מילים כמו 'a', 'am', 'is' וכו '. אלה תלויים לחלוטין בשפה הנתונה.

לאחר מכן, בואו ניצור שאילתת חיפוש ונחפש באינדקס את המסמך שנוסף:

רשימת ציבורי searchIndex (String inField, String queryString) {Query query = new QueryParser (inField, analyzer) .parse (queryString); IndexReader indexReader = DirectoryReader.open (memoryIndex); מחפש אינדקס Searcher = IndexSearcher חדש (indexReader); TopDocs topDocs = searcher.search (שאילתה, 10); רשימת מסמכים = ArrayList חדש (); עבור (ScoreDoc scoreDoc: topDocs.scoreDocs) {document.add (searcher.doc (scoreDoc.doc)); } להחזיר מסמכים; }

בתוך ה לחפש() השיטה טיעון המספר השלם השני מציין כמה תוצאות חיפוש מובילות עליו להחזיר.

עכשיו בואו לבדוק את זה:

@Test הציבורי בטל שניתןSearchQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = InMemoryLuceneIndex חדש (RAMDirectory חדש (), StandardAnalyzer חדש ()); inMemoryLuceneIndex.indexDocument ("שלום עולם", "עולם שלום כלשהו"); רשימת מסמכים = inMemoryLuceneIndex.searchIndex ("גוף", "עולם"); assertEquals ("שלום עולם", documents.get (0) .get ("כותרת")); }

כאן, אנו מוסיפים מסמך פשוט לאינדקס, עם שני שדות 'כותרת' ו'גוף ', ואז ננסה לחפש אותו באמצעות שאילתת חיפוש.

6. שאילתות לוסין

מכיוון שכעת נוח לנו עם יסודות האינדקס והחיפוש, הבה ונעמיק מעט יותר.

בסעיפים קודמים ראינו את תחביר השאילתה הבסיסי וכיצד להמיר אותו ל- שאילתא למשל באמצעות QueryParser.

לוסין מספקת גם יישומים קונקרטיים שונים:

6.1. TermQuery

א טווח היא יחידה בסיסית לחיפוש, המכילה את שם השדה יחד עם הטקסט לחיפוש.

TermQuery הוא הפשוט ביותר מכל השאילתות המורכב ממונח יחיד:

@Test public void givenTermQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = InMemoryLuceneIndex חדש (RAMDirectory חדש (), StandardAnalyzer חדש ()); inMemoryLuceneIndex.indexDocument ("פעילות", "פועל במסלול"); inMemoryLuceneIndex.indexDocument ("פעילות", "מכוניות פועלות על הכביש"); מונח מונח = מונח חדש ("גוף", "פועל"); שאילתת שאילתה = TermQuery חדש (מונח); רשימת מסמכים = inMemoryLuceneIndex.searchIndex (שאילתה); assertEquals (2, documents.size ()); }

6.2. PrefixQuery

לחיפוש מסמך עם מילה "מתחילה":

@Test public void givenPrefixQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (RAMDirectory חדש (), StandardAnalyzer חדש ()); inMemoryLuceneIndex.indexDocument ("מאמר", "מבוא לוסין"); inMemoryLuceneIndex.indexDocument ("מאמר", "מבוא ללוסין"); מונח מונח = מונח חדש ("גוף", "מבוא"); שאילתת שאילתות = PrefixQuery חדש (מונח); רשימת מסמכים = inMemoryLuceneIndex.searchIndex (שאילתה); assertEquals (2, document.size ()); }

6.3. Query Wildcard

כפי שהשם מרמז, אנו יכולים להשתמש בתווים כלליים "*" או "?" לחיפוש:

// ... מונח מונח = מונח חדש ("גוף", "מבוא *"); שאילתת שאילתות = WildcardQuery חדש (מונח); // ...

6.4. PhraseQuery

הוא משמש לחיפוש רצף של טקסטים במסמך:

// ... inMemoryLuceneIndex.indexDocument ("ציטוטים", "ורד בכל שם אחר היה מריח כמו מתוק."); שאילתת שאילתות = PhraseQuery חדש (1, "גוף", BytesRef חדש ("ריח"), BytesRef חדש ("מתוק")); רשימת מסמכים = inMemoryLuceneIndex.searchIndex (שאילתה); // ...

שימו לב כי הטיעון הראשון של PhraseQuery בונה נקרא לשפוך, שהוא המרחק במספר המילים, בין המונחים שיש להתאים.

6.5. FuzzyQuery

אנו יכולים להשתמש בזה בעת חיפוש אחר משהו דומה, אך לא בהכרח זהה:

// ... inMemoryLuceneIndex.indexDocument ("מאמר", "פסטיבל ליל כל הקדושים"); inMemoryLuceneIndex.indexDocument ("קישוט", "קישוטים לליל כל הקדושים"); מונח מונח = מונח חדש ("גוף", "hallowen"); שאילתת שאילתות = FuzzyQuery חדש (מונח); רשימת מסמכים = inMemoryLuceneIndex.searchIndex (שאילתה); // ...

ניסינו לחפש את הטקסט "ליל כל הקדושים", אך עם האיות שגוי.

6.6. BooleanQuery

לפעמים נצטרך לבצע חיפושים מורכבים, בשילוב שניים או יותר של שאילתות שונות:

// ... inMemoryLuceneIndex.indexDocument ("יעד", "מכונית סינגפור בלאס וגאס"); inMemoryLuceneIndex.indexDocument ("נוסעים בסינגפור", "אופני רכבי אוטובוס"); מונח מונח 1 = מונח חדש ("גוף", "סינגפור"); מונח מונח 2 = מונח חדש ("גוף", "מכונית"); TermQuery query1 = חדש TermQuery (term1); TermQuery query2 = TermQuery חדש (term2); BooleanQuery booleanQuery = BooleanQuery.Builder חדש () .add (query1, BooleanClause.Occur.MUST) .add (query2, BooleanClause.Occur.MUST) .build (); // ...

7. מיון תוצאות חיפוש

אנו עשויים גם למיין את מסמכי תוצאות החיפוש על בסיס שדות מסוימים:

@Test ציבורי בטל givenSortFieldWhenSortedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = InMemoryLuceneIndex חדש (RAMDirectory חדש (), StandardAnalyzer חדש ()); inMemoryLuceneIndex.indexDocument ("גאנגס", "נהר בהודו"); inMemoryLuceneIndex.indexDocument ("מקונג", "נהר זה זורם בדרום אסיה"); inMemoryLuceneIndex.indexDocument ("אמזון", "נהר יער הגשם"); inMemoryLuceneIndex.indexDocument ("הריין", "שייך לאירופה"); inMemoryLuceneIndex.indexDocument ("הנילוס", "הנהר הארוך ביותר"); מונח מונח = מונח חדש ("גוף", "נהר"); שאילתת שאילתות = WildcardQuery חדש (מונח); SortField sortField = חדש SortField ("כותרת", SortField.Type.STRING_VAL, false); Sort sortByTitle = חדש Sort (sortField); רשימת מסמכים = inMemoryLuceneIndex.searchIndex (שאילתה, sortByTitle); assertEquals (4, document.size ()); assertEquals ("אמזון", documents.get (0) .getField ("כותרת"). stringValue ()); }

ניסינו למיין את המסמכים שנאספו לפי שדות כותרת, שהם שמות הנהרות. הוויכוח הבוליאני ל SortField קונסטרוקטור מיועד להיפוך סדר המיון.

8. הסר מסמכים מהאינדקס

בואו ננסה להסיר כמה מסמכים מהאינדקס על סמך נתון טווח:

// ... IndexWriterConfig indexWriterConfig = IndexWriterConfig חדש (מנתח); כותב IndexWriter = IndexWriter חדש (memoryIndex, indexWriterConfig); author.deleteDocuments (מונח); // ...

אנו נבדוק זאת:

@ מבחן ציבורי בטל כאשר DocumentDeletedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = InMemoryLuceneIndex חדש (RAMDirectory חדש (), StandardAnalyzer חדש ()); inMemoryLuceneIndex.indexDocument ("גאנגס", "נהר בהודו"); inMemoryLuceneIndex.indexDocument ("מקונג", "נהר זה זורם בדרום אסיה"); מונח מונח = מונח חדש ("כותרת", "כנופיות"); inMemoryLuceneIndex.deleteDocument (מונח); שאילתת שאילתה = TermQuery חדש (מונח); רשימת מסמכים = inMemoryLuceneIndex.searchIndex (שאילתה); assertEquals (0, document.size ()); }

9. מסקנה

מאמר זה היה מבוא מהיר להתחלה עם Apache Lucene. כמו כן, ביצענו שאילתות שונות ומייינו את המסמכים שאוחזרו.

כמו תמיד ניתן למצוא את הקוד לדוגמאות ב- Github.


$config[zx-auto] not found$config[zx-overlay] not found