注(2007-5-22):最新一次更新的時候,我再次研究了一下Lucene,讀完了Lucene In Action,并且實際的使用Lucene構建了一個小型的搜索系統之后,我感覺到很慚愧,因為我一直對Lucene有不滿的心理,認為它做的不好(可能受了國內的某些使用Lucene構建搜索引擎的網站的影響,因為他們構建的都很差,現在想來,可能是他們和我一樣沒有真正深入理解Lucene)。現在我才發現,Lucene的作者在大方向上考慮問題的全面比我要好很多(雖然有些功能我不知道是否真的有用)。現在我的感受是:
1)Lucene對查詢的理解十分深入,幾乎什么樣的查詢需求它都考慮到了。而對于普通的商業搜索引擎來說(比如,百度),它們只是考慮到了布爾查詢模式,遠不及Lucene里面的查詢方式多;
2)Lucene對過濾和排序的處理已經能夠滿足90%的要求了。過濾可以把不需要的結果刪除,而排序則可以根據某個域的值來進行結果排序。基本的垂直搜索引擎,是必須這些功能的,因為這些功能能夠縮小查詢的結果集,提高用戶體驗。而百度等商業搜索引擎顯然是不需要(或者沒有提供)這些功能的。
3)Lucene的分值計算效果不錯。拋開基本的IR的那些標準的分值計算算法不說(誰都知道總體上Lucene使用了簡化的向量模型,但實際上Lucene在布爾模型查詢上面也使用了簡化的擴展布爾模型分值計算公式),Lucene支持了大部分額外的基于經驗的分值計算。舉例來說,查詢“中國北京”的時候,以下是幾個文檔的實際排序(直接解析這個查詢短語是得不到這個結果的,見第4點):
中國北京
北京中國
中國可愛北京
北京屬于中國
4)Lucene不提供真正的查詢串解析。基于文法的查詢串解析,一個嚴重的結果就是容錯太差,所以,我們幾乎可以認為Lucene并不提供實際有用的查詢串解析。所以呢,上面我們說的“中國北京”需要您自己把它解析為:
PhraseQuery query = new PhraseQuery();
query.add(new Term("name","中國"));
query.add(new Term("name","北京"));
query.setSlop(1000);
然后才會有上面的那個結果。所以,使用Lucene的第一步是提供您自己的查詢串解析器。
5)理解Lucene的算法限制。Doug Cutting不是神仙,他也無法解決算法的問題,所以呢,幾乎所有算法的限制都會在Lucene里面出現,比如,RangeQuery查詢速度極慢,Sort排序需要很大的內存來緩沖Field的值。這些并不是Doug Cutting的錯,而是算法的限制,我想應該沒有人能夠解決這類問題吧?(雖然從IR中我們知道RangeQuery可以使用B+樹等結構來加快查詢,不過,我們似乎不能夠責怪Lucene,因為Lucene使用的是緊湊的文件結構,難以支持B+樹結構) 如果您真的需要考慮這些問題的話,您可以自己修改Lucene的代碼或者提供額外的擴展。
6)理解Lucene的其他限制。在實際應用的過程中,您會發現Lucene有各種各樣的限制,在這里,我想提醒您2個問題:a)緩沖同步;b)寫/讀同步。Lucene的緩沖做的不好,您可以根據實際情況自己擴展,在擴展的時候,請時刻注意緩沖和實際數據同步的問題。寫/讀同步其實是Lucene自己的問題,在同時讀寫一個Directory的時候,寫的數據并不會馬上表現到讀上面,所以呢,要么關閉寫,要么自己緩沖寫結果。
Lucene是一個開源的基于java的搜索引擎,它只包含IR(Information Retrieve)部分。它即不是唯一的也不是最好的一個開源搜索引擎,更好的比如egothor,但是它是文檔最全面和受到關注最多的一個。Nutch是基于Lucene并加入了分布式和Crawler部分的搜索引擎。在本文中,作者試圖從掌握的知識范圍談論一下它們使用的技術和一般商業文本搜索引擎使用的技術之間的距離。因為作者水平有限,僅僅擁有2年不到的搜索研究和實踐經驗,不足之處請大家多多指教。謝謝。
1 網絡搜索引擎的構架
一個專業的網絡搜索引擎至少包含3部分,即抓取、處理和搜索。下面是它們的一般功能:
抓取:抓取(蜘蛛、爬蟲、crawler、spider等)程序負責爬行特定網絡(也可能是整個網絡),把網絡上的頁面和其它需要的文件下載到本地來。目前的難點是web2.0的普及導致的js分析和身份認證等問題。
處理:處理(分類、信息抽取、數據挖掘、classify、information extraction、data mining等)程序對抓回來的頁面進行分析,比如,對網站的內容進行分類、對新聞頁面的攣判畔⒔ 刑崛 ⒁趁婺0嬪 傘⒍愿鞲鐾 局 淶墓叵到 屑撲愕鵲取?/span>
搜索:搜索(information retrieve)程序負責把文檔填充到數據庫,然后根據查詢字符串到數據庫中尋找最相關的文檔展現給用戶。
僅僅從搜索引擎的構架來看,Lucene(Nutch)缺失的一環是信息的處理。信息的處理恰恰是整個搜索引擎中最核心的技術。
2 信息抓取
網絡信息抓取包含了網頁抓取、文本文件抓取和其它文件抓取。對于使用http(還有https)等協議的網站來說,信息抓取的主要過程是:
根據指定uri地址,進入第一個頁面;
分析頁面構成,得到超鏈接地址,把地址加入到待下載鏈接中去;
當還有未下載的鏈接時,下載對應頁面,保存頁面,并返回到第2步;
所有鏈接都下載完畢,退出。
普通的信息抓取一般可以被稱為Spider,它利用基本的html頁面分析器(比如,HtmlParser、NeckoHtml、JTidy等)來解析html頁面,得到頁面中的超鏈接。一般的說,一個Spider由以下2部分組成:
http下載器。給定一個uri地址,http下載器負責把這個地址的數據下載回來。或許大家會認為這個很容易,其實不然。網絡中的頁面,除了http協議以外,https安全協議也是十分常用的一種協議。當你要下載的數據需要認證的時候,你還需要讓你的下載器支持認證。當你下載的數據需要登錄的時候,你還需要讓你的下載器支持Cookie。所以,你的下載器僅僅支持http還是遠遠不夠的。在這點上,Nutch還差得遠。
html頁面解析器。html頁面解析器并不是支持html頁面就萬事大吉了的,現在的很多頁面其實使用的是xml的構建方法,雖然html和xml很像,但是,它們的tag名字顯然有很大差別。除此以外,wap手機類頁面也是你可能需要支持的頁面。Nutch使用的是NeckoHtml或者JTidy。實際應用表明JTidy比較一般,但是NeckoHtml具有不錯的效果。在開源的軟件中,它應該算是最好的一個html頁面解析器。然而,它和IE或者Mozilla這些瀏覽器的html parser比較起來,還是有一段距離的。除此以外,這類Spider僅僅只是把頁面分析成了DOM樹,它并不能夠對基于ajax技術的web2.0頁面進行有效處理。要想處理那些大量使用ajax技術或者大量運用js代碼的頁面數據,Spider需要的仍然是js處理能力。當然了,關于js的處理也可以被劃分到數據處理部分去。
3 信息處理
似乎在Lucene(Nutch)面前談論信息處理顯得有點不專業,因為它們壓根就不支持。但是,信息處理部分你還是可以自己編寫后嵌入到Lucene(Nutch)里面去的。信息處理話題太大,作者沒有這個膽量和水平對此妄加談論,雖然這個是作者一直研究的主要方向。
唯一可以肯定的就是,信息處理需要的知識至少有2點:1,機器學習(ML);2,自然語言理解(NLP)。前者典型的包括SVM、HMM等,后者包括HNC、知網等。筆者自信對ML有一定的理解,但是,對NLP的理解不太深入,還沒有形成一個完整的解決方案。
4 信息獲取
對信息獲取的研究已經好多好多年了,多到那個時候作者都未出生。這部分主要分為2個步驟,第一個步驟是把文檔填入到數據庫(也就是所謂的構建索引);第二個部分就是根據用戶輸入得到一系列最相關文檔(即所謂的搜索)。得到最相關文檔無外乎就是比較2個文檔的相似度。當用戶輸入一個字符串(即一個文檔)之后,搜索引擎根據這個文檔到數據庫中尋找和它最相關的文檔,然后返回給用戶。實際搜索引擎必須考慮速度的問題,不可能像理論上說的那樣去做。搜索的時候,一般有如下3步:
查詢字串解析。查詢的組織方式有很多種,比如布爾查詢等。一般商業文本搜索引擎支持的查詢大約這樣:“北京黃河 AND tom cat(OR 美麗 -祖國)jerry + mouse”。即支持AND(+)、OR(|)、-、括號和引號的復合表達式。光解析還不行啊,你還得進行額外的優化,比如,對于“北京北京北京北京”這個串來說,它和“北京”的效果是一樣的吧?Lucene在這個方面做了不少工作,只是,你仍然需要自己寫一個更接近中文和商業搜索引擎的查詢字串解析器。你可以根據需要,決定是把表達式優化為適合并行計算還是最小計算量的形式。
說到布爾查詢,本人之所以認為Lucene距離實用還有一段距離的原因也部分基于此。為什么?因為Lucence使用了java版本的Yacc來自動構建了布爾表達式的解析器,然而,它的文法過于正規,無法處理下面的字符串:
我們 AND AND AND 他們
作為一個追求完美的人,作者實在無法接受這樣無法忍受的“錯誤”。
查詢。查詢的部分沒有什么太多可以說的。有一點是,算法該優化的都沒有優化。不過,對于一個處于初級開發中,并且開發人員缺乏的Project來說,這樣就足夠了。
結果排序。Lucene使用的分值計算方法在比較接近于IR理論上使用的一些計算方法。可是,實際中的分值計算卻比這個要麻煩很多。一個例子就是Lucene使用的方法沒有考慮到查詢詞語之間的緊密位置。比如,對于“北京商店”和“北京 商店”來說,得到的前幾個結果顯然應該差別很大,這是因為前者更傾向于“北京”和“商店”這2個詞語在一起的情況。要Lucene做到這一點其實也容易,只是,它會嚴重的降低搜索速度(幾倍)。
實際應用中,還常常需要計算某些屬性的值的權重。比如,新聞搜索里面,今天的新聞是不是應該比昨天的新聞權重大一點呢?Lucene未能支持這點。
5 速度第一
對于搜索引擎來說,速度絕對是第一個需要考慮的問題。信息抓取的速度顯然不在軟件能夠解決的范圍內,它只能通過增加帶寬和多級更新策略來提高了。信息處理的速度不在本文的討論之列。信息獲取的時候,有2個地方需要考慮到速度:
索引速度。索引速度包含索引的構建速度、文檔的修改速度和文檔的刪除速度。在粗粒度上說,Nutch采用的基于MapReduce策略的分布式索引構建方法是一個不錯的構架。在細粒度上說,Lucene使用的內存緩沖和磁盤小索引合并的方法也是一個很好的構架。它們在構建索引上的表現都是令人稱道的。在文檔的刪除上,Lucene使用的標記后再刪除的方法是無可厚非的。在文檔的修改和文檔的追加上面,Lucene做的還差強人意。文檔在修改的時候,唯一的辦法就只能是先刪除然后再追加。這點顯然是不能令人滿意的。
查詢速度。Lucene(Nutch)認為分布式的查詢是不可取的,因為速度更慢。其實并不是速度慢,而是分布式查詢需要一個快速的構架,這個構架涉及到搜索引擎的所有方面。暫時不是Nutch能夠達到的。所以,Lucene(Nutch)的查詢都是在一臺機器上運行的。另外,Lucene因為它采用的分值計算方法的緣故,它不需要加載詞語在文檔中的位置信息,所以,它表面上看起來比其它搜索引擎快速了。其實是犧牲精度獲得速度,另外,它自己使用的文件結構嚴重的制約了它的速度。
沒有引入緩沖是Lucene(Nutch)的另外一個嚴重失誤。在這個構架里面,你可以自己 構建一個查詢結果緩沖(一級緩沖),但是,你很難構建一個基于詞語的索引緩沖 (二級緩沖)。這對于一個大搜索量的系統來說,是不可想象的。更何況普通商業搜 索引擎還會有第三級緩沖。
6 精度第二
如果僅僅從Lucene(Nutch)支持的查詢方式和分值計算來看,Lucene(Nutch)不存在精度的問題,因為它們都是計算了全部數據的。商業搜索引擎因為數據量過大的緣故,不得不使用一些估計的算法來減少磁盤數據的讀取,這會導致精度的略微丟失。反過來說,Lucene無法支持大規模數據。
7 效率第三
這里說的效率主要是空間效率。比如,程序的內存占用和磁盤占用。Lucene使用了zlib壓縮技術來壓縮文檔內容,還有使用了簡單的壓縮整數的方法。但是,它使用的壓縮方法還是太少了,它少用壓縮的結果是:1)文件結構和代碼簡單;2)查詢速度變慢;3)索引segment合并快速;4)磁盤占用增加。同時,它幾乎不用壓縮也和它放棄緩沖有很大聯系,因為壓縮過的索引很容易放到內存里面,這點十分滿足緩沖的空間需求。