您好,登錄后才能下訂單哦!
網絡爬蟲在信息檢索與處理中有很大的作用,是收集網絡信息的重要工具。
接下來就介紹一下爬蟲的簡單實現。
爬蟲的工作流程如下
爬蟲自指定的URL地址開始下載網絡資源,直到該地址和所有子地址的指定資源都下載完畢為止。http://mmm.qqq23.com
下面開始逐步分析爬蟲的實現。
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,所以實現起來方便不少。
為了提高下載的效率,http://www.qqq100.com我們可以用多個請求并發的方式同時下載多個URL的資源,一種簡單的做法是采用異步請求的方法。
控制并發的數量可以用如下方法實現
1privatevoid DispatchWork()
2{
3if (_stop) //判斷是否中止下載
4 {
5return;
6 }
7for (int i = 0; i < _reqCount; i++)
8 {
9if (!_reqsBusy[i]) //判斷此編號的工作實例是否空閑
10 {
11 RequestResource(i); //讓此工作實例請求資源
12 }
13 }
14 }
由于沒有顯式開新線程,所以用一個工作實例來表示一個邏輯工作線程
1privatebool[] _reqsBusy = null; //每個元素代表一個工作實例是否正在工作
2privateint _reqCount = 4; //工作實例的數量
每次一個工作實例完成工作,相應的_reqsBusy就設為false,并調用DispatchWork,那么DispatchWork就能給空閑的實例分配新任務了。
接下來是發送請求
1privatevoidRequestResource(int index)
2{
3int depth;
4string url = "";
5try
6 {
7lock (_locker)
8 {
9if (_urlsUnload.Count <= 0)
10 {
11 _workingSignals.FinishWorking(index);
12return;
13 }
14 _reqsBusy[index] = true;
15 _workingSignals.StartWorking(index);
16 depth = _urlsUnload.First().Value;
17 url = _urlsUnload.First().Key;
18 _urlsLoaded.Add(url, depth);
19 _urlsUnload.Remove(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); //回調方法的參數
27var result =req.BeginGetResponse(new AsyncCallback(ReceivedResource), rs); //異步請求
28 ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, //注冊超時處理方法
29 TimeoutCallback, rs, _maxTime,true);
30 }
31catch (WebException we)
32 {
33 MessageBox.Show("RequestResource" + we.Message + url + we.Status);
34 }
35 }
第26行的請求的額外信息在異步請求的回調方法作為參數傳入,之后還會提到。
第27行開始異步請求,這里需要傳入一個回調方法作為響應請求時的處理,同時傳入回調方法的參數。
第28行給該異步請求注冊一個超時處理方法TimeoutCallback,最大等待時間是_maxTime,且只處理一次超時,并傳入請求的額外信息作為回調方法的參數。
RequestState的定義是
1class RequestState
2{
3privateconstint BUFFER_SIZE = 131072; //接收數據包的空間大小
4privatebyte[] _data = newbyte[BUFFER_SIZE]; //接收數據包的buffer
5private StringBuilder _sb = new StringBuilder(); //存放所有接收到的字符
6
7public HttpWebRequest Req { get; privateset; } //請求
8publicstring Url { get; privateset; } //請求的URL
9publicint Depth { get; privateset; } //此次請求的相對深度
10publicint Index { get; privateset; } //工作實例的編號
11public StreamResStream { get; set; } //接收數據流
12public StringBuilder Html
13 {
14get
15 {
16return _sb;
17 }
18 }
19
20publicbyte[] Data
21 {
22get
23 {
24return _data;
25 }
26 }
27
28publicint BufferSize
29 {
30get
31 {
32return BUFFER_SIZE;
33 }
34 }
35
36publicRequestState(HttpWebRequest req, string url, int depth, int index)
37 {
38 Req = req;
39 Url = url;
40 Depth = depth;
41 Index = index;
42 }
43 }
TimeoutCallback的定義是
1privatevoidTimeoutCallback(object state, bool timedOut)
2{
3if (timedOut) //判斷是否是超時
4 {
5 RequestState rs = state as RequestState;
6if (rs != null)
7 {
8 rs.Req.Abort(); //撤銷請求
9 }
10 _reqsBusy[rs.Index] = false; //重置工作狀態
11 DispatchWork(); //分配新任務
12 }
13 }
接下來就是要處理請求的響應了
1privatevoidReceivedResource(IAsyncResult ar)
2{
3 RequestState rs = (RequestState)ar.AsyncState;//得到請求時傳入的參數
4 HttpWebRequest req = rs.Req;
5string url = rs.Url;
6try
7 {
8 HttpWebResponse res =(HttpWebResponse)req.EndGetResponse(ar); //獲取響應
9if (_stop) //判斷是否中止下載
10 {
11 res.Close();
12 req.Abort();
13return;
14 }
15if (res != null &&res.StatusCode == HttpStatusCode.OK) //判斷是否成功獲取響應
16 {
17 Stream resStream = res.GetResponseStream(); //得到資源流
18 rs.ResStream = resStream;
19var result =resStream.BeginRead(rs.Data, 0, rs.BufferSize, //異步請求讀取數據
20new AsyncCallback(ReceivedData), rs);
21 }
22else//響應失敗
23 {
24 res.Close();
25 rs.Req.Abort();
26 _reqsBusy[rs.Index] = false; //重置工作狀態
27 DispatchWork(); //分配新任務
28 }
29 }
30catch (WebException we)
31 {
32 MessageBox.Show("ReceivedResource" + we.Message + url + we.Status);
33 }
34 }
第19行這里采用了異步的方法來讀數據流是因為我們之前采用了異步的方式請求,不然的話不能夠正常的接收數據。
該異步讀取的方式是按包來讀取的,所以一旦接收到一個包就會調用傳入的回調方法ReceivedData,然后在該方法中處理收到的數據。
該方法同時傳入了接收數據的空間rs.Data和空間的大小rs.BufferSize。
接下來是接收數據和處理
1privatevoidReceivedData(IAsyncResult ar)
2{
3 RequestState rs =(RequestState)ar.AsyncState; //獲取參數
4 HttpWebRequest req = rs.Req;
5 Stream resStream = rs.ResStream;
6string url = rs.Url;
7int depth = rs.Depth;
8string html = null;
9int index = rs.Index;
10int read = 0;
11
12try
13 {
14 read = resStream.EndRead(ar); //獲得數據讀取結果
15if (_stop)//判斷是否中止下載
16 {
17 rs.ResStream.Close();
18 req.Abort();
19return;
20 }
21if (read > 0)
22 {
23 MemoryStream ms = new MemoryStream(rs.Data, 0, read); //利用獲得的數據創建內存流
24 StreamReader reader = new StreamReader(ms, _encoding);
25string str = reader.ReadToEnd(); //讀取所有字符
26 rs.Html.Append(str); // 添加到之前的末尾
27var result =resStream.BeginRead(rs.Data, 0, rs.BufferSize, //再次異步請求讀取數據
28new AsyncCallback(ReceivedData), rs);
29return;
30 }
31 html = rs.Html.ToString();
32 SaveContents(html, url); //保存到本地
33string[] links = GetLinks(html); //獲取頁面中的鏈接
34 AddUrls(links, depth + 1); //過濾鏈接并添加到未下載集合中
35
36 _reqsBusy[index] = false; //重置工作狀態
37 DispatchWork(); //分配新任務
38 }
39catch (WebException we)
40 {
41 MessageBox.Show("ReceivedDataWeb " + we.Message + url + we.Status);
42 }
43 }
第14行獲得了讀取的數據大小read,如果read>0說明數據可能還沒有讀完,所以在27行繼續請求讀下一個數據包;
如果read<=0說明所有數據已經接收完畢,這時rs.Html中存放了完整的HTML數據,就可以進行下一步的處理了。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。