国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(一)

網(wǎng)絡(luò)爬蟲在信息檢索與處理中有很大的作用,是收集網(wǎng)絡(luò)信息的重要工具。

接下來就介紹一下爬蟲的簡(jiǎn)單實(shí)現(xiàn)。

爬蟲的工作流程如下

爬蟲自指定的URL地址開始下載網(wǎng)絡(luò)資源,直到該地址和所有子地址的指定資源都下載完畢為止。

下面開始逐步分析爬蟲的實(shí)現(xiàn)。

 

1. 待下載集合與已下載集合

為了保存需要下載的URL,同時(shí)防止重復(fù)下載,我們需要分別用了兩個(gè)集合來存放將要下載的URL和已經(jīng)下載的URL。

因?yàn)樵诒4鎁RL的同時(shí)需要保存與URL相關(guān)的一些其他信息,如深度,所以這里我采用了Dictionary來存放這些URL。

具體類型是Dictionary<string, int> 其中string是Url字符串,int是該Url相對(duì)于基URL的深度。

每次開始時(shí)都檢查未下載的集合,如果已經(jīng)為空,說明已經(jīng)下載完畢;如果還有URL,那么就取出第一個(gè)URL加入到已下載的集合中,并且下載這個(gè)URL的資源。

 

2. HTTP請(qǐng)求和響應(yīng)

C#已經(jīng)有封裝好的HTTP請(qǐng)求和響應(yīng)的類HttpWebRequestHttpWebResponse,所以實(shí)現(xiàn)起來方便不少。

為了提高下載的效率,我們可以用多個(gè)請(qǐng)求并發(fā)的方式同時(shí)下載多個(gè)URL的資源,一種簡(jiǎn)單的做法是采用異步請(qǐng)求的方法。

控制并發(fā)的數(shù)量可以用如下方法實(shí)現(xiàn)

 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]) //判斷此編號(hào)的工作實(shí)例是否空閑10         {11             RequestResource(i); //讓此工作實(shí)例請(qǐng)求資源12         }13     }14 }

 由于沒有顯式開新線程,所以用一個(gè)工作實(shí)例來表示一個(gè)邏輯工作線程

1 private bool[] _reqsBusy = null; //每個(gè)元素代表一個(gè)工作實(shí)例是否正在工作2 private int _reqCount = 4; //工作實(shí)例的數(shù)量

 每次一個(gè)工作實(shí)例完成工作,相應(yīng)的_reqsBusy就設(shè)為false,并調(diào)用DispatchWork,那么DispatchWork就能給空閑的實(shí)例分配新任務(wù)了。

 

 接下來是發(fā)送請(qǐng)求

 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) //判斷是否還有未下載的URL10              {11                  _workingSignals.FinishWorking(index); //設(shè)置工作實(shí)例的狀態(tài)為Finished12                  return;13              }14              _reqsBusy[index] = true;15              _workingSignals.StartWorking(index); //設(shè)置工作狀態(tài)為Working16              depth = _urlsUnload.First().Value; //取出第一個(gè)未下載的URL17              url = _urlsUnload.First().Key;18              _urlsLoaded.Add(url, depth); //把該URL加入到已下載里19              _urlsUnload.Remove(url); //把該URL從未下載中移除20          }21                  22          HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);23          req.Method = _method; //請(qǐng)求方法24          req.Accept = _accept; //接受的內(nèi)容25          req.UserAgent = _userAgent; //用戶代理26          RequestState rs = new RequestState(req, url, depth, index); //回調(diào)方法的參數(shù)27          var result = req.BeginGetResponse(new AsyncCallback(ReceivedResource), rs); //異步請(qǐng)求28          ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, //注冊(cè)超時(shí)處理方法29                  TimeoutCallback, rs, _maxTime, true);30      }31      catch (WebException we)32      {33          MessageBox.Show("RequestResource " + we.Message + url + we.Status);34      }35  }

第7行為了保證多個(gè)任務(wù)并發(fā)時(shí)的同步,加上了互斥鎖。_locker是一個(gè)Object類型的成員變量。

第9行判斷未下載集合是否為空,如果為空就把當(dāng)前工作實(shí)例狀態(tài)設(shè)為Finished;如果非空則設(shè)為Working并取出一個(gè)URL開始下載。當(dāng)所有工作實(shí)例都為Finished的時(shí)候,說明下載已經(jīng)完成。由于每次下載完一個(gè)URL后都調(diào)用DispatchWork,所以可能激活其他的Finished工作實(shí)例重新開始工作。

第26行的請(qǐng)求的額外信息在異步請(qǐng)求的回調(diào)方法作為參數(shù)傳入,之后還會(huì)提到。

第27行開始異步請(qǐng)求,這里需要傳入一個(gè)回調(diào)方法作為響應(yīng)請(qǐng)求時(shí)的處理,同時(shí)傳入回調(diào)方法的參數(shù)。

第28行給該異步請(qǐng)求注冊(cè)一個(gè)超時(shí)處理方法TimeoutCallback,最大等待時(shí)間是_maxTime,且只處理一次超時(shí),并傳入請(qǐng)求的額外信息作為回調(diào)方法的參數(shù)。

 

