好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

用C#实现网络爬虫

用C#实现网络爬虫

网络爬虫在信息检索与处理中有很大的作用,是收集网络信息的重要工具。

接下来就介绍一下爬虫的简单实现。

爬虫的工作流程如下

爬虫自指定的URL地址开始下载网络资源,直到该地址和所有子地址的指定资源都下载完毕为止。

下面开始逐步分析爬虫的实现。

 

1. 待下载集合与已下载集合

为了保存需要下载的URL,同时防止重复下载,我们需要分别用了两个集合来存放将要下载的URL和已经下载的URL。

因为在保存URL的同时需要保存与URL相关的一些其他信息,如深度,所以这里我采用了 Dictionary 来存放这些URL。

具体类型是 Dictionary<string, int>  其中 string 是Url字符串, int 是该Url相对于基URL的深度。

每次开始时都检查未下载的集合,如果已经为空,说明已经下载完毕;如果还有URL,那么就取出第一个URL加入到已下载的集合中,并且下载这个URL的资源。

 

2. HTTP请求和响应

C#已经有封装好的HTTP请求和响应的类 HttpWebRequest 和 HttpWebResponse ,所以实现起来方便不少。

为了提高下载的效率,我们可以用多个请求并发的方式同时下载多个URL的资源,一种简单的做法是采用异步请求的方法。

控制并发的数量可以用如下方法实现

   1   private   void   DispatchWork()
   2   {
   3       if  (_stop)  //  判断是否中止下载 
  4       {
   5           return  ;
   6       }
   7       for  ( int  i =  0 ; i < _reqCount; i++ )
   8       {
   9           if  (!_reqsBusy[i])  //  判断此编号的工作实例是否空闲 
 10           {
  11              RequestResource(i);  //  让此工作实例请求资源 
 12           }
  13       }
  14  } 

  由于没有显式开新线程,所以用一个工作实例来表示一个逻辑工作线程

  1   private   bool [] _reqsBusy =  null ;  //  每个元素代表一个工作实例是否正在工作 
 2   private   int  _reqCount =  4 ;  //  工作实例的数量  

  每次一个工作实例完成工作,相应的 _reqsBusy 就设为 false ,并调用 DispatchWork ,那么 DispatchWork 就能给空闲的实例分配新任务了。

 接下来是发送请求

   1   private   void  RequestResource( int   index)
   2   {
   3       int   depth;
   4       string  url =  ""  ;
   5       try 
  6       {
   7           lock   (_locker)
   8           {
   9               if  (_urlsUnload.Count <=  0  )
  10               {
  11                   _workingSignals.FinishWorking(index);
  12                   return  ;
  13               }
  14              _reqsBusy[index] =  true  ;
  15               _workingSignals.StartWorking(index);
  16              depth =  _urlsUnload.First().Value;
  17              url =  _urlsUnload.First().Key;
  18               _urlsLoaded.Add(url, depth);
  19               _urlsUnload.Remove(url);
  20           }
  21                  
 22          HttpWebRequest req =  (HttpWebRequest)WebRequest.Create(url);
  23          req.Method = _method;  //  请求方法 
 24          req.Accept = _accept;  //  接受的内容 
 25          req.UserAgent = _userAgent;  //  用户代理 
 26          RequestState rs =  new  RequestState(req, url, depth, index);  //  回调方法的参数 
 27           var  result = req.BeginGetResponse( new  AsyncCallback(ReceivedResource), rs);  //  异步请求 
 28          ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle,  //  注册超时处理方法 
 29                  TimeoutCallback, rs, _maxTime,  true  );
  30       }
  31       catch   (WebException we)
  32       {
  33          MessageBox.Show( "  RequestResource   "  + we.Message + url +  we.Status);
  34       }
  35  } 

第26行的请求的额外信息在异步请求的回调方法作为参数传入,之后还会提到。

第27行开始异步请求,这里需要传入一个回调方法作为响应请求时的处理,同时传入回调方法的参数。

第28行给该异步请求注册一个超时处理方法 TimeoutCallback ,最大等待时间是 _maxTime ,且只处理一次超时,并传入请求的额外信息作为回调方法的参数。

 

RequestState 的定义是

   1   class   RequestState
   2   {
   3       private   const   int  BUFFER_SIZE =  131072 ;  //  接收数据包的空间大小 
  4       private   byte [] _data =  new   byte [BUFFER_SIZE];  //  接收数据包的buffer 
  5       private  StringBuilder _sb =  new  StringBuilder();  //  存放所有接收到的字符 
  6  
  7       public  HttpWebRequest Req {  get ;  private   set ; }  //  请求 
  8       public   string  Url {  get ;  private   set ; }  //  请求的URL 
  9       public   int  Depth {  get ;  private   set ; }  //  此次请求的相对深度 
 10       public   int  Index {  get ;  private   set ; }  //  工作实例的编号 
 11       public  Stream ResStream {  get ;  set ; }  //  接收数据流 
 12       public   StringBuilder Html
  13       {
  14           get 
 15           {
  16               return   _sb;
  17           }
  18       }
  19  
 20       public   byte  [] Data
  21       {
  22           get 
 23           {
  24               return   _data;
  25           }
  26       }
  27  
 28       public   int   BufferSize
  29       {
  30           get 
 31           {
  32               return   BUFFER_SIZE;
  33           }
  34       }
  35  
 36       public  RequestState(HttpWebRequest req,  string  url,  int  depth,  int   index)
  37       {
  38          Req =  req;
  39          Url =  url;
  40          Depth =  depth;
  41          Index =  index;
  42       }
  43  }  

  

