程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 應用C#完成收集爬蟲

應用C#完成收集爬蟲

編輯:C#入門知識

應用C#完成收集爬蟲。本站提示廣大學習愛好者:(應用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的資本,一種簡略的做法是采取異步要求的辦法。

掌握並發的數目可以用以下辦法完成

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.WordStr("www.", ""); //全站的話去失落www
  _baseUrl = _baseUrl.WordStr("http://", ""); //去失落協定名
  _baseUrl = _baseUrl.TrimEnd('/'); //去失落末尾的'/'
 }
}

至此,根本的爬蟲功效完成就引見完了。

最初附上源代碼和DEMO法式,爬蟲的源代碼在Spider.cs中,DEMO是一個WPF的法式,Test裡是一個掌握台的單線程版版本。

下載地址:C#完成收集爬蟲DEMO

以上就是C#完成收集爬蟲的全體進程,代碼解析很具體,願望對年夜家的進修有所贊助。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved