Web搜索引擎設計和實現分析

胡朝暉(浙江大學計算機系)
王海瑛(寧波海峯塑化有限公司)

---- 一、引言

---- 隨着Internet的飛速發展,人們越來越依靠網絡來查找他們所需要的信息,但是,由於網上的信息源多不勝數,也就是我們經常所說的"Rich Data, Poor Information"。所以如何有效的去發現我們所需要的信息,就成了一個很關鍵的問題。爲了解決這個問題,搜索引擎就隨之誕生。

---- 現在在網上的搜索引擎也已經有很多,比較著名的有AltaVista, Yahoo, InfoSeek, Metacrawler, SavvySearch等等。國內也建立了很多的搜索引擎,比如:搜狐、新浪、北極星等等,當然由於它們建立的時間不長,在信息搜索的取全率和取準率上都有待於改進和提高。

---- Alta Vista是一個速度很快的搜索引擎,由於它強大的硬件配置,使它能夠做及其複雜的查詢。它主要是基於關鍵字進行查詢,它漫遊的領域有Web和Usenet。支持布爾查詢的"AND","OR"和"NOT",同時還加上最相近定位"NEAR",允許通配符和"向後"搜索(比如:你可以查找鏈接到某一頁的所有Web站點)。你可以決定是否對搜索的短語加上權值,在文檔的什麼部位去查找它們。能夠進行短語查詢而不是簡單的單詞查詢的優點是很明顯的,比如,我們想要查找一個短語"to be or not to be",如果只是把它們分解成單詞的話,這些單詞都是屬於Stop Word,這樣這個查詢就不會有任何結果,但是把它當作一個整體來查詢,就很容易返回一些結果,比如關於哈姆雷特或者是莎士比亞等等的信息。系統對查詢結果所得到的網頁的打分是根據在網頁中所包含的你的搜索短語的多少,它們在文檔的什麼位置以及搜索短語在文檔內部之間的距離來決定的。同時可以把得到的搜索結果翻譯成其他的語言。

---- Exite是稱爲具有"智能"的搜索引擎,因爲它建立了一個基於概念的索引。當然,它所謂的"智能"是基於對概率統計的靈活應用。它能夠同時進行基於概念和關鍵字的索引。它能夠索引Web,Usenet和分類的廣告。支持"AND","OR","NOT"等布爾操作,同時也可以使用符號"+"和"-"。缺點是在返回的查詢結果中沒有指定網頁的尺寸和格式。

---- InfoSeek是一個簡單但是功能強大的索引,它的一個優點是有一個面向主題搜索的可擴展的分類。你可以把你的搜索短語和相似的分類目錄的主題短語相互參照,而那些主題短語會自動加到你的查詢中去。使你的搜索有更好的主題相關性。同時它也支持對圖象的查詢。它能夠漫遊Web,Usenet,Usenet FAQs等等。不支持布爾操作,但是可以使用符號"+"和"-"(相當於"AND"和"NOT")

---- Yahoo實際上不能稱爲是一個搜索引擎站點,但是它提供了一個分層的主題索引,使你能夠從一個通常的主題進入到一個特定的主題,Yahoo對Web進行了有效的組織和分類。比如你想要建立一個網頁,但是你不知道如何操作,爲了在Yahoo上找到關於建立網頁的信息,你可以先在Yahoo上選擇一個主題:計算機和Internet,然後在這個主題下,你可以發現一些子主題,比如:Web網頁製作,CGI編程,JAVA,HTML,網頁設計等,選擇一個和你要找的相關的子主題,最終你就可以得到和該子主題相關的所有的網頁的鏈接。也就是說,如果你對要查找的內容屬於哪個主題十分清楚的話,通過目錄查詢的方法要比一般的使用搜索引擎有更好的準確率。你可以搜索Yahoo的索引,但是事實上,你並沒有在搜索整個Web。但是Yahoo提供了選項使你可以同時搜索其他的搜索引擎,比如:Alta Vista。但是要注意的是Yahoo實際上只是對Web的一小部分進行了分類和組織,而且它的實效性也不是很好。

---- 搜索引擎的基本原理是通過網絡機器人定期在web網頁上爬行,然後發現新的網頁,把它們取回來放到本地的數據庫中,用戶的查詢請求可以通過查詢本地的數據庫來得到。如yahoo每天會找到大約500萬個新的網頁。

