上一篇《用C#實現網絡爬蟲(一)》我們實現了網絡通信的部分,接下來繼續討論爬蟲的實現
3. 保存頁面文件
這一部分可簡單可復雜,如果只要簡單地把HTML代碼全部保存下來的話,直接存文件就行了。
1 private void SaveContents(string html, string url) 2 { 3 if (string.IsNullOrEmpty(html)) //判斷html字符串是否有效 4 { 5 return; 6 } 7 string path = string.Format("{0}\\{1}.txt", _path, _index++); //生成文件名 8 9 try 10 { 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, path, url); //調用保存文件事件 24 } 25 }
第23行這里又出現了一個事件,是保存文件之后觸發的,客戶程序可以之前進行注冊。
1 public delegate void ContentsSavedHandler(string path, string url); 2 3 /// <summary> 4 /// 文件被保存到本地后觸發 5 /// </summary> 6 public event ContentsSavedHandler ContentsSaved = null;
4. 提取頁面鏈接
提取鏈接用正則表達式就能搞定了,不懂的可以上網搜。
下面的字符串就能匹配到頁面中的鏈接
http://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?
詳細見代碼
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); //獲得匹配結果 6 string[] links = new string[m.Count]; 7 8 for (int i = 0; i < m.Count; i++) 9 { 10 links[i] = m[i].ToString(); //提取出結果 11 } 12 return links; 13 }
5. 鏈接的過濾
不是所有的鏈接我們都需要下載,所以通過過濾,去掉我們不需要的鏈接
這些鏈接一般有:
- 已經下載的鏈接
- 深度過大的鏈接
- 其他的不需要的資源,如圖片、CSS等
1 //判斷鏈接是否已經下載或者已經處於未下載集合中 2 private bool UrlExists(string url) 3 { 4 bool result = _urlsUnload.ContainsKey(url); 5 result |= _urlsLoaded.ContainsKey(url); 6 return result; 7 } 8 9 private bool UrlAvailable(string url) 10 { 11 if (UrlExists(url)) 12 { 13 return false; //已經存在 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 void AddUrls(string[] urls, int depth) 25 { 26 if (depth >= _maxDepth) 27 { 28 return; //深度過大 29 } 30 foreach (string url in urls) 31 { 32 string cleanUrl = url.Trim(); //去掉前后空格 33 cleanUrl = cleanUrl.TrimEnd('/'); //統一去掉最后面的'/' 34 if (UrlAvailable(cleanUrl)) 35 { 36 if (cleanUrl.Contains(_baseUrl)) 37 { 38 _urlsUnload.Add(cleanUrl, depth); //是內鏈,直接加入未下載集合 39 } 40 else 41 { 42 // 外鏈處理 43 } 44 } 45 } 46 }
第34行的_baseUrl是爬取的基地址,如http://news.sina.com.cn/,將會保存為news.sina.com.cn,當一個URL包含此字符串時,說明是該基地址下的鏈接;否則為外鏈。
_baseUrl的處理如下,_rootUrl是第一個要下載的URL
1 /// <summary> 2 /// 下載根Url 3 /// </summary> 4 public string RootUrl 5 { 6 get 7 { 8 return _rootUrl; 9 } 10 set 11 { 12 if (!value.Contains("http://")) 13 { 14 _rootUrl = "http://" + value; 15 } 16 else 17 { 18 _rootUrl = value; 19 } 20 _baseUrl = _rootUrl.Replace("www.", ""); //全站的話去掉www 21 _baseUrl = _baseUrl.Replace("http://", ""); //去掉協議名 22 _baseUrl = _baseUrl.TrimEnd('/'); //去掉末尾的'/' 23 } 24 }
至此,基本的爬蟲功能實現就介紹完了。
最后附上源代碼和DEMO程序,爬蟲的源代碼在Spider.cs中,DEMO是一個WPF的程序,Test里是一個控制台的單線程版版本。
在下一期中,我們將介紹一些提取出網頁中有效信息的方法,敬請期待。。。