65.9K
CodeProject 正在变化。 阅读更多。
Home

C#/VB - 自动化网络爬虫/网络机器人

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (31投票s)

2004 年 3 月 16 日

5分钟阅读

viewsIcon

146586

downloadIcon

2810

构建灵活的 WebRobot 并使用 WebSpider 处理整个站点

引言

什么是网络爬虫

网络爬虫或爬虫程序是一种自动化程序,它会跟踪网站上的链接并调用网络机器人来处理每个链接的内容。

什么是网络机器人

网络机器人是一种处理通过链接找到的内容的程序。网络机器人可用于索引页面或根据预定义的查询提取有用信息,常见的例子包括 - 链接检查器、电子邮件地址提取器、多媒体提取器和更新监视器。

背景

我最近有一个合同,需要构建一个网页链接检查器,该组件必须能够检查存储在数据库中的链接,以及通过本地文件系统和互联网检查网站上的链接。

本文解释了网络机器人网络爬虫以及如何通过专门的内容处理程序来增强网络机器人,代码中已删除一些冗余代码,例如 try 块、变量初始化和次要方法。

类概述

构成网络机器人的类有:WebPageState,它表示一个 URI 及其在处理链中的当前状态,以及 IWebPageProcessor 的一个实现,该实现执行实际的 URI 读取、调用内容处理程序和处理页面错误。

网络爬虫只有一个类 WebSpider,它维护一个待处理/已处理 URI 列表,这些 URI 包含在 WebPageState 对象列表中,并针对每个 WebPageState 运行 WebPageProcessor 以提取其他页面的链接并测试 URI 是否有效。

使用代码 - WebRobot

网页处理由实现 IWebPageProcessor 的对象处理。Process 方法期望接收一个 WebPageState,该对象将在页面处理过程中更新,如果一切成功,该方法将返回 true。在页面读取后,还可以通过将 WebPageContentDelegate 委托分配给处理器来调用任意数量的内容处理程序。

public delegate void WebPageContentDelegate( WebPageState state );

public interface IWebPageProcessor
{
   bool Process( WebPageState state );

   WebPageContentDelegate ContentHandler { get; set; }
}

WebPageState 对象保存正在处理的 URI 的状态和内容信息。此对象的所有属性都是可读/写的,但 URI 必须通过构造函数传入。

public class WebPageState
{
   private WebPageState( ) {}

   public WebPageState( Uri uri )
   {
      m_uri             = uri;
   }

   public WebPageState( string uri )
      : this( new Uri( uri ) ) { }

   Uri      m_uri;                           // URI to be processed
   string   m_content;                       // Content of webpage
   string   m_processInstructions   = "";    // User defined instructions 
                 // for content handlers
   bool     m_processStarted        = false; 
                // Becomes true when processing starts
   bool     m_processSuccessfull    = false; 
                // Becomes true if process was successful
   string   m_statusCode;                    
                // HTTP status code
   string   m_statusDescription;             
               // HTTP status description, or exception message

   // Standard Getters/Setters....
}