---- 搜索引擎的實現機制一般有兩種,一種是通過手工方式對網頁進行索引,比如yahoo的網頁是通過手工分類的方式實現的,它的缺點是Web的覆蓋率比較低,同時不能保證最新的信息。查詢匹配是通過用戶寫入的關鍵字和網頁的描述和標題來進行匹配,而不是通過全文的匹配進行的。第二種是對網頁進行自動的索引,象AltaVista則是完全通過自動索引實現的。這種能實現自動的文檔分類,實際上採用了信息提取的技術。但是在分類準確性上可能不如手工分類。

---- 搜索引擎一般都有一個Robot定期的訪問一些站點,來檢查這些站點的變化,同時查找新的站點。一般站點有一個robot.txt文件用來說明服務器不希望Robot訪問的區域,Robot 都必須遵守這個規定。如果是自動索引的話,Robot在得到頁面以後,需要對該頁面根據其內容進行索引,根據它的關鍵字的情況把它歸到某一類中。頁面的信息是通過元數據的形式保存的,典型的元數據包括標題、IP地址、一個該頁面的簡要的介紹,關鍵字或者是索引短語、文件的大小和最後的更新的日期。儘管元數據有一定的標準,但是很多站點都採用自己的模板。文檔提取機制和索引策略對Web搜索引擎的有效性有很大的關係。高級的搜索選項一般包括:布爾方法或者是短語匹配和自然語言處理。一個查詢所產生的結果按照提取機制被分成不同的等級提交給用戶。最相關的放在最前面。每一個提取出來的文檔的元數據被顯示給用戶。同時包括該文檔所在的URL地址。

---- 另外有一些關於某一個主題的專門的引擎,它們只對某一個主題的內容進行搜索和處理,這樣信息的取全率和精度相對就比較高。

---- 同時,有一類搜索引擎,它本身不用Robot去定期的採集網頁。象SavvySearch 和 MetaCrawler是通過向多個搜索引擎同時發出詢問並對結果進行綜合返回給用戶實現搜索功能。當然實際上象SavvySearch能夠對各個搜索引擎的功能進行分析和比較,根據不同的用戶查詢提交給不同的搜索引擎進行處理,當然用戶自己也可以指定利用哪一個搜索引擎。

---- 一個優秀的搜索引擎必須處理以下幾個問題:1 網頁的分類2 自然語言的處理3 搜索策略的調度和協作 4 面向特定用戶的搜索。所以很多搜索引擎不同程度的使用了一些人工智能的技術來解決這些方面的問題。

---- 二、網絡Spider的實現描述

---- 現在有很多文章對Web引擎做了大量的介紹和分析,但是很少有對它們的實現做一個詳細的描述,這裏我們主要來介紹一個具有基本功能的Web引擎的實現。本文,我們以類C++語言的形式來描述Web引擎如何採集網頁並存放到數據庫中的過程。同時描述瞭如何根據用戶輸入的關鍵字查詢數據庫並得到相關網頁的過程。

---- 2.1數據庫結構

---- 首先,我們要建立一個數據庫表用來存放我們得到的網頁。這裏一般需要建立如下的表:

---- 1.字典表的建立,事實上這裏是用文檔中有意義的單詞和它們的出現頻率來代表一個文檔。

---- 該表(WordDictionaryTbl)主要要包括三個字段,主要是用來存放和一個網頁相關的單詞的情況

    url_id    對每一個URL的唯一的ID號
    word      該URL中的經過stem的單詞
    intag    該單詞在該網頁中的出現的次數

---- 2.存儲每一個URL信息的表

---- 該表(URLTbl)中主要的關鍵字段有:

  rec_id        每一條記錄的唯一的ID號
  status    得到該URL內容的狀態,比如HTTP_STATUS_TIMEOUT表示
            下載網頁的最大允許超時
  url        URL的字符串名稱
  content_type      內容的類型
  last_modified    最新的更改時間
  title            該URL的標題
  docsize          該URL的文件的尺寸
  last_index_time  最近一次索引的時間
  next_index_time  下一次索引的時間
  tag    對於網頁,用來表示它的類型,比如:是text,或者是html,
                    或者是圖片等等
  hops              得到文件時候的曾經失敗的次數
  keywords          對於網頁,和該網頁相關的關鍵字
  description      對於網頁,指網頁的內容的描述
  lang              文檔所使用的語言

