dySE:一個 Java 搜索引擎的實現,第 2 部分: 網頁預處理

在 上一部分 中,您瞭解到如何編寫一個 spider 程序來進行網頁的爬取,做爲 spider 的爬取結果,咱們得到了一個按照必定格式存儲的原始網頁庫,原始網頁庫也是咱們第二部分網頁預處理的數據基礎。網頁預處理的主要目標是將原始網頁經過一步步的數據處理變成可方便搜索的數據形式。下面就讓咱們逐步介紹網頁預處理的設計和實現。html

預處理模塊的總體結構java

預處理模塊的總體結構以下:mysql


圖 1. 預處理模塊的總體結構
圖 1. 預處理模塊的總體結構  

經過 spider 的收集,保存下來的網頁信息具備較好的信息存儲格式,可是仍是有一個缺點,就是不能按照網頁 URL 直接定位到所指向的網頁。因此,在第一個流程中,須要先創建網頁的索引,如此經過索引,咱們能夠很方便的從原始網頁庫中得到某個 URL 對應的頁面信息。以後,咱們處理網頁數據,對於一個網頁,首先須要提取其網頁正文信息,其次對正文信息進行分詞,以後再根據分詞的狀況創建索引和倒排索引,這樣,網頁的預處理也所有完成。可能讀者對於其中的某些專業術語會有一些不明白之處,在後續詳述各個流程的時候會給出相應的圖或者例子來幫助你們理解。正則表達式

回頁首算法

創建索引網頁庫sql

原始網頁庫是按照格式存儲的,這對於網頁的索引創建提供了方便,下圖給出了一條網頁信息記錄:數據庫


清單 1. 原始網頁庫中的一條網頁記錄
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx     // 以前的記錄

 version:1.0                           // 記錄頭部
 url:http://ast.nlsde.buaa.edu.cn/ 
 date:Mon Apr 05 14:22:53 CST 2010 
 IP:218.241.236.72 
 length:3981 

 <!DOCTYPE ……                     // 記錄數據部分
 <html> …… </html> 

 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx     // 以後的記錄
 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

咱們採用「網頁庫名—偏移」的信息對來定位庫中的某條網頁記錄。因爲數據量比較大,這些索引網頁信息須要一種保存的方法,dySE 使用數據庫來保存這些信息。數據庫們採用 mysql,配合 SQL-Front 軟件能夠輕鬆進行圖形界面的操做。咱們用一個表來記錄這些信息,表的內容以下:url、content、offset、raws。URL 是某條記錄對應的 URL,由於索引數據庫創建以後,咱們是經過 URL 來肯定須要的網頁的;raws 和 offset 分別表示網頁庫名和偏移值,這兩個屬性惟一肯定了某條記錄,content 是網頁內容的摘要,網頁的數據量通常較大,把網頁的所有內容放入數據庫中顯得不是很實際,因此咱們將網頁內容的 MD5 摘要放入到 content 屬性中,該屬性至關於一個校驗碼,在實際運用中,當咱們根據 URL 得到某個網頁信息是,能夠將得到的網頁作 MD5 摘要而後與 content 中的值作一個匹配,若是同樣則網頁獲取成功,若是不同,則說明網頁獲取出現問題。編程

這裏簡單介紹一下 mySql 的安裝以及與 Java 的鏈接:安全

  • 安裝 mySql,最好須要三個組件,mySql,mySql-front,mysql-connector-java-5.1.7-bin.jar,分別能夠在網絡中下載。注意:安裝 mySql 與 mySql-front 的時候要版本對應,MySql5.0 + MySql-Front3.2 和 MySql5.1 + MySql-Front4.1,這個組合是不能亂的,能夠根據相應的版本號來下載,不然會爆「‘ 10.000000 ’ ist kein gUltiger Integerwert 」的錯誤。
  • 導入 mysql-connector-java-5.1.7-bin.jar 到 eclipse 的項目中,打開 eclipse,右鍵點須要導入 jar 包的項 目名,選屬性(properties),再選 java 構建路徑(java Build Path),後在右側點 (libraries),選 add external JARs,以後選擇你要導入的 jar 包肯定。
  • 接着就能夠用代碼來測試與 mySql 的鏈接了,代碼見本文附帶的 testMySql.java 程序,這裏限於篇幅就不在贅述。
  • 對於數據庫的操做,咱們最好進行必定的封裝,以提供統一的數據庫操做支持,而不須要在其餘的類中顯示的進行數據庫鏈接操做,並且這樣也就不須要創建大量的數據庫鏈接從而形成資源的浪費,代碼詳見 DBConnection.java。主要提供的操做是:創建鏈接、執行 SQL 語句、返回操做結果。