WebPageProcessorIWebPageProcessor 的一个实现,它负责实际的读取内容、处理错误代码/异常和调用内容处理程序的工作。WebPageProcessor 可以被替换或扩展以提供附加功能,但添加内容处理程序通常是更好的选择。

   public class WebPageProcessor : IWebPageProcessor
   {
      public bool Process( WebPageState state )
      {
         state.ProcessStarted       = true;
         state.ProcessSuccessfull   = false;

         // Use WebRequest.Create to handle URI's for 
         // the following schemes: file, http & https
         WebRequest  req = WebRequest.Create( state.Uri );
         WebResponse res = null;

         try
         {
            // Issue a response against the request. 
            // If any problems are going to happen they
            // they are likly to happen here in the form of an exception.
            res = req.GetResponse( );

            // If we reach here then everything is likly to be OK.
            if ( res is HttpWebResponse )
            {
               state.StatusCode        = 
                ((HttpWebResponse)res).StatusCode.ToString( );
               state.StatusDescription = 
                ((HttpWebResponse)res).StatusDescription;
            }
            if ( res is FileWebResponse )
            {
               state.StatusCode        = "OK";
               state.StatusDescription = "OK";
            }

            if ( state.StatusCode.Equals( "OK" ) )
            {
               // Read the contents into our state 
               // object and fire the content handlers
               StreamReader   sr    = new StreamReader( 
                 res.GetResponseStream( ) );

               state.Content        = sr.ReadToEnd( );

               if ( ContentHandler != null )
               {
                  ContentHandler( state );
               }
            }

            state.ProcessSuccessfull = true;
         }
         catch( Exception ex )
         {
            HandleException( ex, state );
         }
         finally
         {
            if ( res != null )
            {
               res.Close( );
            }
         }

         return state.ProcessSuccessfull;
      }
   }

   // Store any content handlers
   private WebPageContentDelegate m_contentHandler = null;

   public WebPageContentDelegate ContentHandler
   {
      get { return m_contentHandler; }
      set { m_contentHandler = value; }
   }

WebPageProcessor 中还有其他私有方法,用于在处理 "file://" 方案时处理 HTTP 错误代码和文件未找到错误,以及更严重的异常。

使用代码 - WebSpider

WebSpider 类实际上只是以特定方式调用 WebRobot 的一个载体。它为机器人提供了一个专门的内容处理程序,用于爬行网络链接,并维护一个待处理页面和已访问页面的列表。当前的 WebSpider 被设计为从给定的 URI 开始,并将完全页面处理限制在一个基本路径内。

// CONSTRUCTORS
//
// Process a URI, until all links are checked, 
// only add new links for processing if they
// point to the same host as specified in the startUri.
public WebSpider(
   string            startUri
   ) : this ( startUri, -1 ) { }

// As above only limit the links to uriProcessedCountMax.
public WebSpider(
   string            startUri,
   int               uriProcessedCountMax
   ) : this ( startUri, "", uriProcessedCountMax, 
     false, new WebPageProcessor( ) ) { }

// As above, except new links are only added if
// they are on the path specified by baseUri.
public WebSpider(
   string            startUri,
   string            baseUri,
   int               uriProcessedCountMax
   ) : this ( startUri, baseUri, uriProcessedCountMax, 
     false, new WebPageProcessor( ) ) { }

// As above, you can specify whether the web page
// content is kept after it is processed, by
// default this would be false to conserve memory
// when used on large sites.
public WebSpider(
   string            startUri,
   string            baseUri,
   int               uriProcessedCountMax,
   bool              keepWebContent,
   IWebPageProcessor webPageProcessor )
{
   // Initialize web spider ...
}

为什么会有基本路径限制?

由于互联网上有数万亿个页面,因此该爬虫会检查它找到的所有链接以确定它们是否有效,但只有当这些链接属于初始网站或该网站子路径的上下文时,它才会将新链接添加到待处理队列中。

因此,如果我们从 www.myhost.com/index.html 开始,并且此页面链接到 www.myhost.com/pageWithSomeLinks.htmlwww.google.com/pageWithManyLinks.html,那么 WebRobot 将会针对这两个链接被调用以检查它们是否有效,但它只会将 www.myhost.com/pageWithSomeLinks.html 内找到的新链接添加到队列中。

调用 Execute 方法来启动爬虫。此方法会将 startUri 添加到待处理页面的 Queue 中,然后调用 IWebPageProcessor,直到没有页面可供处理为止。

public void Execute( )
{
   AddWebPage( StartUri, StartUri.AbsoluteUri );

   while ( WebPagesPending.Count > 0 &&
      ( UriProcessedCountMax == -1 || UriProcessedCount 
        < UriProcessedCountMax ) )
   {
      WebPageState state = (WebPageState)m_webPagesPending.Dequeue( );

      m_webPageProcessor.Process( state );

      if ( ! KeepWebContent )
      {
         state.Content = null;
      }

      UriProcessedCount++;
   }
}