---- 3.因爲網頁中有很多單詞是一些介詞和語氣助詞或者是非常常用的常用詞,它們本身沒有多少意義。比如:英語中的about,in,at,we,this等等。中文中的如"和","一起","關於"等等。我們統一的把它們稱爲停止詞(stop word)。所以我們要建立一個表,來包括所有這些停止詞。該表(StopWordTbl)主要有兩個字段。
word char(32)    表示那些停止詞
lang char(2)      表示所使用的語言

---- 4.我們要建立一個關於robot的表,我們在前面說過,所有的網站一般都有一個robot.txt文件用來表示網絡上的robot可以訪問的權限。該表(RobotTbl)主要有以下字段。
    hostinfo          Web站點主機的信息
    path              不允許robot訪問的目錄

---- 5.建立我們需要屏蔽的那些網頁(比如一些內容不健康的或者沒有必要去搜索的站點)的一張表(ForbiddenWWWTbl),主要的字段就是網頁的URL。

---- 6.另外我們需要建立一個我們所要得到的文件類型的表(FileTypeTbl),比如,對於一個簡單的Web搜索引擎,我們可能只需要得到後綴爲.html,htm,.shtml和txt的類型文件。其他的我們只是簡單的忽略它們。主要的字段就是文件的類型和說明。

---- 其中關於停止詞的表的內容是我們要實現要根據各種語言的統計結果,把那些意義不大的單詞放進去。關於文檔單詞、URL和Robot的表的內容都是在獲取Web網頁的時候動態增加記錄的。

---- 2.2 具體網頁獲取算法描述

---- 具體的網頁的獲取步驟是這樣的:

---- 我們可以設定我們的搜索程序最大可以開的線程的數目,然後這些線程可以同時在網上進行搜索,它們根據數據庫中已有的關於網頁的信息,找出那些需要更新的網頁(如何判斷哪些網頁需要更新是一個值得研究的過程,現在有很多啓發式和智能的算法,基本上是基於統計規律進行建模。最簡單的當然是設定一個時間範圍,在某個時間範圍以前的網頁被重新去搜索一遍),然後判斷那些網頁是否在屏蔽表中,如果是的話,就從關於URL的表中刪除該條記錄。否則,我們就到相應的WWW站點去得到URL指定的文件(這裏需要注意的是根據不同的URL的特點,需要使用不同的協議,比如對於FTP站點要採用FTP協議,對於HTTP站點要採用HTTP協議,新聞站點要採用NNTP協議等等)事實上,我們先得到關於該網頁的頭信息,如果該網頁的最新修改時間和我們最近提取的時間是一樣的話,表示該網頁內容沒有任何更新,則我們就不必去得到它的內容,只需要修改最近一次更新它的時間爲當前的時間就可以了。如果該網頁最近做了修改,我們就要得到該網頁,並對它的內容進行分析,主要要包括和它相關的鏈接,把它們加到相應的數據庫中,同時判斷網頁所包含的各種其他的文件,如文本文件、圖形文件、聲音文件和其他多媒體文件是否是我們所需要的文件,如果是的話,就把它加到我們響應的數據庫中。同時要根據網頁的內容提取所有的有意義的單詞和它們的出現的次數,放到相應的數據庫中。爲了更好的描述這個過程,我們來看跟這個過程相關的主要的幾個對象和數據結構。對象主要是針對三個層次來講的。第一層是針對WWW服務器,第二層是針對每一個頁面,第三層是針對每一個頁面的全文的索引。

