亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

利用C#實現網絡爬蟲

2020-01-24 01:13:48
字體:
來源:轉載
供稿:網友

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

接下來就介紹一下爬蟲的簡單實現。

爬蟲的工作流程如下

爬蟲自指定的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的資源,一種簡單的做法是采用異步請求的方法。

控制并發的數量可以用如下方法實現

private void DispatchWork(){ if (_stop) //判斷是否中止下載 {  return; } for (int i = 0; i < _reqCount; i++) {  if (!_reqsBusy[i]) //判斷此編號的工作實例是否空閑  {   RequestResource(i); //讓此工作實例請求資源  } }}

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

private bool[] _reqsBusy = null; //每個元素代表一個工作實例是否正在工作private int _reqCount = 4; //工作實例的數量

 每次一個工作實例完成工作,相應的_reqsBusy就設為false,并調用DispatchWork,那么DispatchWork就能給空閑的實例分配新任務了。 

 接下來是發送請求

private void RequestResource(int index) {  int depth;  string url = "";  try  {   lock (_locker)   {    if (_urlsUnload.Count <= 0) //判斷是否還有未下載的URL    {     _workingSignals.FinishWorking(index); //設置工作實例的狀態為Finished     return;    }    _reqsBusy[index] = true;    _workingSignals.StartWorking(index); //設置工作狀態為Working    depth = _urlsUnload.First().Value; //取出第一個未下載的URL    url = _urlsUnload.First().Key;    _urlsLoaded.Add(url, depth); //把該URL加入到已下載里    _urlsUnload.Remove(url); //把該URL從未下載中移除   }        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);   req.Method = _method; //請求方法   req.Accept = _accept; //接受的內容   req.UserAgent = _userAgent; //用戶代理   RequestState rs = new RequestState(req, url, depth, index); //回調方法的參數   var result = req.BeginGetResponse(new AsyncCallback(ReceivedResource), rs); //異步請求   ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, //注冊超時處理方法     TimeoutCallback, rs, _maxTime, true);  }  catch (WebException we)  {   MessageBox.Show("RequestResource " + we.Message + url + we.Status);  } }

第7行為了保證多個任務并發時的同步,加上了互斥鎖。_locker是一個Object類型的成員變量。

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

第26行的請求的額外信息在異步請求的回調方法作為參數傳入,之后還會提到。

第27行開始異步請求,這里需要傳入一個回調方法作為響應請求時的處理,同時傳入回調方法的參數。

第28行給該異步請求注冊一個超時處理方法TimeoutCallback,最大等待時間是_maxTime,且只處理一次超時,并傳入請求的額外信息作為回調方法的參數。

 RequestState的定義是

class RequestState{ private const int BUFFER_SIZE = 131072; //接收數據包的空間大小 private byte[] _data = new byte[BUFFER_SIZE]; //接收數據包的buffer private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字符 public HttpWebRequest Req { get; private set; } //請求 public string Url { get; private set; } //請求的URL public int Depth { get; private set; } //此次請求的相對深度 public int Index { get; private set; } //工作實例的編號 public Stream ResStream { get; set; } //接收數據流 public StringBuilder Html {  get  {   return _sb;  } } public byte[] Data {  get  {   return _data;  } } public int BufferSize {  get  {   return BUFFER_SIZE;  } } public RequestState(HttpWebRequest req, string url, int depth, int index) {  Req = req;  Url = url;  Depth = depth;  Index = index; }}

TimeoutCallback的定義是

private void TimeoutCallback(object state, bool timedOut){ if (timedOut) //判斷是否是超時 {  RequestState rs = state as RequestState;  if (rs != null)  {   rs.Req.Abort(); //撤銷請求  }  _reqsBusy[rs.Index] = false; //重置工作狀態  DispatchWork(); //分配新任務 }}

接下來就是要處理請求的響應了

private void ReceivedResource(IAsyncResult ar){ RequestState rs = (RequestState)ar.AsyncState; //得到請求時傳入的參數 HttpWebRequest req = rs.Req; string url = rs.Url; try {  HttpWebResponse res = (HttpWebResponse)req.EndGetResponse(ar); //獲取響應  if (_stop) //判斷是否中止下載  {   res.Close();   req.Abort();   return;  }  if (res != null && res.StatusCode == HttpStatusCode.OK) //判斷是否成功獲取響應  {   Stream resStream = res.GetResponseStream(); //得到資源流   rs.ResStream = resStream;   var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //異步請求讀取數據    new AsyncCallback(ReceivedData), rs);  }  else //響應失敗  {   res.Close();   rs.Req.Abort();   _reqsBusy[rs.Index] = false; //重置工作狀態   DispatchWork(); //分配新任務  } } catch (WebException we) {  MessageBox.Show("ReceivedResource " + we.Message + url + we.Status); }}

第19行這里采用了異步的方法來讀數據流是因為我們之前采用了異步的方式請求,不然的話不能夠正常的接收數據。

該異步讀取的方式是按包來讀取的,所以一旦接收到一個包就會調用傳入的回調方法ReceivedData,然后在該方法中處理收到的數據。

該方法同時傳入了接收數據的空間rs.Data和空間的大小rs.BufferSize。 

接下來是接收數據和處理

private void ReceivedData(IAsyncResult ar){ RequestState rs = (RequestState)ar.AsyncState; //獲取參數 HttpWebRequest req = rs.Req; Stream resStream = rs.ResStream; string url = rs.Url; int depth = rs.Depth; string html = null; int index = rs.Index; int read = 0; try {  read = resStream.EndRead(ar); //獲得數據讀取結果  if (_stop)//判斷是否中止下載  {   rs.ResStream.Close();   req.Abort();   return;  }  if (read > 0)  {   MemoryStream ms = new MemoryStream(rs.Data, 0, read); //利用獲得的數據創建內存流   StreamReader reader = new StreamReader(ms, _encoding);   string str = reader.ReadToEnd(); //讀取所有字符   rs.Html.Append(str); // 添加到之前的末尾   var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //再次異步請求讀取數據    new AsyncCallback(ReceivedData), rs);   return;  }  html = rs.Html.ToString();  SaveContents(html, url); //保存到本地  string[] links = GetLinks(html); //獲取頁面中的鏈接  AddUrls(links, depth + 1); //過濾鏈接并添加到未下載集合中  _reqsBusy[index] = false; //重置工作狀態  DispatchWork(); //分配新任務 } catch (WebException we) {  MessageBox.Show("ReceivedData Web " + we.Message + url + we.Status); }}

第14行獲得了讀取的數據大小read,如果read>0說明數據可能還沒有讀完,所以在27行繼續請求讀下一個數據包;

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

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

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

private void StartDownload(){ _checkTimer = new Timer(new TimerCallback(CheckFinish), null, 0, 300); DispatchWork();}private void CheckFinish(object param){ if (_workingSignals.IsFinished()) //檢查是否所有工作實例都為Finished {  _checkTimer.Dispose(); //停止定時器  _checkTimer = null;  if (DownloadFinish != null && _ui != null) //判斷是否注冊了完成事件  {   _ui.Dispatcher.Invoke(DownloadFinish, _index); //調用事件  } }}

第3行創建了一個定時器,每過300ms調用一次CheckFinish來判斷是否完成任務。
第15行提供了一個完成任務時的事件,可以給客戶程序注冊。_index里存放了當前下載URL的個數。

該事件的定義是

public delegate void DownloadFinishHandler(int count);/// <summary>/// 全部鏈接下載分析完畢后觸發/// </summary>public event DownloadFinishHandler DownloadFinish = null;

3. 保存頁面文件

這一部分可簡單可復雜,如果只要簡單地把HTML代碼全部保存下來的話,直接存文件就行了。

private void SaveContents(string html, string url){ if (string.IsNullOrEmpty(html)) //判斷html字符串是否有效 {  return; } string path = string.Format("{0}//{1}.txt", _path, _index++); //生成文件名 try {  using (StreamWriter fs = new StreamWriter(path))  {   fs.Write(html); //寫文件  } } catch (IOException ioe) {  MessageBox.Show("SaveContents IO" + ioe.Message + " path=" + path); } if (ContentsSaved != null) {  _ui.Dispatcher.Invoke(ContentsSaved, path, url); //調用保存文件事件 }}

第23行這里又出現了一個事件,是保存文件之后觸發的,客戶程序可以之前進行注冊。

public delegate void ContentsSavedHandler(string path, string url);/// <summary>/// 文件被保存到本地后觸發/// </summary>public event ContentsSavedHandler ContentsSaved = null;

 4. 提取頁面鏈接

提取鏈接用正則表達式就能搞定了,不懂的可以上網搜。

下面的字符串就能匹配到頁面中的鏈接

http://([/w-]+/.)+[/w-]+(/[/w- ./?%&=]*)?

詳細見代碼

private string[] GetLinks(string html){ const string pattern = @"http://([/w-]+/.)+[/w-]+(/[/w- ./?%&=]*)?"; Regex r = new Regex(pattern, RegexOptions.IgnoreCase); //新建正則模式 MatchCollection m = r.Matches(html); //獲得匹配結果 string[] links = new string[m.Count];  for (int i = 0; i < m.Count; i++) {  links[i] = m[i].ToString(); //提取出結果 } return links;}

5. 鏈接的過濾

不是所有的鏈接我們都需要下載,所以通過過濾,去掉我們不需要的鏈接

這些鏈接一般有:

1)、已經下載的鏈接
2)、深度過大的鏈接
3)、其他的不需要的資源,如圖片、CSS等

//判斷鏈接是否已經下載或者已經處于未下載集合中private bool UrlExists(string url) { bool result = _urlsUnload.ContainsKey(url); result |= _urlsLoaded.ContainsKey(url); return result;}private bool UrlAvailable(string url){ if (UrlExists(url)) {  return false; //已經存在 } if (url.Contains(".jpg") || url.Contains(".gif")  || url.Contains(".png") || url.Contains(".css")  || url.Contains(".js")) {  return false; //去掉一些圖片之類的資源 } return true;}private void AddUrls(string[] urls, int depth){ if (depth >= _maxDepth) {  return; //深度過大 } foreach (string url in urls) {  string cleanUrl = url.Trim(); //去掉前后空格  cleanUrl = cleanUrl.TrimEnd('/'); //統一去掉最后面的'/'  if (UrlAvailable(cleanUrl))  {   if (cleanUrl.Contains(_baseUrl))   {    _urlsUnload.Add(cleanUrl, depth); //是內鏈,直接加入未下載集合   }   else   {    // 外鏈處理   }  } }}

 第34行的_baseUrl是爬取的基地址,如http://news.sina.com.cn/,將會保存為news.sina.com.cn,當一個URL包含此字符串時,說明是該基地址下的鏈接;否則為外鏈。 

_baseUrl的處理如下,_rootUrl是第一個要下載的URL

/// <summary>/// 下載根Url/// </summary>public string RootUrl{ get {  return _rootUrl; } set {  if (!value.Contains("http://"))  {   _rootUrl = "http://" + value;  }  else  {   _rootUrl = value;  }  _baseUrl = _rootUrl.Replace("www.", ""); //全站的話去掉www  _baseUrl = _baseUrl.Replace("http://", ""); //去掉協議名  _baseUrl = _baseUrl.TrimEnd('/'); //去掉末尾的'/' }}

至此,基本的爬蟲功能實現就介紹完了。

最后附上源代碼和DEMO程序,爬蟲的源代碼在Spider.cs中,DEMO是一個WPF的程序,Test里是一個控制臺的單線程版版本。

下載地址:C#實現網絡爬蟲DEMO

以上就是C#實現網絡爬蟲的全部過程,代碼解析很詳細,希望對大家的學習有所幫助。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久久亚洲精品国产亚洲老地址| 一本色道久久综合狠狠躁篇的优点| 久久久久久久久久久人体| 国产精品亚洲综合天堂夜夜| 日韩av免费在线观看| 亚洲欧洲日韩国产| 欧美有码在线观看| 伊人久久大香线蕉av一区二区| 亚洲欧美日韩国产精品| 91在线看www| 九九精品在线观看| 国产精品久久久| 日韩av电影国产| 亚洲精品在线不卡| 97精品国产97久久久久久| 97精品国产97久久久久久免费| 国内精品久久久久伊人av| 欧美精品激情在线观看| 国产精品美女主播| 午夜精品在线视频| 亚洲色图在线观看| 久久五月天色综合| 成人在线免费观看视视频| 一区二区在线视频| 久久亚洲欧美日韩精品专区| 欧洲中文字幕国产精品| 国产精品久久999| 国产丝袜一区二区三区免费视频| 亚洲第一天堂av| 亚洲区中文字幕| 国产精品入口免费视频一| 草民午夜欧美限制a级福利片| 热久久这里只有| 伊人亚洲福利一区二区三区| 久久久精品一区二区| 久久99青青精品免费观看| 亚洲人成网站色ww在线| 亚洲美女福利视频网站| 亚洲最大福利视频网站| 色与欲影视天天看综合网| 中文字幕视频一区二区在线有码| 精品久久中文字幕| 97精品国产91久久久久久| 亚洲情综合五月天| 中文字幕自拍vr一区二区三区| 亚洲加勒比久久88色综合| 国产精品丝袜白浆摸在线| 久久久999精品免费| 欧美性生交大片免网| 亚洲精品久久久久久久久| 亚洲美腿欧美激情另类| 日韩在线观看免费高清| 亚洲精品丝袜日韩| 91夜夜未满十八勿入爽爽影院| 欧美日韩黄色大片| 日韩在线资源网| 欧美国产在线电影| 亚洲va欧美va国产综合久久| 久热国产精品视频| 亚洲欧美视频在线| 日韩av色在线| 亚洲一级片在线看| 3344国产精品免费看| 亚洲第一视频网| 亚洲最大的av网站| 国产午夜精品理论片a级探花| 国产精品黄视频| 亚洲国产中文字幕在线观看| 亚洲剧情一区二区| www日韩中文字幕在线看| 欧美激情在线视频二区| 中文字幕免费国产精品| 97人洗澡人人免费公开视频碰碰碰| 亚洲精品在线观看www| 成人久久精品视频| 日韩在线不卡视频| 日韩在线视频中文字幕| 欧美午夜视频一区二区| 成人黄色影片在线| 久久精品视频中文字幕| 日韩av毛片网| 久久青草精品视频免费观看| 国产91精品久久久久久久| 538国产精品一区二区免费视频| 亚洲欧美日韩在线一区| 国产91ⅴ在线精品免费观看| 亚洲人成77777在线观看网| 日韩视频在线一区| 国产视频久久久久| 欧美综合在线第二页| 亚洲一区中文字幕| 欧美日韩中文字幕在线| 最近2019免费中文字幕视频三| 日本精品一区二区三区在线播放视频| 一区二区三区国产在线观看| 亚洲国产精品va在线看黑人| 日本精品一区二区三区在线| 国产精品啪视频| 久久国产精品影片| 欧美体内谢she精2性欧美| 欧美在线国产精品| 亚洲欧美一区二区三区情侣bbw| 国产亚洲欧洲黄色| 97人人爽人人喊人人模波多| 亚洲一区二区中文字幕| 国产精品专区第二| 一区二区三区黄色| 国产精品美女久久| 91亚洲午夜在线| 亚洲精品之草原avav久久| 91欧美激情另类亚洲| 亚洲香蕉成人av网站在线观看| 日韩在线视频线视频免费网站| 久久久久久久国产| 97国产精品视频人人做人人爱| 久久精品国产一区二区三区| 亚洲另类欧美自拍| 中文字幕一精品亚洲无线一区| 中文字幕亚洲综合久久| 日韩欧美aaa| 欧美怡红院视频一区二区三区| 亚洲网在线观看| 亚洲韩国欧洲国产日产av| 清纯唯美亚洲综合| 亚洲人成在线观| 日韩中文字幕在线免费观看| 亚洲欧美另类人妖| 久久精品夜夜夜夜夜久久| 中文字幕亚洲综合| 日本乱人伦a精品| 91精品久久久久久久久久久久久久| 日韩一区二区三区国产| 日本一区二区在线播放| 欧美成人全部免费| 韩剧1988免费观看全集| 亚洲最大福利网站| 亚洲天堂av女优| 2020久久国产精品| 亚洲人成电影网站色www| 日韩在线视频播放| 国产精品高潮呻吟久久av无限| 久久免费视频观看| 5278欧美一区二区三区| 亚洲a在线观看| 国产精品99免视看9| 欧美日韩一区二区免费在线观看| 在线播放日韩专区| 91超碰caoporn97人人| 国产精品小说在线| 国产中文字幕亚洲| 性色av香蕉一区二区| 最近免费中文字幕视频2019| 日韩美女在线观看一区| 91久久久国产精品| 亚洲欧美一区二区精品久久久| 成人在线中文字幕| 亚洲已满18点击进入在线看片| 亚洲国产精彩中文乱码av在线播放| 91视频九色网站| 日韩av在线直播| 欧美性生交xxxxx久久久| 亚洲天堂免费在线| 日韩一区二区av| 亚洲色图国产精品|