用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲_第1頁(yè)
用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲_第2頁(yè)
用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲_第3頁(yè)
用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲_第4頁(yè)
用C#實(shí)現(xiàn)網(wǎng)絡(luò)爬蟲_第5頁(yè)
已閱讀5頁(yè),還剩4頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、網(wǎng)絡(luò)爬蟲在信息檢索與處理中有很大的作用,是收集網(wǎng)絡(luò)信息的重要工具。接下來(lái)就介紹一下爬蟲的簡(jiǎn)單實(shí)現(xiàn)。爬蟲的工作流程如下爬蟲自指定的URL地址開始下載網(wǎng)絡(luò)資源,直到該地址和所有子地址的指定資源都下載完畢為止。下面開始逐步分析爬蟲的實(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。具體類型是Dictionary<string, int> 其中string是Url字符串,int

2、是該Url相對(duì)于基URL的深度。每次開始時(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)的類HttpWebRequest和HttpWebResponse,所以實(shí)現(xiàn)起來(lái)方便不少。為了提高下載的效率,我們可以用多個(gè)請(qǐng)求并發(fā)的方式同時(shí)下載多個(gè)URL的資源,一種簡(jiǎn)單的做法是采用異步請(qǐng)求的方法??刂撇l(fā)的數(shù)量可以用如下方法實(shí)現(xiàn) 1 private void DispatchWork() 2 3 if (_stop) /判斷是否中止下載 4 5 retu

3、rn; 6 7 for (int i = 0; i < _reqCount; i+) 8 9 if (!_reqsBusyi) /判斷此編號(hào)的工作實(shí)例是否空閑10 11 RequestResource(i); /讓此工作實(shí)例請(qǐng)求資源12 13 14 由于沒(méi)有顯式開新線程,所以用一個(gè)工作實(shí)例來(lái)表示一個(gè)邏輯工作線程1 private bool _reqsBusy = null; /每個(gè)元素代表一個(gè)工作實(shí)例是否正在工作2 private int _reqCount = 4; /工作實(shí)例的數(shù)量 每次一個(gè)工作實(shí)例完成工作,相應(yīng)的_reqsBusy就設(shè)為false,并調(diào)用DispatchWork,那

4、么DispatchWork就能給空閑的實(shí)例分配新任務(wù)了。 接下來(lái)是發(fā)送請(qǐng)求 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) /判斷是否還有未下載的URL10 11 _workingSignals.FinishWorking(index); /設(shè)置工作實(shí)例的狀態(tài)為Finished12 return;13 14 _reqsBusyindex = true;15

5、 _workingSignals.StartWorking(index); /設(shè)置工作狀態(tài)為Working16 depth = _urlsUnload.First().Value; /取出第一個(gè)未下載的URL17 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.M

6、ethod = _method; /請(qǐng)求方法24 req.Accept = _accept; /接受的內(nèi)容25 req.UserAgent = _userAgent; /用戶代理26 RequestState rs = new RequestState(req, url, depth, index); /回調(diào)方法的參數(shù)27 var result = req.BeginGetResponse(new AsyncCallback(ReceivedResource), rs); /異步請(qǐng)求28 ThreadPool.RegisterWaitForSingleObject(result.AsyncWa

7、itHandle, /注冊(cè)超時(shí)處理方法29 TimeoutCallback, rs, _maxTime, true);30 31 catch (WebException we)32 33 MessageBox.Show("RequestResource " + we.Message + url + we.Status);34 35 第7行為了保證多個(gè)任務(wù)并發(fā)時(shí)的同步,加上了互斥鎖。_locker是一個(gè)Object類型的成員變量。第9行判斷未下載集合是否為空,如果為空就把當(dāng)前工作實(shí)例狀態(tài)設(shè)為Finished;如果非空則設(shè)為Working并取出一個(gè)URL開始下載。當(dāng)所有工作實(shí)例

8、都為Finished的時(shí)候,說(shuō)明下載已經(jīng)完成。由于每次下載完一個(gè)URL后都調(diào)用DispatchWork,所以可能激活其他的Finished工作實(shí)例重新開始工作。第26行的請(qǐng)求的額外信息在異步請(qǐng)求的回調(diào)方法作為參數(shù)傳入,之后還會(huì)提到。第27行開始異步請(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的定義是 1 class RequestState 2 3 private const

9、int BUFFER_SIZE = 131072; /接收數(shù)據(jù)包的空間大小 4 private byte _data = new byteBUFFER_SIZE; /接收數(shù)據(jù)包的buffer 5 private StringBuilder _sb = new StringBuilder(); /存放所有接收到的字符 6 7 public HttpWebRequest Req get; private set; /請(qǐng)求 8 public string Url get; private set; /請(qǐng)求的URL 9 public int Depth get; private set; /此次請(qǐng)求的

10、相對(duì)深度10 public int Index get; private set; /工作實(shí)例的編號(hào)11 public Stream ResStream get; set; /接收數(shù)據(jù)流12 public StringBuilder Html13 14 get15 16 return _sb;17 18 19 20 public byte Data21 22 get23 24 return _data;25 26 27 28 public int BufferSize29 30 get31 32 return BUFFER_SIZE;33 34 35 36 public RequestStat

11、e(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) /判斷是否是超時(shí) 4 5 RequestState rs = state as RequestState; 6 if (rs != null) 7 8 rs

12、.Req.Abort(); /撤銷請(qǐng)求 9 10 _reqsBusyrs.Index = false; /重置工作狀態(tài)11 DispatchWork(); /分配新任務(wù)12 13 接下來(lái)就是要處理請(qǐng)求的響應(yīng)了 1 private void ReceivedResource(IAsyncResult ar) 2 3 RequestState rs = (RequestState)ar.AsyncState; /得到請(qǐng)求時(shí)傳入的參數(shù) 4 HttpWebRequest req = rs.Req; 5 string url = rs.Url; 6 try 7 8 HttpWebResponse res

13、 = (HttpWebResponse)req.EndGetResponse(ar); /獲取響應(yīng) 9 if (_stop) /判斷是否中止下載10 11 res.Close();12 req.Abort();13 return;14 15 if (res != null && res.StatusCode = HttpStatusCode.OK) /判斷是否成功獲取響應(yīng)16 17 Stream resStream = res.GetResponseStream(); /得到資源流18 rs.ResStream = resStream;19 var result = resSt

14、ream.BeginRead(rs.Data, 0, rs.BufferSize, /異步請(qǐng)求讀取數(shù)據(jù)20 new AsyncCallback(ReceivedData), rs);21 22 else /響應(yīng)失敗23 24 res.Close();25 rs.Req.Abort();26 _reqsBusyrs.Index = false; /重置工作狀態(tài)27 DispatchWork(); /分配新任務(wù)28 29 30 catch (WebException we)31 32 MessageBox.Show("ReceivedResource " + we.Messag

15、e + url + we.Status);33 34 第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ù)和處理 1 private void ReceivedData(IAsyncResult ar) 2 3 RequestState rs = (RequestState)ar.AsyncState; /獲取參數(shù)

16、 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 try13 14 read = resStream.EndRead(ar); /獲得數(shù)據(jù)讀取結(jié)果15 if (_stop)/判斷是否中止下載16 17 rs.ResStream.Close();18 req.Abort();19 retu

17、rn;20 21 if (read > 0)22 23 MemoryStream ms = new MemoryStream(rs.Data, 0, read); /利用獲得的數(shù)據(jù)創(chuàng)建內(nèi)存流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, /再次異步

18、請(qǐng)求讀取數(shù)據(jù)28 new AsyncCallback(ReceivedData), rs);29 return;30 31 html = rs.Html.ToString();32 SaveContents(html, url); /保存到本地33 string links = GetLinks(html); /獲取頁(yè)面中的鏈接34 AddUrls(links, depth + 1); /過(guò)濾鏈接并添加到未下載集合中35 36 _reqsBusyindex = false; /重置工作狀態(tài)37 DispatchWork(); /分配新任務(wù)38 39 catch (WebException we

19、)40 41 MessageBox.Show("ReceivedData Web " + we.Message + url + we.Status);42 43 第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ù)完成的處理 1 private void StartDown