---- 2.3 和實現相關的主要類對象和功能描述下面的結構是針對一個站點來說的。

    Class  CServer {
    主要的屬性有:
    char *url;            //WWW站點的URL名稱
    char *proxy;          //使用的代理的名稱
    char *basic_auth;      //進行基本的HTTP認證
    int  proxy_port;      //代理的端口號
    int  period;          //再次索引的週期
    int  net_errors;      //網絡連接不通的次數
    int  max_net_errors;  //可以允許的最大的網絡錯誤
    int  read_timeout;    //下載文件允許的最大的延遲
    int  maxhops;          //表示URL可以最大跳轉的深度
    int  userobots;        //是否遵守robot.txt中的約定
    int  bodyweight;  // 在< body >....< /body >之間的單詞的權重
    int  titleweight; // 在< title >....< /title >之間的單詞的權重
    int  urlweight;  // 在文檔的URL中的單詞的權重
    int descweight;//在    < META
NAME="Description"        Content="..." >之間單詞的權重
    int  keywordweight; //在< META NAME="Keywords" Content="..." >
  之間的單詞的權重

---- 主要方法有:
FindServer();//用來查找該服務器是否存在並可以連接
FillDefaultAttribute() //用來針對所有的WWW服務器填寫默認的屬};

以上的對象中的成員變量是和一個站點相關的參數的設置,我們對所有的站點有一個默認的設置,但是可以對某些站點做一些特殊的設置。這些設置可以在配置文件中設定。
---- 下面是關於文檔的結構的主要的數據成員:

Class CNetDocument
    主要屬性有:
    int    url_id; //該URL的ID號
    int    status;  //獲取該文檔時候的狀態
    int    size;  //文檔的尺寸
int    tag;  //和該文檔相關的標籤,表示該文檔是
HTML,TEXT或者是其他類型
    int    hops;    //URL跳轉的次數
    char    *url; //和該文檔相關的URL的名稱
    char    *content_type;      //該內容的類型
    char    *last_modified;    //最近一次的更新時間
    char    *title;            //該文檔的標題
    char    *last_index_time;  //上次索引的時間
    char    *next_index_time;  //下次索引的時間
    char    *keywords;          //該文檔中的關鍵字
    char    *description;      //該文檔的描述

  主要方法有:
  FillDocInfo(…) //根據數據庫,得到該文檔相關信息
  AddHerf(…)    //加入網頁中存在的新的鏈接的網址
  DeleteURL(…)  //刪除一個存在的網址
  CanGetThisURL(…) //根據配置決定是否去得到該網頁
  //下面三個方法是根據不同的URL,用不同的協議去獲得文檔
  NNTPGet(…)     
  FTPGet(….)
  HTTPGet(….)
  ParseHead(…)  //如果是HTTP協議得到的話,分析頭信息
  ParseMainBody(…)    //對獲得的文檔的主體進行分析
  ServerResponseType (….)  //得到服務器端的響應消息
  UpdateURLDB(….)  //更新的數據入庫
} ;

---- 事實上,我們在要提取一個網頁的時候,都要建立一個CNetDocument對象,然後再對這個網頁進行分析的時候,把相關的內容放到這個CNetDocument的成員變量裏面。下面是關於頁面全文索引的結構的主要數據成員:
Class CIndexer {
主要屬性有:
  char    *url;      //我們要處理的文檔相關的URL的名稱
  int mwords;      //  我們事先設定的一個網頁的最大的單詞數目
    int nwords;          // 實際的得到的單詞的數目
    int swords;          // 我們已經排序的單詞的數目
    WORD *Word;      //所有單詞的內容
    char *buf;      //我們爲文檔所分配的空間
主要方法有:
  InitIndexer(…)    //進行初始設置和分配
  ParseGetFile(…)  //對得到的網頁進行全文索引
  AddWord(…)    //把網頁的可以索引的單詞加到Word數組中去
  InToDB(….)    //關於網頁全文索引的信息入庫
};

---- 進行網頁提取前,我們要建立一個CIndexer對象,它主要是用來對網頁進行全文的索引。一般來說我們只對兩種類型的URL進行全文索引,一個是text/html,另外一個是text/plain。其中WORD的數據結構如下:
        typedef struct word_struct {
    int count;  //該單詞出現的次數
    int code;  //該單詞的正常的形式,
比如單詞可能爲 encouraging,它的正常的形式應該爲
encourage,這其實是一種對單詞的stem。
即我們只取單詞的主幹部分。
    char *word;  //該單詞的內容
} WORD;

---- 以下的結構是和網頁中的一些鏈接的對象相關的一個數據結構
    typedef struct href_struct {
    char *href;    //該鏈接的名稱
    int hops;      //發生的跳轉次數
    int stored;    //是否已經存儲到數據庫中
} HREF;
 

---- 所有需要更新的和新產生的URL都被放到這個結構中,當它的數量超過一定的範圍以後,被一次性的存入數據庫。
---- 關於URL的一個數據結構如下:

