用C#实现网络爬虫
- 格式:doc
- 大小:62.00 KB
- 文档页数:10
网络爬虫在信息检索与处理中有很大的作用,是收集网络信息的重要工具。
接下来就介绍一下爬虫的简单实现。
爬虫的工作流程如下爬虫自指定的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 try6 {7 lock (_locker)8 {9 if (_urlsUnload.Count <= 0) //判断是否还有未下载的URL10 {11 _workingSignals.FinishWorking(index); //设置工作实例的状态为Finished12 return;13 }14 _reqsBusy[index] = true;15 _workingSignals.StartWorking(index); //设置工作状态为Working16 depth = _urlsUnload.First().Value; //取出第一个未下载的URL17 url = _urlsUnload.First().Key;18 _urlsLoaded.Add(url, depth); //把该URL加入到已下载里19 _urlsUnload.Remove(url); //把该URL从未下载中移除20 }2122 HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);23 req.Method = _method; //请求法24 req.Accept = _accept; //接受的容25 erAgent = _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 }第7行为了保证多个任务并发时的同步,加上了互斥锁。
_locker是一个Object类型的成员变量。
第9行判断未下载集合是否为空,如果为空就把当前工作实例状态设为Finished;如果非空则设为Working并取出一个URL开始下载。
当所有工作实例都为Finished的时候,说明下载已经完成。
由于每次下载完一个URL后都调用DispatchWork,所以可能激活其他的Finished工作实例重新开始工作。
第26行的请求的额外信息在异步请求的回调法作为参数传入,之后还会提到。
第27行开始异步请求,这里需要传入一个回调法作为响应请求时的处理,同时传入回调法的参数。
第28行给该异步请求注册一个超时处理法TimeoutCallback,最大等待时间是_maxTime,且只处理一次超时,并传入请求的额外信息作为回调法的参数。
RequestState的定义是1 class RequestState2 {3 private const int BUFFER_SIZE = 131072; //接收数据包的空间大小4 private byte[] _data = new byte[BUFFER_SIZE]; //接收数据包的buffer5 private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字符67 public HttpWebRequest Req { get; private set; } //请求8 public string Url { get; private set; } //请求的URL9 public int Depth { get; private set; } //此次请求的相对深度10 public int Index { get; private set; } //工作实例的编号11 public Stream ResStream { get; set; } //接收数据流12 public StringBuilder Html13 {14 get15 {16 return _sb;17 }18 }1920 public byte[] Data21 {22 get23 {24 return _data;25 }26 }2728 public int BufferSize29 {30 get31 {32 return BUFFER_SIZE;33 }34 }3536 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 try7 {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,然后在该法中处理收到的数据。