欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(chóng)

 更新時(shí)間:2016年03月24日 17:24:19   投稿:lijiao  
這篇文章主要介紹了利用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(chóng),完整的介紹了C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(chóng)詳細(xì)過(guò)程,感興趣的小伙伴們可以參考一下

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

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

爬蟲(chóng)的工作流程如下

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

下面開(kāi)始逐步分析爬蟲(chóng)的實(shí)現(xiàn)。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

第27行開(kāi)始異步請(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的定義是

class RequestState
{
 private const int BUFFER_SIZE = 131072; //接收數(shù)據(jù)包的空間大小
 private byte[] _data = new byte[BUFFER_SIZE]; //接收數(shù)據(jù)包的buffer
 private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字符

 public HttpWebRequest Req { get; private set; } //請(qǐng)求
 public string Url { get; private set; } //請(qǐng)求的URL
 public int Depth { get; private set; } //此次請(qǐng)求的相對(duì)深度
 public int Index { get; private set; } //工作實(shí)例的編號(hào)
 public Stream ResStream { get; set; } //接收數(shù)據(jù)流
 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) //判斷是否是超時(shí)
 {
  RequestState rs = state as RequestState;
  if (rs != null)
  {
   rs.Req.Abort(); //撤銷(xiāo)請(qǐng)求
  }
  _reqsBusy[rs.Index] = false; //重置工作狀態(tài)
  DispatchWork(); //分配新任務(wù)
 }
}

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

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

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

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

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

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

private void ReceivedData(IAsyncResult ar)
{
 RequestState rs = (RequestState)ar.AsyncState; //獲取參數(shù)
 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); //獲得數(shù)據(jù)讀取結(jié)果
  if (_stop)//判斷是否中止下載
  {
   rs.ResStream.Close();
   req.Abort();
   return;
  }
  if (read > 0)
  {
   MemoryStream ms = new MemoryStream(rs.Data, 0, read); //利用獲得的數(shù)據(jù)創(chuàng)建內(nèi)存流
   StreamReader reader = new StreamReader(ms, _encoding);
   string str = reader.ReadToEnd(); //讀取所有字符
   rs.Html.Append(str); // 添加到之前的末尾
   var result = resStream.BeginRead(rs.Data, 0, rs.BufferSize, //再次異步請(qǐng)求讀取數(shù)據(jù)
    new AsyncCallback(ReceivedData), rs);
   return;
  }
  html = rs.Html.ToString();
  SaveContents(html, url); //保存到本地
  string[] links = GetLinks(html); //獲取頁(yè)面中的鏈接
  AddUrls(links, depth + 1); //過(guò)濾鏈接并添加到未下載集合中

  _reqsBusy[index] = false; //重置工作狀態(tài)
  DispatchWork(); //分配新任務(wù)
 }
 catch (WebException we)
 {
  MessageBox.Show("ReceivedData Web " + we.Message + url + we.Status);
 }
}

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

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

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

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

private void StartDownload()
{
 _checkTimer = new Timer(new TimerCallback(CheckFinish), null, 0, 300);
 DispatchWork();
}

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

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

該事件的定義是

public delegate void DownloadFinishHandler(int count);

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

3. 保存頁(yè)面文件

這一部分可簡(jiǎn)單可復(fù)雜,如果只要簡(jiǎn)單地把HTML代碼全部保存下來(lái)的話,直接存文件就行了。

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); //寫(xiě)文件
  }
 }
 catch (IOException ioe)
 {
  MessageBox.Show("SaveContents IO" + ioe.Message + " path=" + path);
 }

 if (ContentsSaved != null)
 {
  _ui.Dispatcher.Invoke(ContentsSaved, path, url); //調(diào)用保存文件事件
 }
}

第23行這里又出現(xiàn)了一個(gè)事件,是保存文件之后觸發(fā)的,客戶程序可以之前進(jìn)行注冊(cè)。

public delegate void ContentsSavedHandler(string path, string url);

/// <summary>
/// 文件被保存到本地后觸發(fā)
/// </summary>
public event ContentsSavedHandler ContentsSaved = null;

 4. 提取頁(yè)面鏈接

提取鏈接用正則表達(dá)式就能搞定了,不懂的可以上網(wǎng)搜。

下面的字符串就能匹配到頁(yè)面中的鏈接

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

詳細(xì)見(jiàn)代碼

private string[] GetLinks(string html)
{
 const string pattern = @"http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?";
 Regex r = new Regex(pattern, RegexOptions.IgnoreCase); //新建正則模式
 MatchCollection m = r.Matches(html); //獲得匹配結(jié)果
 string[] links = new string[m.Count]; 

 for (int i = 0; i < m.Count; i++)
 {
  links[i] = m[i].ToString(); //提取出結(jié)果
 }
 return links;
}

5. 鏈接的過(guò)濾

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

這些鏈接一般有:

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

//判斷鏈接是否已經(jīng)下載或者已經(jīng)處于未下載集合中
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; //已經(jīng)存在
 }
 if (url.Contains(".jpg") || url.Contains(".gif")
  || url.Contains(".png") || url.Contains(".css")
  || url.Contains(".js"))
 {
  return false; //去掉一些圖片之類(lèi)的資源
 }
 return true;
}

