GJM:用C#實現網路爬蟲(一) [轉載]

来源:http://www.cnblogs.com/GJM6/archive/2016/11/16/6068663.html
-Advertisement-
Play Games

網路爬蟲在信息檢索與處理中有很大的作用,是收集網路信息的重要工具。 接下來就介紹一下爬蟲的簡單實現。 爬蟲的工作流程如下 爬蟲自指定的URL地址開始下載網路資源,直到該地址和所有子地址的指定資源都下載完畢為止。 下麵開始逐步分析爬蟲的實現。 1. 待下載集合與已下載集合 為了保存需要下載的URL,同 ...


 

 

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

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

爬蟲的工作流程如下

爬蟲自指定的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      try
 6      {
 7          lock (_locker)
 8          {
 9              if (_urlsUnload.Count <= 0) //判斷是否還有未下載的URL
10              {
11                  _workingSignals.FinishWorking(index); //設置工作實例的狀態為Finished
12                  return;
13              }
14              _reqsBusy[index] = true;
15              _workingSignals.StartWorking(index); //設置工作狀態為Working
16              depth = _urlsUnload.First().Value; //取出第一個未下載的URL
17              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; //請求方法
24          req.Accept = _accept; //接受的內容
25          req.UserAgent = _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 RequestState
 2 {
 3     private const int BUFFER_SIZE = 131072; //接收數據包的空間大小
 4     private byte[] _data = new byte[BUFFER_SIZE]; //接收數據包的buffer
 5     private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字元
 6 
 7     public HttpWebRequest Req { get; private set; } //請求
 8     public string Url { get; private set; } //請求的URL
 9     public int Depth { get; private set; } //此次請求的相對深度
10     public int Index { get; private set; } //工作實例的編號
11     public Stream ResStream { get; set; } //接收數據流
12     public StringBuilder Html
13     {
14         get
15         {
16             return _sb;
17         }
18     }
19 
20     public byte[] Data
21     {
22         get
23         {
24             return _data;
25         }
26     }
27 
28     public int BufferSize
29     {
30         get
31         {
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) //判斷是否是超時
 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     try
 7     {
 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,然後在該方法中處理收到的數據。

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

 

接下來是接收數據和處理

複製代碼
 1 private void ReceivedData(IAsyncResult ar)
 2 {
 3     RequestState rs = (RequestState)ar.AsyncState; //獲取參數
 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     try
13     {
14         read = resStream.EndRead(ar); //獲得數據讀取結果
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); //利用獲得的數據創建記憶體流
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, //再次非同步請求讀取數據
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; //重置工作狀態
37         DispatchWork(); //分配新任務
38     }
39     catch (WebException we)
40     {
41         MessageBox.Show("ReceivedData Web " + we.Message + url + we.Status);
42     }
43 } 
複製代碼

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

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

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

 

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

複製代碼
 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()) //檢查是否所有工作實例都為Finished
10     {
11         _checkTimer.Dispose(); //停止定時器
12         _checkTimer = null;
13         if (DownloadFinish != null && _ui != null) //判斷是否註冊了完成事件
14         {
15             _ui.Dispatcher.Invoke(DownloadFinish, _index); //調用事件
16         }
17     }
18 }
複製代碼

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

該事件的定義是

複製代碼
1 public delegate void DownloadFinishHandler(int count);
2 
3 /// <summary>
4 /// 全部鏈接下載分析完畢後觸發
5 /// </summary>
6 public event DownloadFinishHandler DownloadFinish = null;
複製代碼    GJM :於 2016-11-16 轉載自 http://www.cnblogs.com/Jiajun/archive/2012/06/17/2552458.html   如影響作者版權問題 請聯繫我 [email protected]  
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ARM彙編程式結構 一個ARM程式可以被劃分為多個代碼段和數據段,在彙編的時候這些段會被形成一個可執行文件 子程式調用 ARM彙編中,子程式的調用一般通過 指令實現,在程式中,執行 即可完成子程式的調用。該指令在執行時完成如下操作: 1. 將子程式的返回地址保存在LR 2. 將PC指向子程式的入口 ...
  • GNU平臺無關 符號定義偽指令 ,`.local .set .equ` .global 使得符號對連接器可見,變為對整個工程可用的全局變數 .local 表示符號對外部不可見,只對本文件可見 .set 給一個全局變數或局部變數賦值,和 的功能一樣 .equ 和 一樣,只是格式不同 數據定義偽指令 , ...
  • 最近悟出來一個道理,在這兒分享給大家:學歷代表你的過去,能力代表你的現在,學習代表你的將來。 十年河東十年河西,莫欺少年窮 學無止境,精益求精 最近在做自學MVC,遇到的問題很多,索性一點點總結下。 本篇旨在寫一篇上傳文件的博客,上傳文件中以上傳圖片最多,所以本篇以上傳圖片為例進行說明: 在進行講解 ...
  • 在UWP開發中,我們能使用的到方向有三種: OrientationSensor下的四元數;Compass羅盤的HeadingMagneticNorth;以及SimpleOrientationSensor。 先說下SimpleOrientationSensor,這個是用在比較簡單的情況下,它只能讀取6 ...
  • Nop里自帶的為名稱、姓氏,FirstName、LastName。 這些欄位並不存在與Custom表中,看過代碼的應該都知道,在GenericAttribute中以縱表的形式存儲。 \Libraries\Nop.Core\Domain\Customers\SystemCustomerAttribut ...
  • 有用戶反映,Tausus.MVC 能寫WebAPI麽?能!教程呢?嗯,木有-_-!好吧,剛好2.0出來,就帶上WEBAPI教程了! ...
  • 本節內容: 顯示信息 確認 Message API給用戶顯示一個信息,或從用戶那裡獲取一個確認信息。 Message API預設使用sweetalert實現,為使sweetalert正常工作,你應該包含它的css和javascript文件,然後把abp.sweet-alert.js適配器包含到你的頁 ...
  • 首先創建一個C# 控制台應用程式, 直接伺服器端代碼丟進去,然後再到Unity 裡面建立一個工程,把客戶端代碼掛到相機上,運行服務端,再運行客戶端。 高手勿噴!~! 完全源碼已經奉上,大家開始研究吧!! 嘎嘎嘎! 服務端代碼:Program.cs using System; using System ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...