介紹了數據庫的相關操做時候,如今咱們能夠來完成網頁索引庫的創建過程。這裏要說明的是,第一條記錄的偏移是 0,因此在當前記錄 record 處理以前,該記錄的偏移是已經計算出來的,處理 record 的意義在於得到下一個記錄在網頁庫中的偏移。假設當前 record 的偏移爲 offset,定位於頭部的第一條屬性以前,咱們經過讀取記錄的頭部和記錄的數據部分來獲得該記錄的長度 length,從而,offset+length 即爲下一條記錄的偏移值。讀取頭部和讀取記錄都是經過數據間的空行來標識的,其僞代碼以下:網絡


清單 2. 索引網頁庫創建
For each record in Raws do 
begin 
    讀取 record 的頭部和數據,從頭部中抽取 URL;
    計算頭部和數據的長度,加到當前偏移值上獲得新的偏移;
    從 record 中數據中計算其 MD5 摘要值;
    將數據插入數據庫中,包括:URL、偏移、數據 MD5 摘要、Raws;
end;

您可能會對 MD5 摘要算法有些疑惑,這是什麼?這有什麼用? Message Digest Algorithm MD5(中文名爲消息摘要算法第五版)爲計算機安全領域普遍使用的一種散列函數,用以提供消息的完整性保護。MD5 的典型應用是對一段信息 (Message) 產生一個 128 位的二進制信息摘要 (Message-Digest),即爲 32 位 16 進制數字串,以防止被篡改。對於咱們來講,好比經過 MD5 計算,某個網頁數據的摘要是 00902914CFE6CD1A959C31C076F49EA8,若是咱們任意的改變這個網頁中的數據,經過計算以後,該摘要就會改變,咱們能夠將信息的 MD5 摘要視做爲該信息的指紋信息。因此,存儲該摘要能夠驗證以後獲取的網頁信息是否與原始網頁一致。

對 MD5 算法簡要的敘述能夠爲:MD5 以 512 位分組來處理輸入的信息,且每一分組又被劃分爲 16 個 32 位子分組,通過了一系列的處理後,算法的輸出由四個 32 位分組組成,將這四個 32 位分組級聯後將生成一個 128 位散列值。其中「一系列的處理」即爲計算流程,MD5 的計算流程比較多,可是不難,同時也不難實現,您能夠直接使用網上現有的 java 版本實現或者使用本教程提供的源碼下載中的 MD5 類。對於 MD5,咱們知道其功能,能使用就能夠,具體的每一個步驟的意義不須要深刻理解。

回頁首

正文信息抽取

PageGetter

在正文信息抽取以前,咱們首先須要一個簡單的工具類,該工具類能夠取出數據庫中的內容而且去原始網頁集中得到網頁信息,dySE 對於該功能的實如今 originalPageGetter.java 中,該類經過 URL 從數據庫中得到該 URL 對應的網頁數據的所在網頁庫名以及偏移,而後就能夠根據偏移來讀取該網頁的數據內容,一樣以原始網頁集中各記錄間的空行做爲數據內容的結束標記,讀取內容以後,經過 MD5 計算當前讀取的內容的摘要,校驗是否與以前的摘要一致。對於偏移的使用,BufferedReader 類提供一個 skip(int offset) 的函數,其做用是跳過文檔中,從當前開始計算的 offset 個字符,用這個函數咱們就能夠定位到咱們須要的記錄。


