代码改变世界

Searcharoo源码学习日志(二)

2009-10-08 02:19  Yin.P  阅读(1859)  评论(0编辑  收藏  举报

  Searcharoo第二版相对第一版做了很多改动,如将之前的基于当前站点的文件索引换成了基于Internet的网络爬行器、新增了HtmlDocument对象作为中间文档对象用于暂存文档信息、增加对已访问页面的判断处理、新增使用HttpWebRequest对象去获取Internet文档内容、新增去除页面内容中的脚本块样式块等功能。

 

l  对象模型

         在新一版片的Searcharoo中,对象模型没有发生变化,和前一版是完全是一样的,如下图。下面从爬行器开始分析。

图一:Searcharoo v2的对象模型(转自www.searcharoo.net

        

l  Spider(Crawler)

         Searcharoo第二版爬行器(第一版叫Crawler,从第二版开始名称改为Spider)的核心方法是Process,在刚进入索引构造的时候,应用程序会先从配置信息中获取一个入口URI,以这个入口URI作为索引的起点。在Process方法中有三个主要的处理步骤,分别为Download方法、Parse方法以及填充索引数据。Download方法是从网络上下载页面内容,Parse是对页面内容进行处理,当下载的页面内容处理完毕后将其填充到索引数据结构中。下面先看看Process方法的整体结构: 

Code
if (visited.Contains(url.ToLower()))
{
    …
}
else
{
    …
   visited.Add(url.ToLower());     
    …
   
if (Download(htmldoc))
    {
        
if (htmldoc.RobotIndexOK)
        {
            Parse(htmldoc);             
            wordcount 
= Catalog(htmldoc); 
        }
    }
    …
    
if (null != htmldoc.LocalLinks && htmldoc.RobotFollowOK)
     {
         
foreach (object link in htmldoc.LocalLinks)
         {
             
try
             {
                 Uri linkToFollow 
= new Uri(htmldoc.Uri, link.ToString());
                 
if (linkToFollow.Host == startingUri.Host)
                 {
                      HtmlDocument hd 
= new HtmlDocument(linkToFollow);
                     Process(hd);
                     hd 
= null;
                 }
                 …
           }
            …
     }

 

  以上是Process方法的一个框架,从这个框架中可以看出Process方法的主要处理步骤:首先判断在已访问的URI列表中是不是已经存在当前待处理的URI,即当前URL是不是已经被访问过。如果已经访问则要跳过,否则继续处理这个URI。如果当前的URI没有被访问过那么要先将这个URI放到已访问列表中。然后下载这个URI对应的页面内容,内容下载成功后就调用Parse方法对下载下来的内容进行处理,将当前URI对应页面中的所有符合规则的URI提取出来并保存起来。最后将这个文档存到索引中。网页获取是一个递归的过程,因此接下来是对之前保存下来的URI进行递归处理。在第二版中,URI被分为两类,一类是外部URI,另一类是内部URI。外部URI的目标指向当前站点之外,反之内部URI的目标指向的是本站点。递归处理是基于内部URI这个范围的,其原因是为了防止无止境的递归(当然,控制递归层次和次数的还有其它一些方法)。

         下面再来看看Download这个方法的代码:  

Code

HttpWebRequest req 
= (HttpWebRequest)WebRequest.Create(htmldoc.Uri.AbsoluteUri);

webresponse 
= (HttpWebResponse)req.GetResponse();

if (webresponse != null)
{
    htmldoc.ContentType 
= webresponse.ContentType;
    
switch (htmldoc.MimeType.ToLower())
    {
          
case "text/css":break;
          
default:
          
if (htmldoc.MimeType.IndexOf("text">= 0)
          {
               
string enc = "utf-8"// default
               …
            StreamReader stream 
= new StreamReader(
webresponse.GetResponseStream(), 
               Encoding.GetEncoding(htmldoc.Encoding));
               htmldoc.Uri 
= webresponse.ResponseUri;
               htmldoc.Length 
= webresponse.ContentLength;
               htmldoc.All 
= stream.ReadToEnd();
               …

 

  首先初始化一个HttpWebRequest文档用于从Internet获取网页内容,当收到响应后,判断响应内容的MIME类型,如果是CSS文本则跳过不处理,如果MIME类型标识中带有text的,则将其编码方式设置为UTF8,然后将文档的基本信息设置到HtmlDocument对象中。如果这个阶段处理成功则返回成功标志。

         Process方法的第二步是Parse方法,下面看看它的代码。首先是提取内容中的文档标题:

Code
htmldoc.Title = Regex.Match(htmlData, @"(?<=<title>).*?(?=</title>)",RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture).Value;

 

  然后是提取网页中的META元素信息:

Code
foreach (Match metamatch in Regex.Matches(htmlData , @"<meta\s*(?:(?:\b(\w|-)+\b\s*(?:=\s*(?:""[^""]*""|'[^']*'|[^""'<> ]+)\s*)?)*)/?\s*>"
, RegexOptions.IgnoreCase 
| RegexOptions.ExplicitCapture))
{
     …
    
foreach (Match submetamatch in Regex.Matches(metamatch.Value.ToString(), @"(?<name>\b(\w|-)+\b)\s*=\s*(""(?<value>[^""]*)""|'(?<value>[^']*)'|(?<value>[^""'<> ]+)\s*)+", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture))
{
    
if ("http-equiv" == submetamatch.Groups[1].ToString().ToLower())
{
    metaKey 
= submetamatch.Groups[2].ToString();
}
if (("name" == submetamatch.Groups[1].ToString().ToLower()) && (metaKey == String.Empty))
{
    metaKey 
= submetamatch.Groups[2].ToString();
}
if ("content" == submetamatch.Groups[1].ToString().ToLower())
{
    metaValue 
= submetamatch.Groups[2].ToString();
}
}

 

遍历每个META标签中的键值对,并对每个键进行如下的分析,通过这样的分析来提取当前文档中的描述信息、关键词和爬行器对这个页面文档的处理行为。

Code
switch (metaKey.ToLower())
{
    
case "description":htmldoc.Description = metaValue;
    
break;
    
case "keywords":
    
case "keyword":htmldoc.Keywords = metaValue;
    
break;
    
case "robots":
    
case "robot":htmldoc.SetRobotDirective(metaValue);
    
break;
}

 

 

接下来就要提取这个文档中的所有链接信息了,如下面的代码:

Code
ArrayList linkLocal = new ArrayList();
ArrayList linkExternal 
= new ArrayList();
foreach (Match match in Regex.Matches(htmlData, @"(?<anchor><\s*(a|area)\s*(?:(?:\b\w+\b\s*(?:=\s*(?:""[^""]*""|'[^']*'|[^""'<> ]+)\s*)?)*)?\s*>)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture))
{
       …
foreach (Match submatch in Regex.Matches(match.Value.ToString(), @"(?<name>\b\w+\b)\s*=\s*(""(?<value>[^""]*)""|'(?<value>[^']*)'|(?<value>[^""'<> \s]+)\s*)+", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture))
      {
          
if ("href" == submatch.Groups[1].ToString().ToLower())
          {
                link 
= submatch.Groups[2].ToString();
                
break;
          }
      }
if (link.IndexOf("#"> -1)
{
    link 
= link.Substring(0, link.IndexOf("#"));
}

if (link.IndexOf("javascript:"== -1 && link.IndexOf("mailto:"== -1 && !link.StartsWith("#"&& link != String.Empty)
{
    
if ((link.Length > 8&& (link.StartsWith("http://"|| link.StartsWith("https://"|| link.StartsWith("file://"||     link.StartsWith("//"|| link.StartsWith(@"\\")))
    {
        linkExternal.Add(link);
    }
    
else if (link.StartsWith("?"))
    {
        LinkLocal.Add(htmldoc.Uri.AbsolutePath 
+ link);
    }
    
else
    {
        linkLocal.Add(link);
    }
}

 

  首先初始化一个内部连接集合和一个外部连接集合。然后通过正则表达式解析文档中所有的<anchor>标签, <a>标签以及<area>标签,并提取它们的href属性值,再对这个属性值进行分析,将文档中的链接分别存放到外部链接集合和内部链接集合中。外部链接集合在这个版本中不会被使用,而内部链接则用于进行递归获取。最后将内部和外部链接信息设置到HtmlDocument对象,完成Parse过程的处理。

         完成Parse方法后就回到了前面的Process方法,接下来进行的就是将得到的HtmlDocument对象进行索引,存放到索引数据结构中。最后就是递归处理,对所有的内部链接执行上面的过程。第二版中索引的处理和第一版基本相同,只是在切分内容为关键词集合的时候,这个版本是把文档删除HTML标签后的内容、文档描述以及META信息中的关键词合并到一起处理的。

         从第一版本到第二版本的变化主要体现在索引过程中,而检索和前一版是差不多的,索引数据同样是缓存在内存中,在检索的时候先从缓存取索引,如果取不到就自动转到索引生成页生成索引后再回到搜索界面。 下一篇文章就要接着分析Searcharoo第三版的内容了,从第二版到第三版有相对较大的变化,不但对基本对象模型进行了扩展,而且还增加一些新的功能特性,使Searcharoo更趋近于一个标准的搜索引擎。