简单多线程网络爬虫的实现

前言

虽然在博客园也有两年的时间了,但基本没写过什么博客,昨天心血来潮,写了个专门针对某个网站的站外搜索工具,今天想写点什么和大家分享下。

由于使用Lucene搭建搜索引擎已经有大篇的文献可参考,所以在此不再详述。

主要实现功能

(1)实现简单网络爬虫,多线程从互联网下载网页,防死循环

(2)使用Lucene实现本地搜索,对下载的网页进行分析,创建索引

爬虫原理

本文实现的爬虫通过给定的一个网站链接,下载链接的网页,用正则表达式提取链接内的网址,然后再重复下载获得的网址的网页,提取链接。

实现

由于这只是简单的实现爬虫,所以未使用数据库来保存待请求,已经请求过得链接,而使用Dictionary类型数据来代替,Dictionary类型数据能有效的减短链接匹配的时间复杂度。

为了防止类似 http://xxxx.xxx?xx=xxx&t=123456 这样的类似的网站循环导向  http://xxxx.xxx?xx=xxx&t=123457 导致爬虫死循环,所以把链接分层管理,主页的链接为第一层,第一层链接的网页内容中的链接为第二层链接,依次类推,理论上网页的深度不超过17层,所以可以通过控制爬虫深爬的层数来防止死循环。   

主要代码:

        /// <summary>
        /// 请求过的链接
        /// </summary>
        Dictionary<string, UrlInfo> requestedUrls = new Dictionary<string, UrlInfo>();
        /// <summary>
        /// 将要请求的链接
        /// </summary>
        Dictionary<string, UrlInfo> toRequestUrls = new Dictionary<string, UrlInfo>();
        /// <summary>
        /// 正在请求的链接
        /// </summary>
        Dictionary<string, UrlInfo> onRequestUrls = new Dictionary<string, UrlInfo>();
        /// <summary>
        /// 当前文档编码
        /// </summary>
        Encoding encoder = Encoding.UTF8;
        /// <summary>
        /// 字典数据读写锁
        /// </summary>
        object lockhash = new object();
        /// <summary>
        /// 处理一个链接,请求链接内容,获取网址保存到toRequestUrls,并建立文档索引
        /// </summary>
        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;
        }
        /// <summary>
        /// 网页请求线程函数
        /// </summary>
        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); } } } } } /// <summary> /// 初始化爬虫 /// </summary> 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 < threadcount; 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 http-equiv="Content-Type" content="text/html; charset=utf-8"/>

所以我们可以根据meta标签内的编码方式来编码。

        public static Encoding GetEncoding(string html)
        {
            var pattern = "(?i)charset *= *['\"]?(?<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

总结

     心血来潮就写了这个,只为学习参考,代码写的有点乱。毕业了,这是我在学校的最后一个学习项目。工作了要多到园子里分享,毕竟园子里有很多大神帮助过我们。就这样吧,回家了。

posted @ 2013-06-27 07:59  杂货匠工  Views(816)  Comments(0)    收藏  举报