private void AddUrls(string[] urls, int depth)
{
 if (depth >= _maxDepth)
 {
  return; //深度過(guò)大
 }
 foreach (string url in urls)
 {
  string cleanUrl = url.Trim(); //去掉前后空格
  cleanUrl = cleanUrl.TrimEnd('/'); //統(tǒng)一去掉最后面的'/'
  if (UrlAvailable(cleanUrl))
  {
   if (cleanUrl.Contains(_baseUrl))
   {
    _urlsUnload.Add(cleanUrl, depth); //是內(nèi)鏈,直接加入未下載集合
   }
   else
   {
    // 外鏈處理
   }
  }
 }
}

 第34行的_baseUrl是爬取的基地址,如http://news.sina.com.cn/,將會(huì)保存為news.sina.com.cn,當(dāng)一個(gè)URL包含此字符串時(shí),說(shuō)明是該基地址下的鏈接;否則為外鏈。 

_baseUrl的處理如下,_rootUrl是第一個(gè)要下載的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://", ""); //去掉協(xié)議名
  _baseUrl = _baseUrl.TrimEnd('/'); //去掉末尾的'/'
 }
}

至此,基本的爬蟲(chóng)功能實(shí)現(xiàn)就介紹完了。

最后附上源代碼和DEMO程序,爬蟲(chóng)的源代碼在Spider.cs中,DEMO是一個(gè)WPF的程序,Test里是一個(gè)控制臺(tái)的單線程版版本。

下載地址:C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(chóng)DEMO

以上就是C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲(chóng)的全部過(guò)程,代碼解析很詳細(xì),希望對(duì)大家的學(xué)習(xí)有所幫助。

相關(guān)文章

  • C#設(shè)置子窗體在主窗體中居中顯示解決方案

    C#設(shè)置子窗體在主窗體中居中顯示解決方案

    接下來(lái)將介紹C#如何設(shè)置子窗體在主窗體中居中顯示,本文提供詳細(xì)的操作步驟,需要的朋友可以參考下
    2012-12-12
  • C#加密在實(shí)際中的應(yīng)用

    C#加密在實(shí)際中的應(yīng)用

    在系統(tǒng)的管理員有著實(shí)際的應(yīng)用,對(duì)于一個(gè)數(shù)據(jù)庫(kù)管理系統(tǒng)來(lái)說(shuō),數(shù)據(jù)庫(kù)安全還是挺重要的,所以在存入到數(shù)據(jù)庫(kù)的密碼通常都是加密的
    2012-11-11
  • VS2019使用快捷鍵將代碼對(duì)齊的方法

    VS2019使用快捷鍵將代碼對(duì)齊的方法

    這篇文章主要介紹了VS2019使用快捷鍵將代碼對(duì)齊的相關(guān)資料,非常不錯(cuò)對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-04-04
  • C#實(shí)現(xiàn)winform版飛行棋

    C#實(shí)現(xiàn)winform版飛行棋

    這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)winform版飛行,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • c#添加Newtonsoft.Json包的操作

    c#添加Newtonsoft.Json包的操作

    這篇文章主要介紹了c#添加Newtonsoft.Json包的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • C#中timer定時(shí)器用法實(shí)例

    C#中timer定時(shí)器用法實(shí)例

    這篇文章主要介紹了C#中timer定時(shí)器用法,實(shí)例分析了timer定時(shí)器實(shí)現(xiàn)定時(shí)觸發(fā)事件的技巧,需要的朋友可以參考下
    2015-04-04
  • C#?代碼大小寫(xiě)規(guī)范說(shuō)明

    C#?代碼大小寫(xiě)規(guī)范說(shuō)明

    這篇文章主要介紹了C#?代碼大小寫(xiě)規(guī)范說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • C#內(nèi)插字符串的簡(jiǎn)單使用

    C#內(nèi)插字符串的簡(jiǎn)單使用

    這篇文章主要為大家詳細(xì)介紹了C#內(nèi)插字符串的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • C#實(shí)現(xiàn)漂亮的數(shù)字時(shí)鐘效果

    C#實(shí)現(xiàn)漂亮的數(shù)字時(shí)鐘效果

    這篇文章主要介紹了C#實(shí)現(xiàn)漂亮的數(shù)字時(shí)鐘效果,涉及時(shí)間函數(shù)的應(yīng)用及繪圖的方法,需要的朋友可以參考下
    2014-10-10
  • C#使用Socket進(jìn)行簡(jiǎn)單的通訊的示例代碼

    C#使用Socket進(jìn)行簡(jiǎn)單的通訊的示例代碼

    Socket 類(lèi)是基于與 Linux、macOS 或 Windows 的本機(jī)互操作性提供的托管代碼版本的套接字服務(wù),提供了一系列的接口來(lái)支持應(yīng)用層的調(diào)用,下面我們就來(lái)學(xué)習(xí)一下如何使用Socket進(jìn)行簡(jiǎn)單的通訊,需要的可以參考下
    2023-12-12

最新評(píng)論