虽然在博客园也有两年的时间了,但基本没写过什么博客,昨天心血来潮,写了个专门针对某个网站的站外搜索工具,今天想写点什么和大家分享下。
由于使用Lucene搭建搜索引擎已经有大篇的文献可参考,所以在此不再详述。
主要实现功能:
(1)实现简单网络爬虫,多线程从互联网下载网页,防死循环
(2)使用Lucene实现本地搜索,对下载的网页进行分析,创建索引
前言
虽然在博客园也有两年的时间了,但基本没写过什么博客,昨天心血来潮,写了个专门针对某个网站的站外搜索工具,今天想写点什么和大家分享下。
由于使用Lucene搭建搜索引擎已经有大篇的文献可参考,所以在此不再详述。
主要实现功能
(1)实现简单网络爬虫,多线程从互联网下载网页,防死循环
(2)使用Lucene实现本地搜索,对下载的网页进行分析,创建索引
爬虫原理
本文实现的爬虫通过给定的一个网站链接,下载链接的网页,用正则表达式提取链接内的网址,然后再重复下载获得的网址的网页,提取链接。
实现
由于这只是简单的实现爬虫,所以未使用数据库来保存待请求,已经请求过得链接,而使用Dictionary类型数据来代替,Dictionary类型数据能有效的减短链接匹配的时间复杂度。
为了防止类似 http://xxxx.xxx?xx=xxx&t=123456 这样的类似的网站循环导向 http://xxxx.xxx?xx=xxx&t=123457 导致爬虫死循环,所以把链接分层管理,主页的链接为第一层,第一层链接的网页内容中的链接为第二层链接,依次类推,理论上网页的深度不超过17层,所以可以通过控制爬虫深爬的层数来防止死循环。
主要代码:
////// 请求过的链接 /// Dictionary<string, UrlInfo> requestedUrls = new Dictionary<string, UrlInfo>(); /// /// 将要请求的链接 /// Dictionary<string, UrlInfo> toRequestUrls = new Dictionary<string, UrlInfo>(); /// /// 正在请求的链接 /// Dictionary<string, UrlInfo> OnRequestUrls= new Dictionary<string, UrlInfo>(); /// /// 当前文档编码 /// Encoding encoder = Encoding.UTF8; /// /// 字典数据读写锁 /// object lockhash = new object(); /// /// 处理一个链接,请求链接内容,获取网址保存到toRequestUrls,并建立文档索引 /// public string RequestWebPageThread(UrlInfo urlinfo, Encoding encoder=null) { Uri uri = new Uri(urlinfo.Url, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri || uri.IsFile) return string.Empty; WebClient wc = new WebClient(); //伪装User-Agent,防止部分网站禁止爬取数据 wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:0.9.4) Gecko/20011128 Netscape6/6.2.1"); var downdata = wc.DownloadData(uri); string datastr = string.Empty; if (encoder != null) { datastr = encoder.GetString(downdata); } else { string html = Encoding.UTF8.GetString(downdata); //更具网页内容,获取编码方式 Encoding realEncoding = HtmlHelper.GetEncoding(html); datastr = realEncoding.GetString(downdata); } //理论上网站最多17层,当当前请求的网页相对于主页的层数超过webrequestLevel时则不获取其页面内的链接 if (urlinfo.Level < webrequestLevel) { var mts = urlReg.Matches(datastr); var dataurls = from Match m in mts select m.Groups[1].Value; lock (lockhash) { //添加url到字典中 foreach (string durl in dataurls) { try { Uri nuri; if (absobleUrlReg.IsMatch(durl)) { nuri = new Uri(durl, UriKind.Absolute); if (nuri.Host != uri.Host) continue; } else { nuri = new Uri(uri, durl); } FileBase fb = new FileBase(nuri.ToString()); if (unacceptext.Contains(fb.Ext.ToLower())) continue; //添加url到索引列表 if (!(requestedUrls.ContainsKey(nuri.ToString()) || toRequestUrls.ContainsKey(nuri.ToString()) || onRequestUrls.ContainsKey(nuri.ToString()))) toRequestUrls.Add(nuri.ToString(), new UrlInfo() { Url = nuri.ToString(), Level = urlinfo.Level + 1 }); } catch (Exception ex) { } } } } if (!string.IsNullOrWhiteSpace(datastr)) { var titleMT = MakeIndexRule.TitleReg.Match(datastr); string title = titleMT.Success ? titleMT.Groups[1].Value : string.Empty; if (!string.IsNullOrWhiteSpace(title)) { indexrule.AddItem(uri.ToString(), title,datastr); } } Console.WriteLine(string.Format("{0}-{1}-{2}",requestedUrls.Count,onRequestUrls.Count,toRequestUrls.Count)); return datastr; } /// /// 网页请求线程函数 /// public void RequestThread(object e) { while (true) { if (requestedUrls.Count > 0 && toRequestUrls.Count <1 && onRequestUrls.Count <1) { break; } if (toRequestUrls.Count <1) { Thread.Sleep(200); continue; } UrlInfo url=null; lock (lockhash) { //获取下一个要请求的url if (toRequestUrls.Count > 0) { url = toRequestUrls.FirstOrDefault().Value; onRequestUrls.Add(url.Url,url); toRequestUrls.Remove(url.Url); } } if (null!=url) { try { //请求网页 RequestWebPageThread(url, encoder); } catch (Exception) {
} finally { lock (lockhash) { onRequestUrls.Remove(url.Url); requestedUrls.Add(url.Url,url); } } } } } ////// 初始化爬虫 /// public void StartRequestThread(string url,Encoding encoder,int threadcount) { this.encoder = encoder; toRequestUrls.Add(url,new UrlInfo() { Url=url,Level=1}); //创建索引构造器 indexrule = new MakeIndexRule(); try { Uri uri = new Uri(url, UriKind.Absolute); //获取索引绝对位置 indexrule.INDEX_DIR = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, uri.Host); if (indexrule.StartIndex(uri.Host)) { for (int i = 0; i ) { //创建网页请求线程 Thread thread = new Thread(RequestThread); thread.IsBackground = true; thread.Start(url); } } } catch (Exception) { } finally { while (true) { if (requestedUrls.Count > 0 && toRequestUrls.Count <1 && onRequestUrls.Count <1 ) { break; } Thread.Sleep(1000); } indexrule.Rebuild(); indexrule.Close(); } }
网页的编码方式有多种,使用WebClient请求的网页数据时常乱码,经查看网页文件发现通常有一行
所以我们可以根据meta标签内的编码方式来编码。
public static Encoding GetEncoding(string html) { var pattern = "(?i)charset *= *[\'\"]?(?[-a-zA-Z_0-9]+)[\'\"]? "; var charset = Regex.Match(html,pattern,RegexOptions.IgnoreCase).Groups["charset"].Value; if(charset.Length <= 0) { charset = Encoding.UTF8.BodyName; } try { return Encoding.GetEncoding(charset); } catch(Exception) { return Encoding.Default; } }
实现效果
下载地址:http://sdrv.ms/1camhyg
总结
心血来潮就写了这个,只为学习参考,代码写的有点乱。毕业了,这是我在学校的最后一个学习项目。工作了要多到园子里分享,毕竟园子里有很多大神帮助过我们。就这样吧,回家了。