typedef struct url {
char *schema; //表示該URL是通過什麼協議得到的,比如HTTP,
              FTP,NNTP等。
char *specific;    //主機的名稱加上路徑
char *hostinfo;    //主機的名稱加上相關的協議端口
char *hostname;    //主機的名稱
char *path;        //在主機的具體的路徑
char *filename;    //文件的名稱
char *anchor;      //相關的anchor
int  port;        //協議相關的端口
} URL;

---- 這是針對URL的一些相關的屬性的描述的一個數據結構。事實上在數據庫中,我們存儲的只是對網頁的描述和對一些文本和HTML頁面的關鍵詞的索引信息。我們並不存儲網頁的實際的內容。
---- 三、用戶查詢實現描述

---- 關於對用戶提交的查詢請求的實現分析:

---- 用戶想要查詢某一方面的信息一般都是通過提供和該領域相關的幾個關鍵字來進行的。

---- 我們來看一下關於用戶查詢的相關的數據結構和類:

---- 下面是一個關於單詞和它的權值的基本結構:

  typedef struct word_weight_pair
    {
      char word[WORD_LEN];
      int weight;
    }word_weight_pair;
   

---- 下面的類主要是用來對用戶的查詢進行處理和分析:
    Class CUserQuery
    {
char m_UserQuery[MAX_QUERYLEN];  //用戶的查詢表達式
CPtrArray word_weight_col;
//是關於結構word_weight_pair的動態數組
int m_maxReturnSum;  //用戶希望返回的最多的網頁數
int search_mode;
CObArray m_returnDoc;  //是關於CNetDocument對象的一個動態數組
NormalizeWord(char* OneWord);  //對單詞進行歸整化,即Stem.
Find(char* odbcName);  //進行數據庫查找和匹配
};

---- 系統實現的基本的步驟如下:

---- 1.對用戶輸入的查詢表達式進行分析。事實上,我們在前面的Spider搜索過程中對文檔的表示是通過關鍵字形式描述的,每一個文檔可以表示爲這樣的一個集合

    其中 ::=< 單詞或短語名稱 >< 單詞或短語的權值 >

---- 實際上就是採用矢量空間的表示方法來表示的文檔。

---- 我們對用戶輸入的查詢表達式也採用矢量空間的表示方法。我們認爲用戶輸入的關鍵字的順序代表了它的重要性的程度,所以對於位置靠前的單詞有相對比較高的優先級,同時我們對所有的內容以短語或者是單詞爲最小原子,進行Stem操作,即象前面所提到的:比如單詞Encouraging就轉化成Encourage的格式。然後去掉那些Stop Word,比如is ,as等等的單詞,這些單詞存放在StopWordTbl表中。 然後把所有歸整化後的內容放入動態數組word_weight_col中去。

---- 2.對於動態數組word_weight_col中的每一個元素,即結構word_weight_pair(包括單詞和該單詞的權重),我們從表WordDictionaryTbl中可以找到和這些單詞相關的記錄,這些記錄應該是包括了所有的在word_weight_col中的單詞。

---- 進行網頁是否和查詢相匹配的計算。匹配計算的過程如下:首先我們對所有的記錄按URL地址進行排序。因爲可能好幾條記錄對應的是一個URL,然後對每一個網頁進行打分,每一條記錄的單詞權值爲INITSCORE*WEIGHT+(TOTALTIMES-1)*WEIGHT* INCREMENT。其中INITSCORE爲每一個單詞的基準分數,TOTALTIMES爲該單詞在網頁中的出現的次數,WEIGHT是該單詞在不同的內容段出現有不同的權值(比如在KEYWORD段,或者是標題段,或者是內容段等等)。INCREMENT是該單詞每多出現一次所增加的分數。

---- 3.根據用戶指定的m_maxReturnSum,顯示匹配程度最高的前m_maxReturnSum頁。

---- 四、結束語

---- 我們利用上面所討論的機制,在WINDOWS NT操作系統下,用VC++和SQL SERVER實現了一個Web搜索引擎的網頁蒐集過程。在建立了一個基本的搜索引擎的框架以後,我們可以基於這個框架,實現一些我們自己設計的算法,比如如何更好的進行Spider的調度,如何更好的進行文檔的歸類,如何更好的理解用戶的查詢,用來使Web搜索引擎具有更好的智能性和個性化的特點。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章