只有当 URI(不包括锚点)指向一个路径或一个有效的页面(例如 .html、.aspx、.jsp 等)并且之前没有被见过时,页面才能被添加到队列中。

private bool AddWebPage( Uri baseUri, string newUri )
{
   Uri      uri      = new Uri( baseUri, 
     StrUtil.LeftIndexOf( newUri, "#" ) );

   if ( ! ValidPage( uri.LocalPath ) || m_webPages.Contains( uri ) )
   {
      return false;
   }
   WebPageState state = new WebPageState( uri );

   if ( uri.AbsoluteUri.StartsWith( BaseUri.AbsoluteUri ) )
   {
      state.ProcessInstructions += "Handle Links";
   }

   m_webPagesPending.Enqueue  ( state );
   m_webPages.Add             ( uri, state );

   return true;
}

运行爬虫的示例

以下代码显示了调用 WebSpider 的三个示例,所示路径仅为示例,不代表此网站的真实结构。注意: 示例中的 Bondi Beer 网站是我使用我自己的 SiteGenerator 构建的。这个易于使用的程序可以从专有数据文件、XML/XSLT 文件、数据库、RSS feed 等动态内容生成静态网站...

/*
* Check for broken links found on this website, limit the spider to 100 pages.
*/
WebSpider spider = new WebSpider( "http://www.bondibeer.com.au/", 100 );
spider.execute( );

/*
* Check for broken links found on this website, 
* there is no limit on the number
* of pages, but it will not look for new links on
* pages that are not within the
* path http://www.bondibeer.com.au/products/.  This
* means that the home page found
* at http://www.bondibeer.com.au/home.html may be
* checked for existence if it was
* called from the somepub/index.html but any
* links within that page will not be
* added to the pending list, as there on an a lower path.
*/
spider = new WebSpider(
      "http://www.bondibeer.com.au/products/somepub/index.html",
      "http://www.bondibeer.com.au/products/", -1 );
spider.execute( );

/*
* Check for pages on the website that have funny 
* jokes or pictures of sexy women.
*/
spider = new WebSpider( "http://www.bondibeer.com.au/" );
spider.WebPageProcessor.ContentHandler += 
  new WebPageContentDelegate( FunnyJokes );
spider.WebPageProcessor.ContentHandler += 
  new WebPageContentDelegate( SexyWomen );
spider.execute( );

private void FunnyJokes( WebPageState state )
{
   if( state.Content.IndexOf( "Funny Joke" ) > -1 )
   {
      // Do something
   }
}
private void SexyWomen( WebPageState state )
{
   Match       m     = RegExUtil.GetMatchRegEx( 
     RegularExpression.SrcExtractor, state.Content );
   string      image;

   while( m.Success )
   {
      m     = m.NextMatch( );
      image = m.Groups[1].ToString( ).toLowerCase( );

      if ( image.indexOf( "sexy" ) > -1 || 
        image.indexOf( "women" ) > -1 )
      {
         DownloadImage( image );
      }
   }
}

结论

WebSpider 足够灵活,可用于各种有用的场景,并且可以成为数据挖掘互联网和内联网网站的强大工具。我很想听听人们是如何使用这段代码的。

待解决的问题

这些问题并不严重,但如果有人有什么想法,请分享。

  • state.ProcessInstructions - 这实际上只是一个快速的解决方案,用于提供内容处理程序可以自行使用的指令。我正在寻找一个更优雅的解决方案来解决这个问题。
  • 多线程爬虫 - 该项目最初是一个多线程爬虫,但很快就被放弃了,因为我发现使用线程处理每个 URI 的性能要慢得多。似乎瓶颈在于 GetResponse,它似乎无法在多个线程中运行。
  • URI 有效,但返回查询数据是一个错误的页面。 - 当前处理器不处理 URI 指向有效页面但 Web 服务器返回的页面被认为是错误的场景。例如 http://www.validhost.com/validpage.html?opensubpage=invalidid。解决此问题的一个方法是读取返回页面的内容并查找关键信息,但这种技术有点不可靠。
© . All rights reserved.