代码改变世界

Searcharoo源码学习日志(一)

2009-10-06 18:10  Yin.P  阅读(2501)  评论(5编辑  收藏  举报

Searcharoo源码学习系列文章将会对这套开源搜索引擎的各个版本(主要是前几个版本,因为关于搜索技术的变化更新主要体现在前几个版本,后面的版本主要是一些扩展和展现方式的改进)源代码进行简单的介绍,包括组成这个搜索引擎的各部分及其在各版本中的发展过程。从上一篇文章中我们可以大概了解到Searcharoo是一套比较简单且易于上手学习的搜索引擎开源项目。从这篇文章开始我们就要从细节来学习这套搜索引擎源代码。

         从宏观概念来看,搜索引擎主要包含了索引和检索两部分,大量的技术研发和创新都围绕着这两个主题展开,在以后的各篇关于Searcharoo源代码分析的文章中主要的线索就分为对索引方法的分析和对检索方法的分析。在Searcharoo中,版本间的区别主要体现在索引技术上,其搜索是通过在固定数据结构(Hashtable)上的查找操作,相对来说这部分在Searcharoo各版本中的变化不是很大。

         本文主要介绍Searcharoo第一版的相关内容。Searcharoo第一版主要是实现了搜索引擎的原型,即体现了索引和检索两个部分的最基本功能以及这两个部分的协同工作。第一版的功能很有限,它只能对本地机器上并且位于应用服务器根目录的指定类型的文件进行索引,其索引数据被缓存在内存中。而检索则是通过.NETHashtable类型实现的。

 

l  对象模型

         Searcharoo第一版的对象模型如下图:


图一:Searcharoo v1的对象模型(www.searcharoo.net

 

         其中Catalog类表示了索引结构,其中Hashtable包含了多个索引项目,每一个索引项目包括关键词、关键词所在的文档、关键词在文档中的位置,索引项由Add方法添加,Word表示了一个特定的关键词,在Word对象中有一个很重要的Hashtable类型的属性:fileCollection。这个属性可以由InFiles方法取得,这个属性就是构造倒排索引的重要手段。文档信息由UrlTitleDescriptionFileDate以及Size构成。

         搜索引擎索引数据是由爬行器填充,Searcharoo第一版的爬行器默认对服务器根目录下的指定文件进行查找,然后取出其全部文本内容连同文件的其它基本信息保存起来作为索引项。SearcharooCrawler.aspx页面中的服务器代码实现了爬行器的主要功能。其主要代码如下: 

Code
DirectoryInfo m_dir = new DirectoryInfo(path);

// Look for matching files
foreach (FileInfo f in m_dir.GetFiles(m_filter))
{
    …
    StreamReader reader 
= File.OpenText(path + @"\" + f.Name);
    fileContents 
= reader.ReadToEnd();
    …
    Match TitleMatch 
= Regex.Match(fileContents, "<title>([^<]*)</title>", RegexOptions.IgnoreCase | RegexOptions.Multiline);
  filetitle 
= TitleMatch.Groups[1].Value;

    
// ### Parse out META data ###
    Match DescriptionMatch 
= Regex.Match(fileContents, "<META NAME=\"DESCRIPTION\" CONTENT=\"([^<]*)\">", RegexOptions.IgnoreCase | RegexOptions.Multiline);
    filedesc 
= DescriptionMatch.Groups[1].Value;

    
// ### Get the file SIZE ###
    filesize 
= fileContents.Length;   

    // ### Now remove HTML, convert to array, clean up words and index them ###
    fileContents 
= stripHtml(fileContents); 
    
string wordsOnly = stripHtml(fileContents);

    
// ### If no META DESC, grab start of file text ###
    
if (null == filedesc || String.Empty == filedesc)
    {
        
if (wordsOnly.Length > 350) filedesc = wordsOnly.Substring(0350);
        else if (wordsOnly.Length > 100) filedesc = wordsOnly.Substring(0100);
        else filedesc = wordsOnly; // file is only short!
    }
    Regex r 
= new Regex(@"\s+");           // remove all whitespace
    wordsOnly 
= r.Replace(wordsOnly, " "); // compress all whitespace to one space
    
string[] wordsOnlyA = wordsOnly.Split(' '); // results in an array of words

    
// Create the object to represent this file (there will only ever be ONE instance per file)
    File infile 
= new File(fileurl, filetitle, filedesc, DateTime.Now, filesize);
    …

    // Now recursively call this method for all subfolders
    foreach (DirectoryInfo d in m_dir.GetDirectories())
    {
        CrawlCatalog(root, path 
+ @"\" + d.Name);
    } 
// foreach folder

 

爬行器循环处理目录下的所有文件及子目录,对每一个文件,以流的方式取出其所有文本内容。然后对文本内容进行处理,如获取文档的标题(Match TitleMatch = Regex.Match(fileContents, "<title>([^<]*)</title>", RegexOptions.IgnoreCase | RegexOptions.Multiline);)、获取文档的描述文字(Match DescriptionMatch = Regex.Match(fileContents, "<META NAME=\"DESCRIPTION\" CONTENT=\"([^<]*)\">", RegexOptions.IgnoreCase | RegexOptions.Multiline);)去除HTML标签和空白串等。在获取描述文字的时候,如果去除标签后的内容长度小于100则直接把其作为文档的描述文字保存。接下来就是保存每一个关键词到索引中,在将关键词添加到索引中的时候同时也处理了关键词的PageRank值,即判断关键词在同一文档中的出现次数。分析下面的代码:

Code
public void Add(File infile, int position)
{
    if (fileCollection.ContainsKey(infile))
    {
        
int wordcount = (int)fileCollection[infile];
        fileCollection[infile] 
= wordcount + 1;
    }
    
else
    {
        fileCollection.Add(infile, 
1);
    }
}

 

这个方法位于Word类中,这个方法的功能就是为关键词添加与其相关联的文档。当这个文档已经和当前的关键词相关联的时候就将fileCollection[infile]1,即增加这个关键词在同一个文档中的出现次数。在这个版本中,Searcharoo把去除HTML标签后的内容按单词分开所形成的单词集合作为关键词集合,也就是把文档中每个内容单词都作为关键词,而没有进行更多的过滤(更多的处理细节会在后续版本中逐步添加)。最后,就是递归地处理当前目录的子目录,按同样的过程处理每个子目录中的文件。当所有文件都处理完成后,在Catalog对象中就保存了以关键词为Key值的倒排索引结构,并将其保存到缓存中。

 

l  搜索

         检索开始的时候,应用程序会首先从缓存加载已经生成的索引数据,如果没有索引则自动跳转到爬行器页面进行索引构建。反之当已经找到缓存中的索引数据后就会显示搜索界面,在搜索框中输入搜索后就开始检索流程,下面是检索功能的主要代码: 

Code
searchWord = searchWord.Trim('?''\"'',''\''';'':''.''('')').ToLower();
Hashtable retval 
= null;
if (index.ContainsKey(searchWord))
// does all the work !!!
    Word thematch 
= (Word)index[searchWord];
    retval 
= thematch.InFiles(); // return the collection of File objects
}

 

搜索代码比较简单,就是直接从索引的Hashtable中按Key查找对应的项,每一个项代表了一个关键词以及这个关键词所在的文档和词频。如果搜索结果不为空,则将搜索结果格化地显示在搜索结果页面上。因为这部分内容比较简单,而且没有什么特别的处理,都是使用Response.Write方法输出文档,因此输出搜索结果这部分就不再赘述。

Searcharoo第一版构建了一个基本的搜索引擎框架,虽然功能还很有限甚至还不能完成我们常规意义上的检索任务,但是这个版本已经包含了作为一个搜索引擎的主要部分,后成的版本主要就是在这个基础之上的升级和改进,以增量的方式不断地迭代开发出一个更加完善的搜索引擎。