清單 3. 獲取原始網頁庫中內容
public String getContent(String fileName, int offset) 
 { 
     String content = ""; 
     try { 
         FileReader fileReader = new FileReader(fileName); 
         BufferedReader bfReader = new BufferedReader(fileReader); 
         bfReader.skip(offset); 
         readRawHead(bfReader); 
         content = readRawContent(bfReader);         
     } catch (Exception e) {e.printStackTrace();} 
     return content;     
 }

上述代碼中,省略了 readRawHead 和 readRawContent 的實現,這些都是基本的 I/O 操做,詳見所附源碼。

正文抽取

對於得到的單個網頁數據,咱們就能夠進行下一步的處理,首先要作的就是正文內容的抽取,從而剔除網頁中的標籤內容,這一步的操做主要採用正則表達式來完成。咱們用正則表達式來匹配 html 的標籤,而且把匹配到的標籤刪除,最後,剩下的內容就是網頁正文。限於篇幅,咱們以過濾 script 標籤爲示例,其代碼以下 :


清單 4. 標籤過濾
public String html2Text(String inputString) {        
     String htmlStr = inputString; // 含 html 標籤的字符串    
     Pattern p_script;    Matcher m_script;      
     try { 
            String regEx_script = "<script[^>]*?>[\\s\\S]*?</script>";
            p_script = Pattern.compile(regEx_script,Pattern.CASE_INSENSITIVE);    
            m_script = p_script.matcher(htmlStr);    
            htmlStr = m_script.replaceAll(""); // 過濾 script 標籤    
     }catch(Exception e) {e.printStackTrace();} 
     return htmlStr;// 返回文本字符串    
 }

經過一系列的標籤過濾,咱們能夠獲得網頁的正文內容,就能夠用於下一步的分詞了。

回頁首

分詞

中文分詞是指將一個漢字序列切分紅一個一個單獨的詞,從而達到計算機能夠自動識別的效果。中文分詞主要有三種方法:第一種基於字符串匹配,第二種基於語義理解,第三種基於統計。因爲第二和第三種的實現須要大量的數據來支持,因此咱們採用的是基於字符串匹配的方法。

基於字符串匹配的方法又叫作機械分詞方法,它是按照必定的策略將待分析的漢字串與一個「充分大的」機器詞典中的詞條進行配,若在詞典中找到某個字符串,則匹配成功(識別出一個詞)。按照掃描方向的不一樣,串匹配分詞方法能夠分爲正向匹配和逆向匹配;按照不一樣長度優先匹配的狀況,能夠分爲最大(最長)匹配和最小(最短)匹配。經常使用的幾種機械分詞方法以下:

  1. 正向減字最大匹配法(由左到右的方向);
  2. 逆向減字最大匹配法(由右到左的方向);
  3. 最少切分(使每一句中切出的詞數最小);
  4. 雙向最大減字匹配法(進行由左到右、由右到左兩次掃描);

咱們採用其中的正向最大匹配法。算法描述以下:輸入值爲一箇中文語句 S,以及最大匹配詞 n

  1. 取 S 中前 n 個字,根據詞典對其進行匹配,若匹配成功,轉 3,不然轉 2;
  2. n = n – 1:若是 n 爲 1,轉 3;不然轉 1;
  3. 將 S 中的前 n 個字做爲分詞結果的一部分,S 除去前 n 個字,若 S 爲空,轉 4;不然,轉 1;
  4. 算法結束。

須要說明的是,在第三步的起始,n 若是不爲 1,則意味着有匹配到的詞;而若是 n 爲 1,咱們默認 1 個字是應該進入分詞結果的,因此第三步能夠將前 n 個字做爲一個詞而分割開來。還有須要注意的是對於停用詞的過濾,停用詞即漢語中「的,了,和,麼」等字詞,在搜索引擎中是忽略的,因此對於分詞後的結果,咱們須要在用停用詞列表進行一下停用詞過濾。