TimeoutCallback 的定义是

   1   private   void  TimeoutCallback( object  state,  bool   timedOut)
   2   {
   3       if  (timedOut)  //  判断是否是超时 
  4       {
   5          RequestState rs = state  as   RequestState;
   6           if  (rs !=  null  )
   7           {
   8              rs.Req.Abort();  //  撤销请求 
  9           }
  10          _reqsBusy[rs.Index] =  false ;  //  重置工作状态 
 11          DispatchWork();  //  分配新任务 
 12       }
  13  } 

接下来就是要处理请求的响应了

   1   private   void   ReceivedResource(IAsyncResult ar)
   2   {
   3      RequestState rs = (RequestState)ar.AsyncState;  //  得到请求时传入的参数 
  4      HttpWebRequest req =  rs.Req;
   5       string  url =  rs.Url;
   6       try 
  7       {
   8          HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(ar);  //  获取响应 
  9           if  (_stop)  //  判断是否中止下载 
 10           {
  11               res.Close();
  12               req.Abort();
  13               return  ;
  14           }
  15           if  (res !=  null  && res.StatusCode == HttpStatusCode.OK)  //  判断是否成功获取响应 
 16           {
  17              Stream resStream = res.GetResponseStream();  //  得到资源流 
 18              rs.ResStream =  resStream;
  19               var  result = resStream.BeginRead(rs.Data,  0 , rs.BufferSize,  //  异步请求读取数据 
 20                   new   AsyncCallback(ReceivedData), rs);
  21           }
  22           else   //  响应失败 
 23           {
  24               res.Close();
  25               rs.Req.Abort();
  26              _reqsBusy[rs.Index] =  false ;  //  重置工作状态 
 27              DispatchWork();  //  分配新任务 
 28           }
  29       }
  30       catch   (WebException we)
  31       {
  32          MessageBox.Show( "  ReceivedResource   "  + we.Message + url +  we.Status);
  33       }
  34  }  

第19行这里采用了异步的方法来读数据流是因为我们之前采用了异步的方式请求,不然的话不能够正常的接收数据。

该异步读取的方式是按包来读取的,所以一旦接收到一个包就会调用传入的回调方法 ReceivedData ,然后在该方法中处理收到的数据。

该方法同时传入了接收数据的空间 rs.Data 和空间的大小 rs.BufferSize 。

 

接下来是接收数据和处理

   1   private   void   ReceivedData(IAsyncResult ar)
   2   {
   3      RequestState rs = (RequestState)ar.AsyncState;  //  获取参数 
  4      HttpWebRequest req =  rs.Req;
   5      Stream resStream =  rs.ResStream;
   6       string  url =  rs.Url;
   7       int  depth =  rs.Depth;
   8       string  html =  null  ;
   9       int  index =  rs.Index;
  10       int  read =  0  ;
  11  
 12       try 
 13       {
  14          read = resStream.EndRead(ar);  //  获得数据读取结果 
 15           if  (_stop) //  判断是否中止下载 
 16           {
  17               rs.ResStream.Close();
  18               req.Abort();
  19               return  ;
  20           }
  21           if  (read >  0  )
  22           {
  23              MemoryStream ms =  new  MemoryStream(rs.Data,  0 , read);  //  利用获得的数据创建内存流 
 24              StreamReader reader =  new   StreamReader(ms, _encoding);
  25               string  str = reader.ReadToEnd();  //  读取所有字符 
 26              rs.Html.Append(str);  //   添加到之前的末尾 
 27               var  result = resStream.BeginRead(rs.Data,  0 , rs.BufferSize,  //  再次异步请求读取数据 
 28                   new   AsyncCallback(ReceivedData), rs);
  29               return  ;
  30           }
  31          html =  rs.Html.ToString();
  32          SaveContents(html, url);  //  保存到本地 
 33           string [] links = GetLinks(html);  //  获取页面中的链接 
 34          AddUrls(links, depth +  1 );  //  过滤链接并添加到未下载集合中 
 35  
 36          _reqsBusy[index] =  false ;  //  重置工作状态 
 37          DispatchWork();  //  分配新任务 
 38       }
  39       catch   (WebException we)
  40       {
  41          MessageBox.Show( "  ReceivedData Web   "  + we.Message + url +  we.Status);
  42       }
  43  }  

第14行获得了读取的数据大小 read ,如果 read>0 说明数据可能还没有读完,所以在27行继续请求读下一个数据包;

如果 read<=0 说明所有数据已经接收完毕,这时 rs.Html 中存放了完整的HTML数据,就可以进行下一步的处理了。

 

未完待续...

 

 

标签:  爬虫 ,  C#

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于用C#实现网络爬虫的详细内容...

  阅读:48次