RequestState的定義是

 1 class RequestState 2 { 3     private const int BUFFER_SIZE = 131072; //接收數(shù)據(jù)包的空間大小 4     private byte[] _data = new byte[BUFFER_SIZE]; //接收數(shù)據(jù)包的buffer 5     private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字符 6  7     public HttpWebRequest Req { get; private set; } //請(qǐng)求 8     public string Url { get; private set; } //請(qǐng)求的URL 9     public int Depth { get; private set; } //此次請(qǐng)求的相對(duì)深度10     public int Index { get; private set; } //工作實(shí)例的編號(hào)11     public Stream ResStream { get; set; } //接收數(shù)據(jù)流12     public StringBuilder Html13     {14         get15         {16             return _sb;17         }18     }19 20     public byte[] Data21     {22         get23         {24             return _data;25         }26     }27 28     public int BufferSize29     {30         get31         {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) //判斷是否是超時(shí) 4     { 5         RequestState rs = state as RequestState; 6         if (rs != null) 7         { 8             rs.Req.Abort(); //撤銷請(qǐng)求 9         }10         _reqsBusy[rs.Index] = false; //重置工作狀態(tài)11         DispatchWork(); //分配新任務(wù)12     }13 }

 

接下來就是要處理請(qǐng)求的響應(yīng)了

 1 private void ReceivedResource(IAsyncResult ar) 2 { 3     RequestState rs = (RequestState)ar.AsyncState; //得到請(qǐng)求時(shí)傳入的參數(shù) 4     HttpWebRequest req = rs.Req; 5     string url = rs.Url; 6     try 7     { 8         HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(ar); //獲取響應(yīng) 9         if (_stop) //判斷是否中止下載10         {11             res.Close();12             req.Abort();13             return;14         }15         if (res != null && res.StatusCode == HttpStatusCode.OK) //判斷是否成功獲取響應(yīng)16         {17             Stream resStream = res.GetResponseStream(); //得到資源流18             rs.ResStream = resStream;19             var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //異步請(qǐng)求讀取數(shù)據(jù)20                 new AsyncCallback(ReceivedData), rs);21         }22         else //響應(yīng)失敗23         {24             res.Close();25             rs.Req.Abort();26             _reqsBusy[rs.Index] = false; //重置工作狀態(tài)27             DispatchWork(); //分配新任務(wù)28         }29     }30     catch (WebException we)31     {32         MessageBox.Show("ReceivedResource " + we.Message + url + we.Status);33     }34

第19行這里采用了異步的方法來讀數(shù)據(jù)流是因?yàn)槲覀冎安捎昧水惒降姆绞秸?qǐng)求,不然的話不能夠正常的接收數(shù)據(jù)。

該異步讀取的方式是按包來讀取的,所以一旦接收到一個(gè)包就會(huì)調(diào)用傳入的回調(diào)方法ReceivedData,然后在該方法中處理收到的數(shù)據(jù)。

該方法同時(shí)傳入了接收數(shù)據(jù)的空間rs.Data和空間的大小rs.BufferSize

 

接下來是接收數(shù)據(jù)和處理

 1 private void ReceivedData(IAsyncResult ar) 2 { 3     RequestState rs = (RequestState)ar.AsyncState; //獲取參數(shù) 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     try13     {14         read = resStream.EndRead(ar); //獲得數(shù)據(jù)讀取結(jié)果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); //利用獲得的數(shù)據(jù)創(chuàng)建內(nèi)存流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, //再次異步請(qǐng)求讀取數(shù)據(jù)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; //重置工作狀態(tài)37         DispatchWork(); //分配新任務(wù)38     }39     catch (WebException we)40     {41         MessageBox.Show("ReceivedData Web " + we.Message + url + we.Status);42     }43 } 

第14行獲得了讀取的數(shù)據(jù)大小read,如果read>0說明數(shù)據(jù)可能還沒有讀完,所以在27行繼續(xù)請(qǐng)求讀下一個(gè)數(shù)據(jù)包;

如果read<=0說明所有數(shù)據(jù)已經(jīng)接收完畢,這時(shí)rs.Html中存放了完整的HTML數(shù)據(jù),就可以進(jìn)行下一步的處理了。

第26行把這一次得到的字符串拼接在之前保存的字符串的后面,最后就能得到完整的HTML字符串。

 

然后說一下判斷所有任務(wù)完成的處理

 1 private void StartDownload() 2 { 3     _checkTimer = new Timer(new TimerCallback(CheckFinish), null, 0, 300); 4     DispatchWork(); 5 } 6  7 private void CheckFinish(object param) 8 { 9     if (_workingSignals.IsFinished()) //檢查是否所有工作實(shí)例都為Finished10     {11         _checkTimer.Dispose(); //停止定時(shí)器12         _checkTimer = null;13         if (DownloadFinish != null && _ui != null) //判斷是否注冊(cè)了完成事件14         {15             _ui.Dispatcher.Invoke(DownloadFinish, _index); //調(diào)用事件16         }17     }18 }

第3行創(chuàng)建了一個(gè)定時(shí)器,每過300ms調(diào)用一次CheckFinish來判斷是否完成任務(wù)。
第15行提供了一個(gè)完成任務(wù)時(shí)的事件,可以給客戶程序注冊(cè)。_index里存放了當(dāng)前下載URL的個(gè)數(shù)。

該事件的定義是

1 public delegate void DownloadFinishHandler(int count);2 3 /// <summary>4 /// 全部鏈接下載分析完畢后觸發(fā)5 /// </summary>6 public event DownloadFinishHandler DownloadFinish = null;

 

下接后篇《用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(二)》

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
用C跑爬蟲
C#同步方法和異步方法的區(qū)別
Java網(wǎng)絡(luò)爬蟲的實(shí)現(xiàn)
JDBC詳解
python爬取mv
輕盈高效的異步訪問庫grequests庫
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服