您也許有疑問,如何得到分詞字典或者是停用詞字典。停用詞字典比較好辦,因爲中文停用詞數量有限,能夠從網上得到停用詞列表,從而本身建一個停用詞字典;然而對於分詞字典,雖然網上有許多知名的漢字分詞軟件,可是不多有分詞的字典提供,這裏咱們提供一些在 dySE 中使用的分詞字典給您。在程序使用過程當中,分詞字典能夠放入一個集合中,這樣就能夠比較方便的進行比對工做。

分詞的結果對於搜索的精準性有着相當重要的影響,好的分詞策略常常是由若干個簡單算法拼接而成的,因此您也能夠試着實現雙向最大減字匹配法來提升分詞的準確率。而若是遇到歧義詞組,能夠經過字典中附帶的詞頻來決定哪一種分詞的結果更好。

回頁首

倒排索引

這個章節咱們爲您講解預處理模塊的最後兩個步驟,索引的創建和倒排索引的創建。有了分詞的結果,咱們就能夠得到一個正向的索引,即某個網頁以及其對應的分詞結果。以下圖所示:


圖 2. 正向索引
圖 2. 正向索引  

圖 3. 倒排索引
圖 3. 倒排索引  

在本文的開頭,咱們創建了索引網頁庫,用於經過 URL 能夠直接定位到原始網頁庫中該 URL 對應的數據的位置;而如今的正向索引,咱們能夠經過某個網頁的 URL 獲得該網頁的分詞信息。得到正向索引看似對於咱們的即將進行的查詢操做沒有什麼實際的幫助,由於查詢服務是經過關鍵詞來得到網頁信息,而正向索引並不能經過分詞結果反查網頁信息。其實,咱們創建正向索引的目的就是經過翻轉的操做創建倒排索引。所謂倒排就是相對於正向索引中網頁——分詞結果的映射方式,採用分詞——對應的網頁這種映射方式。與圖 2 相對應的倒排索引如上圖 3 所示。

接下來咱們分析如何從正向索引來獲得倒排索引。算法過程以下:

  1. 對於網頁 i,獲取其分詞列表 List;
  2. 對於 List 中的每一個詞組,查看倒排索引中是否含有這個詞組,若是沒有,將這個詞組插入倒排索引的索引項,並將網頁 i 加到其索引值中;若是倒排索引中已經含有這個詞組,直接將網頁 i 加到其索引值中;
  3. 若是還有網頁還沒有分析,轉 1;不然,結束

創建倒排索引的算法不難實現,主要是其中數據結構的選用,在 dySE 中,正向索引和倒排索引都是採用 HashMap 來存儲,映射中正向索引的鍵是採用網頁 URL 對應的字符串,而倒排索引是採用分詞詞組,映射中的值,前者是一個分詞列表,後者是一個 URL 的字符串列表。這裏能夠採用一個優化,分別創建兩個表,按照標號存儲分詞列表和 URL 列表,這樣,索引中的值就可使用整型變量列表來節省空間。

回頁首

初步實驗

到目前爲止,雖然咱們尚未正式的查詢輸入界面以及結果返回頁面,但這絲絕不影響咱們來對咱們的搜索引擎進行初步的實驗。在倒排索引創建之後,咱們在程序中得到一個倒排索引的實例,而後定義一個搜索的字符串,直接在倒排索引中遍歷這個字符串,而後返回該詞組所指向的倒排索引中的 URL 列表便可。

回頁首

小結

網頁的預處理是搜索引擎的核心部分,創建索引網頁庫是爲了網頁數據更方便的從原始網頁庫中獲取,而抽取正文信息是後續操做的基礎。從分詞開始就正式涉及到搜索引擎中文本數據的處理,分詞的好壞以及效率很大程度上決定着搜索引擎的精確性,是很是須要關注的一點,而倒排索引時根據分詞的結果創建的一個「詞組——對應網頁列表」映射,倒排索引是網頁搜索的最關鍵數據結構,搜索引擎執行的速度與倒排索引的創建以及倒排索引的搜索方式息息相關。

回頁首

後續內容

在本系列的第三部分中,您將瞭解到如何從建立網頁,從網頁中輸入查詢信息經過倒排索引的搜索完成結果的返回,而且完成網頁排名的功能。


參考資料

學習

相關文章
相關標籤/搜索