在 Linux 環境 Python 下開發全文索引(轉)

ba發表於2007-08-11
在 Linux 環境 Python 下開發全文索引(轉)[@more@]  隨著資訊量的增長,高效地定位特定資訊變得越來越重要。本專欄將探討全文索引領域,並集中討論作者的公共域 indexer 模組。
  
  本專欄將探討我的 Python 專案:indexer 模組,並且還有一項特殊目的:我和你們一樣也一直盡力學習更多知識,為此,本專欄歡迎讀者提出自己的意見和想法。您的投稿將會在專案中或在未來的專欄裡出現。通常,我希望本專欄能反映出讀者的興趣和知識而不僅僅是我一個人的。這就讓我們開始吧。
  
  我希望 indexer 模組,即使是早期的版本也能證明給讀者是有用的。此全文 indexer 可作為單獨的實用工具或大型專案的一個模組。其設計說明了可重複使用的物件導向編碼的準則和文字索引(極其精妙而豐富的主題)的基本原理。儘管 Knuth曾經忠告我們:“不成熟的最佳化是問題的根源”,但索引的目的在於快速地找到資訊,因此本專欄同時也將討論效能問題。
  
  indexer 模組大約是來源於某大學希望尋求一種好的方法來查詢大量文字和 HTML 幫助文件。也是我想利用多年積累下來的信件、新聞和寫作檔案的一個小動機。很簡單, indexer 讓使用者定位文件時,很難甚至無法以規則表示式來指定搜尋條件,並且快速的執行。雖然有些商業軟體或免費工具能完成類似的工作,但大多都是針對 Web 索引。他們(即使是透過 LOGALHOST 也)需要 CGI 介面,安裝和使用都相當困難,其中只有唯一一個為 Python 設計的軟體(有不同的側重點)。另一方面,indexer 必須設計成易於使用。當然,有些更早期並且更復雜的軟體功能更強大,但 indexer 設計目的是在不失其簡單易用特性的前提下擴充功能。
  
  關於搜尋引擎
  本專欄的名稱“全文 indexer”隸屬於另一個更寬泛的類別 -- “搜尋引擎”。對大多數使用者,搜尋引擎通常是用來定位 URL 和 WWW 的。的確,WWW 肯定是人類有史以來最大的公共文件庫,它的非正規組織結構使其非常需要有好的搜尋引擎。而且,其他文件集 -- 特別是包括本地硬碟上不斷增加的檔案 -- 也將獲益於搜尋引擎。分級檔案系統和檔案命名規範是好方法,但他們的發展還遠遠不夠;有些時候您只需找到 包含某條資訊的文件。
  
  網際網路搜尋引擎有一半的問題在於定位其內容要被索引的文件。雖然有很多方法可以找到許多相關的 URL,但卻沒有羅列每個有效 URL 的演算法。幸運的是,當索引本地文件(正如目前版本的 indexer 這樣)時,找到所有文件非常簡單;這些文件都位於明確而可定位的地方。而當使用者進一步希望索引某些目錄的子目錄樹而非其它時,文件列表就能變得精確而無遺漏。
  
  在設計本地搜尋引擎時有兩種不同的策略。可以在搜尋時察看檔案的實際內容以判斷是否和搜尋條件相符,也可以準備一個包含每個檔案內容的資料庫,然後搜尋資料庫而不搜尋檔案本身。第一種方法的優點在於始終保持精確,始終能準確的定位在哪裡有您想要的哪些內容。這種特別方法的 最大缺點在於速度特別慢,而且如果進行許多次搜尋的話,成本很高。
  
  第二種方法的優點在於如果實施得當,它將快得多。一次搜尋傳遞能生成文件可搜尋特性的摘要,後繼搜尋就不必再次閱讀這些文件。這使得搜尋成本 更低。缺點在於,資料庫可能與檔案內容不同步,需要週期性重新索引,而且這種做法會佔用額外的空間(被索引文字的大小的 1% 到 100%,由搜尋特性和設計選擇而定)。
  
  這種特殊方法的示例有 Windows 下的 "File Find"、類 Unix 作業系統的 find 和 grep 工具(與 KDE 中的 kfind)、OS/2 中的 PMSeek.exe 工具和 "Find Object" 還有 MacOS 7 中的 "Finder"。資料庫方法包括 Microsoft Office 中的 "Fast Find",Corel Office 中的 "QuickFinder"、 MacOS 8+ 中的 "Sherlock" 還有很有侷限性的 Linux 的 locate 實用工具。BeOS 的 "Find" 是兩種方法的結合,但功能非常侷限 -- 非全文搜尋。其他作業系統也提供類似的實用工具。

  有許多不同方法來指定要搜尋的內容。以下為一些示例:
  
  詞語出現頻率指出一系列搜尋詞語在文件中的出現頻度。此處假設對於給定的搜尋,文件中被搜尋到的條件出現的比較頻繁,就屬於“更好”的匹配。
  
  布林搜尋允許出現的文字與短語之間有複雜的關係。例如 "(spam AND eggs) OR (ham AND cheese)" 中,任何括號中的組合都將符合條件,而不必包括另一頭分離的詞彙。
  
  規則表示式搜尋滿足(儘可能複雜)的模式。這種方法更有利於查詢高度結構化的資料,而不適合識別概念化的內容。
  
  短語搜尋只是允許多詞條件的搜尋。規則表示式搜尋雖然能完成相同的搜尋,但更簡單的系統也能做到。
  
  近似搜尋查詢一系列相互“接近”的詞語或短語。有多接近通常是一項查詢選項。
  
  詞幹搜尋,有時候搜尋詞幹而不是整個單詞。將 "run"、"runner"、"running" 和 "runs" 當作相關詞彙來考慮而將他們全部找到,而不是試圖單獨搜尋符合條件的每個詞語,這種做法有時非常有效。
  
  概念搜尋透過辨別具有類似涵義的詞語來查詢具有類似主題的文件。此類搜尋需要在搜尋引擎中整合某些詞典。
  
  探測法搜尋可以查詢不規則拼寫,特別是針對英語。搜尋並不使用單詞在文中的拼寫,而是根據發音將其轉換為規則拼寫。然後將轉換後的文字與轉換後的搜尋條件做比較。
  
  關於 indexer
  indexer 使用出現的詞語的資料庫。版本 0.1X (alpha 測試版)只能搜尋全文單詞結構固定的文件。作為可選項,搜尋能將符合條件的文件按照搜尋詞語的出現頻率並且對比文件長度來排列。 indexer 能以不同方式進行擴充套件,某些擴充套件方式邏輯化而直接,其它則更為複雜。
  
  布林能力很簡單而且也已經在按計劃實施。由於 indexer 跟蹤哪些文件中包含哪些單詞(和出現次數),因此如果要在規則中加入邏輯或者根據搜尋詞語出現或不出現來包括檔案是很容易實現的。實際上,目前的功能本質上預設為在每個查詢詞語中間加 AND。(我的直覺是大多數現行的搜尋都是這種 "x AND y AND z" 方式的搜尋。)
  
  規則表示式幾乎無法加入到 indexer 中,據我所知沒有一個資料庫搜尋系統具有哪些檔案包含符合哪些規則表達的內容的列表。實用起見,規則表示式需要以特殊方式處理 -- 為此我們使用 grep。
  
  短語和近似搜尋現在並未實行,但實施並不困難。基本上,除了每個檔案中的每個詞語的出現頻率,還必須收集每個檔案中詞語出現的偏移列表。根據該列表,我們能推論短語和近似度。然而,我認為這樣做會大幅增加資料庫的大小和搜尋時間。
  
  概念上,詞幹和探測法搜尋可能已在現有的基本框架之中,但其需要花費很大的工作量。這種方法的確能減小資料庫大小,因為只需儲存正則形式而不必儲存變化形式,但詞語轉換需要消耗外部類屬詞典和變化規則形態。
  
  indexer 程式設計
  建議讀者下載 indexer 原始碼 (請參閱本文後的參考資料)。它只有一個檔案,而且有詳盡的註解,相當於一本程式設計書籍。
  
  以下是有關程式結構的備註。請注意文件是編號的,每個文件都關聯一個整數 "fileid"。
  
  indexer 有一個 Python 詞典,其關鍵字為詞語,其值本身又是詞典,這個詞典的關鍵字為 "fileid",其值為 "fileid" 指定的詞語在檔案中的出現次數。Python 詞典的查詢效率很高,而且連結 "fileid" 和實際檔名的附加工作相對很少。
  
  從大體上說,indexer 包含一個被稱為 GenericIndexer 的抽象類。在 GenericIndexer 中定義的最重要的方法為 add_files() 和 find()。如果儲存機制需要最終化(大部分都需要)的話, save_index() 方法也很重要。
  
  使 GenericIndexer 抽象的原因是它不能被例項化;而其子類只要完成進一步的工作後就能被例項化。術語"抽象"來源於 C++,在 C++ 中它可以是類的正規宣告。在 Python 中並沒有該正規宣告,而類的“抽象”只是類的開發者給其使用者的建議。Python 是這樣處理的 -- 不強制資料隱藏、成員可見、繼承需求和類似的做法,而是遵從在何時完成的規範。然而, GenericIndexer 能很好的強制執行其建議,因為它的許多方法由 "raise NotImplementedError" 行組成。特別是 __init__() 呼叫 "NotImplemented" 的方法之一的 load_index()。

  GenericIndexer 的派生在實際儲存索引中有不同的實現方法。其中最實用的是 ZPickleIndexer,將 zlib 和 cPickle 合併,儲存壓縮的詞典。一方面為了興趣,一方面又由於令人吃驚的效能測試結果(請參閱基準測試模組),我建立了許多其它 SomethingIndexer 類。如果願意,shelve、XML、flat-file 和 cPickle 的類都已經具備。建立 NullIndexer 派生,高效地將每個索引存放到 /dev/null 是可能的(雖然有些無意義),而且每次開始搜尋時都需要重新索引。
  
  在提供 load_index() 和 save_index() 實現的同時,具體的(與“抽象”相反) SomethingIndexer 類從“mixin 類”中繼承 SomethingSplitter。目前,這樣的類只有 TextSplitter,但是其他相似的類會不斷出現。SomethingSplitter 提供非常重要的 splitter() 方法,它獲得一個文字字串並把它分割成組成其的單詞。這種方法比想象的要難得 多;區分是或者不是一個單詞是非常微妙的。以後,我希望建立 TextSplitter 的派生,比如 XMLSplitter、TeXSplitter、PDFSplitter 和相似的類。而現在我們試圖以相對原始的方法查詢文字單詞。
  
  “mixin 類”是個有趣的概念,也常常是個很好的設計選擇。類似 TextSplitter 的類(或其未來的派生類)往往會包含針對許多具體派生類的有用功能。和抽象類相似,mixin 類不能直接被例項化(其有效性與禁止不同,在 mixin 中我並沒有提出 NotImplementedError。)然而,與抽象類不同的是

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-947550/,如需轉載,請註明出處,否則將追究法律責任。

相關文章