20、load() 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() /檢查是否所有工作實(shí)例都為Finished10 11 _checkTimer.Dispose(); /停止定時(shí)器12 _checkTimer = null;13 if (DownloadFinish != null &&

21、 _ui != null) /判斷是否注冊(cè)了完成事件14 15 _ui.Dispatcher.Invoke(DownloadFinish, _index); /調(diào)用事件16 17 18 第3行創(chuàng)建了一個(gè)定時(shí)器,每過(guò)300ms調(diào)用一次CheckFinish來(lái)判斷是否完成任務(wù)。第15行提供了一個(gè)完成任務(wù)時(shí)的事件,可以給客戶程序注冊(cè)。_index里存放了當(dāng)前下載URL的個(gè)數(shù)。該事件的定義是1 public delegate void DownloadFinishHandler(int count);2 3 / <summary>4 / 全部鏈接下載分析完畢后觸發(fā)5 / </summ

22、ary>6 public event DownloadFinishHandler DownloadFinish = null;3. 保存頁(yè)面文件這一部分可簡(jiǎn)單可復(fù)雜,如果只要簡(jiǎn)單地把HTML代碼全部保存下來(lái)的話,直接存文件就行了。 1 private void SaveContents(string html, string url) 2 3 if (string.IsNullOrEmpty(html) /判斷html字符串是否有效 4 5 return; 6 7 string path = string.Format("01.txt", _path, _index+

23、); /生成文件名 8 9 try10 11 using (StreamWriter fs = new StreamWriter(path)12 13 fs.Write(html); /寫文件14 15 16 catch (IOException ioe)17 18 MessageBox.Show("SaveContents IO" + ioe.Message + " path=" + path);19 20 21 if (ContentsSaved != null)22 23 _ui.Dispatcher.Invoke(ContentsSaved, p

24、ath, url); /調(diào)用保存文件事件24 25 第23行這里又出現(xiàn)了一個(gè)事件,是保存文件之后觸發(fā)的,客戶程序可以之前進(jìn)行注冊(cè)。1 public delegate void ContentsSavedHandler(string path, string url);2 3 / <summary>4 / 文件被保存到本地后觸發(fā)5 / </summary>6 public event ContentsSavedHandler ContentsSaved = null; 4. 提取頁(yè)面鏈接提取鏈接用正則表達(dá)式就能搞定了,不懂的可以上網(wǎng)搜。下面的字符串就能匹配到頁(yè)面中的鏈接h

25、ttp:/(w-+.)+w-+(/w- ./?%&=*)?詳細(xì)見代碼 1 private string GetLinks(string html) 2 3 const string pattern = "http:/(w-+.)+w-+(/w- ./?%&=*)?" 4 Regex r = new Regex(pattern, RegexOptions.IgnoreCase); /新建正則模式 5 MatchCollection m = r.Matches(html); /獲得匹配結(jié)果 6 string links = new stringm.Count;

26、7 8 for (int i = 0; i < m.Count; i+) 9 10 linksi = mi.ToString(); /提取出結(jié)果11 12 return links;13 5. 鏈接的過(guò)濾不是所有的鏈接我們都需要下載,所以通過(guò)過(guò)濾,去掉我們不需要的鏈接這些鏈接一般有:已經(jīng)下載的鏈接深度過(guò)大的鏈接其他的不需要的資源,如圖片、CSS等 1 /判斷鏈接是否已經(jīng)下載或者已經(jīng)處于未下載集合中 2 private bool UrlExists(string url) 3 4 bool result = _urlsUnload.ContainsKey(url); 5 result |=

27、 _urlsLoaded.ContainsKey(url); 6 return result; 7 8 9 private bool UrlAvailable(string url)10 11 if (UrlExists(url)12 13 return false; /已經(jīng)存在14 15 if (url.Contains(".jpg") | url.Contains(".gif")16 | url.Contains(".png") | url.Contains(".css")17 | url.Contains(".js")18 19 return false; /去掉一些圖片之類的資源20 21 return true;22 23 24 private

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論