上一篇我们实现了网络通信的部分,接下来继续讨论爬虫的实现
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 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, path, url); //调用保存文件事件24 }25 }
第23行这里又出现了一个事件,是保存文件之后触发的,客户程序可以之前进行注册。
1 public delegate void ContentsSavedHandler(string path, string url);2 3 ///4 /// 文件被保存到本地后触发5 /// 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 }
不是所有的链接我们都需要下载,所以通过过滤,去掉我们不需要的链接
这些链接一般有:
- 已经下载的链接
- 深度过大的链接
- 其他的不需要的资源,如图片、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 else41 {42 // 外链处理43 }44 }45 }46 }
第34行的_baseUrl是爬取的基地址,如http://news.sina.com.cn/,将会保存为news.sina.com.cn,当一个URL包含此字符串时,说明是该基地址下的链接;否则为外链。
_baseUrl的处理如下,_rootUrl是第一个要下载的URL
1 ///2 /// 下载根Url 3 /// 4 public string RootUrl 5 { 6 get 7 { 8 return _rootUrl; 9 }10 set11 {12 if (!value.Contains("http://"))13 {14 _rootUrl = "http://" + value;15 }16 else17 {18 _rootUrl = value;19 }20 _baseUrl = _rootUrl.Replace("www.", ""); //全站的话去掉www21 _baseUrl = _baseUrl.Replace("http://", ""); //去掉协议名22 _baseUrl = _baseUrl.TrimEnd('/'); //去掉末尾的'/'23 }24 }
最后附上源代码和DEMO程序,爬虫的源代码在Spider.cs中,DEMO是一个WPF的程序,Test里是一个控制台的单线程版版本。
在下一期中,我们将介绍一些提取出网页中有效信息的方法,敬请期待。。。