首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > .NET > C# >

lucene.Net-学习札记(3)-C#'网络爬虫' 源码详解

2012-09-20 
lucene.Net--学习笔记(3)---C#网络爬虫 源码详解我们知道,要想对数据进行检索,最基本也是最重要的东西就是

lucene.Net--学习笔记(3)---C#'网络爬虫' 源码详解

我们知道,要想对数据进行检索,最基本也是最重要的东西就是数据本身了。

本章介绍如何获取大量的网页信息。

相信大家都听说过‘网络爬虫’,我们正是通过这种方式搜集网页的。

 

一、下面首先简单的介绍一下网络爬虫的基本结构:

 

简单的讲就是:

1、从一个url开始搜索,将这个页面上的所有链接保存,放入一个queue中。

2、接着从这个queue中取出一个url,重复第1步

 

这个过程类似于BFS(广度优先搜索)。(为了防止url被重复使用,这里可以用两个集合分别存放已下载与未下载的url)。

由于下载网页的速度与网速有关,cpu的时间大部分时间都消耗到了等待上面,因此,这里的网络爬虫采用的是多线程的方式。

 

二、分析网络爬虫的源码:

首先对程序各个类进行简要的讲解:

Cyh_HttpServer类:

 

该类中只有一个方法public string GetResponse(string url)功能是对指定的url获取该页面的html,实现该功能必须解决以下几个问题:

1.如何获取指定url的html?

其实实现该功能很简单,在C#中通过HttpWebResponse类的调用就能实现,具体方法是:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);HttpWebResponse response = (HttpWebResponse)request.GetResponse();Stream reader = response.GetResponseStream();

 

然后从reader流中读取内容就行了

 

2.编码问题,网页通常使用utf-8或gb2312进行编码,如果程序在读取流时使用了错误的编码会导致中文字符的错误

 

3.对于有些页面的html可能会非常大所以我们要限制大小,在程序中最在读取不会超过100k。

 

代码如下:

 

/// <summary>    /// <para>HTTP服务类</para>    /// 由于在程序外该类是不可见的,所以声明时用了internal.    /// </summary>    internal class Cyh_HttpServer    {        public string GetResponse(string url)        {            string html = string.Empty;         //文本内容            string encoding = string.Empty;     //文本格式            #region MyRegion            try            {                //创建一个hettpReq请求对象,包含要传递的值name                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);                request.Method = "get";     //发送方式                request.ContentType = "text/html";  //Http标头的值                request.Timeout = 30 * 1000;        //请求超时时间                byte[] buffer = new byte[1024];                //使用using的作用,可以在using结束时,回收所有using段内的内存                //创建一个响应对象,并重请求对象中得到响应对象的事例。                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())                {                    using (Stream reader = response.GetResponseStream())    //得到回应过来的流                    {                        reader.ReadTimeout = 30 * 1000;                        #region 处理流                        //MemoryStream是一个支持存储区为内存的流。                        using (MemoryStream memory = new MemoryStream())                        {                            int index = 1;                            int sum = 0;                            //限制的读取的大小不超过100k                            while (index > 0 && sum < 100 * 1024)                            {                                index = reader.Read(buffer, 0, 1024);                                if (index > 0)                                {                                    memory.Write(buffer, 0, index); //将缓存写入memory                                    sum += index;                                }                            }                            //网页通常使用utf-8或gb2312进行编码                                                       html = Encoding.GetEncoding("gb2312").GetString(memory.ToArray());  //返回与指定代码页名称关联的编码。                             if (string.IsNullOrEmpty(html))                            {                                return html;                            }                            else                            {                                Regex re = new Regex(@"charset=(?<charset>[\s\S]*?)[""|']");                                Match m = re.Match(html.ToLower());                                encoding = m.Groups["charset"].ToString();                            }                            if (string.IsNullOrEmpty(encoding) || string.Equals(encoding.ToLower(), "gb2312"))                            {                                return html;                            }                            else                            {                                //不是gb2312编码则按charset值的编码进行读取                                return Encoding.GetEncoding(encoding).GetString(memory.ToArray());                            }                        }                        #endregion                    }                }            }            #endregion            catch            {                return "";              }        }    }



Cyh_AbsChain类:

/// <summary>    /// <para>职责链抽象类</para>    /// 对于AbsChain采用的是职责链设计模式,    /// 目的是抽象出网络爬虫处理html的过程,    /// 因为在spider程序集中并不真正处理如何解析html,    /// 用户只需重载AbsChain类中的process方法,完成自定义的处理过程     /// </summary>    public abstract class Cyh_AbsChain     {        /// <summary>        /// 责任链中的一个 hander        /// </summary>        private Cyh_AbsChain _handler = null;        internal Cyh_AbsChain Handler        {            get            { return _handler; }        }        /// <summary>        /// 待处理的url        /// </summary>        private string _url = string.Empty;        public string Url        {            get { return _url; }            set { _url = value; }        }        /// <summary>        /// 文本处理过程(Protected abstract)        /// </summary>        /// <param name="htmlStream">html文本</param>        protected abstract void Process(string html);        /// <summary>        /// 设置下一个处理节点         /// </summary>        /// <param name="handler">下一个处理节点</param>        public void SetProcessHandler(Cyh_AbsChain handler)        {            _handler = handler;        }        /// <summary>        /// Cyh_AbsChain 开始处理         /// </summary>        /// <param name="htmlStream">html文本流</param>        public void Start_AbsChain(string html)        {            Process(html); //处理  用户重载方法            if (Handler != null)            {                Handler.Url = Url;                Handler.Start_AbsChain(html);               }        }    }



Cyh_ChainMain类:

 

/// <summary><para>ChainMain类是对AbsChain类的具体实现</para>    /// 它的Process方法是个空方法,    /// 所以你可以把它理解成它就是具体处理职责链上的头节点,    /// 通过ChainMain类的_handler将处理任务往下传递,    /// 用户通过调用ChainMain的SetProcessHandler方法设置下一个处理节点,    /// 这个节点必须由用户继承AbsChain并实现抽象方法Process    /// </summary>    internal class Cyh_ChainMain : Cyh_AbsChain    {        /// <summary> 需要用户重置的处理函数 </summary>        protected override void Process(string html)        {                    }    }

 

Cyh_WordThread类:

 

/// <summary>    /// <para>工作线程</para>    /// <para>WorkThread类是工作线程类,    /// 每个工作线程类都包括</para>    /// <para>一个职责链的头节点ChainMain、一个HttpServer类和一个UrlStack,</para>    /// 其中UrlStack类采用了单构件设计模式,    /// 所以对于整个应该用程序都是使用一个UrlStack对象。    /// </summary>    internal class Cyh_WordThread    {        #region 定义头节点ChainMain、HttpServer类和UrlStack        private Cyh_ChainMain _chainHeader = new Cyh_ChainMain();        internal Cyh_ChainMain ChainMain        { get { return _chainHeader; } }        private Cyh_HttpServer _httpServer = new Cyh_HttpServer();        internal Cyh_HttpServer HttpServer        { get { return _httpServer; } }        public Cyh_UrlStack UrlStack        { get { return Cyh_UrlStack.Instance; } }         private bool _isRun = false;        public bool IsRun        { get { return _isRun; } }         #endregion        /// <summary>        /// <para>工作线程入口函数</para>        /// Start_WordThread()从UrlStack中取出url,        /// 并调用Cyh_HttpServer的GetResponse方法取出Url对应网页的HTML代码,        /// 并将HTML代码传递给职责链的头节点Cyh_ChainMain,        /// 由它的Start_AbsChain()方法开始处理。        ///         /// 它是先调用自身类的Process方法,        /// 然后再调用_handler.Start_AbsChain()方法,        /// 就这样把处理过程传递下去。        /// </summary>        public void Start_WordThread()        {            #region Try            try            {                this._isRun = true;                while (_isRun)                {                    string url = this.UrlStack.Pop();                    if (!string.IsNullOrEmpty(url))                    {                        string html = _httpServer.GetResponse(url);                        if (!string.IsNullOrEmpty(html))                        {                            this.ChainMain.Url = url;                            //处理得到的html                            this.ChainMain.Start_AbsChain(html);                          }                    }                }            }             #endregion              catch            {                 }         }        /// <summary>        /// 停止工作线程        /// </summary>        public void Stop_WorkThread()        {            this._isRun = false;            }    }

 

Start_WordThread方法是工作线程的入口方法,它从Cyh_UrlStack中取出url,并调用Cyh_HttpServer的GetResponse方法取出Url对应 网页的HTML代码,并将HTML代码传递给职责链的头节点Cyh_ChainMain,由它的Start_WordThread方法开始处理。回忆一下Cyh_AbsChain的 Start_AbsChain()方法,它是先调用自身类的Process方法,然后再调用_handler.Start_AbsChain(方法,就这样把处理过程传递下去。


 

Cyh_UrlStack类:

/// <summary>    /// UrlStack类非常的简单,    /// 它采用单构件设计模式,    /// 整个程序只用到一个UrlStack对象    /// 并维护了一个数据结构,    /// 该数据结构用来存储需要爬虫抓取的Url    /// </summary>    public class Cyh_UrlStack    {        private static Cyh_UrlStack _urlstack = new Cyh_UrlStack();        /// <summary> stack、用来存放url </summary>        private Queue<string> _stack = new Queue<string>();        /// <summary> stack的最大存放数量 </summary>        private readonly int _maxLength = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["MaxLength"]);        /// <summary> 构造函数 </summary>        private Cyh_UrlStack() { }        /// <summary> UrlStack的实例 </summary>        public static Cyh_UrlStack Instance        {            get { return _urlstack; }        }        public void Push(string url)        {            lock (this)            {                if (!_stack.Contains(url))                {                    if (_stack.Count >= _maxLength)                    {                        _stack.Dequeue();   //移除并返回位于 Queue 开始处的对象。                    }                    _stack.Enqueue(url);    //将url添加到 Queue 的结尾处。                }            }        }        public string Pop()        {            lock (this)            {                if (_stack.Count > 0)                {                    return _stack.Dequeue();                }                else                {                    return "";                  }            }        }        public int Count        {            get { return _stack.Count; }        }    }


Cyh_AbsThreadManager类:

/// <summary>    /// <para>AbsThreadManager的主要功能是管理开启WorkThread工作线程,</para>    /// 与监控线线程的,WorkThread对象与Thread对象一一对应,    /// 这两个对象都被封在ObjThread对象里    ///     /// 在AbsThreadManagers中用List<ObjThread>来维护一系列的线程对象与WorkThread对象,    /// 同时在 AbsThreadManagers中增加了一个监控线程,    /// 用来查看工作线程的工作线程,    /// 若工作线程死去,由监控线程重新启动。    /// </summary>    public abstract class Cyh_AbsThreadManager    {        public int _maxThread = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["MaxCount"]);        /// <summary>  用List<ObjThread>来维护一系列的线程对象与WorkThread对象, </summary>        internal List<Cyh_ObjThread> list = new List<Cyh_ObjThread>();        private bool _isRun = false;        /// <summary> 用来监控线程存活死亡的主线程 </summary>        private System.Threading.Thread _watchThread = null;        /// <summary> 当前深度 </summary>        public int Current { get { return Cyh_UrlStack.Instance.Count; } }        /// <summary>        /// 开启服务        /// </summary>        /// <param name="url">种子URL</param>        public void Start_AbsThreadManager(string url)        {            Cyh_UrlStack.Instance.Push(url);            _isRun = true;            //初始化线程list            for (int i = 0; i < _maxThread; i++)            {                this.AddObjThread();                }            _watchThread = new System.Threading.Thread(Watch);            _watchThread.Start();           }        /// <summary> 停止服务 </summary>        public void Stop_AbsThreadManager()        {            _isRun = false;            _watchThread.Join();       //阻塞调用线程,直到线程终止为止。            foreach (Cyh_ObjThread obj in list)            {                obj.WorkThread.Stop_WorkThread();                obj.Thread.Abort();                obj.Thread.Join();            }            list.RemoveRange(0, list.Count);        }        /// <summary> 增加一个线程 </summary>        private void AddObjThread()        {            Cyh_ObjThread thread = new Cyh_ObjThread();             //初始化一个新的Thread            thread.WorkThread = new Cyh_WordThread();            //设置该线程用于处理职责链中的下一个节点            thread.WorkThread.ChainMain.SetProcessHandler(GetChainHeader());            thread.Thread = new System.Threading.Thread(thread.WorkThread.Start_WordThread);            list.Add(thread);   //线程list中加入新的thread            thread.Thread.Start();  //开启该线程        }        /// <summary>        /// <para>设置职责链头节点,该方法由用户设定</para>        /// 返回一个继承了Cyh_AbsChain类的对象,        /// 这个对象将会被设置到 Cyh_ChainMain的_handler中        /// </summary>        /// <returns>返回用户定义的Chain</returns>        protected abstract Cyh_AbsChain GetChainHeader();        /// <summary>        /// 监测存活的或正在运行的线程,        /// 将运行结束或死亡的进程去除,        /// 并新增线程         /// </summary>        internal void Watch()        {            List<Cyh_ObjThread> newList = new List<Cyh_ObjThread>();            while (this._isRun)            {                try                {   //检测存活的线程并保存下来,                    foreach (Cyh_ObjThread temp in this.list)                    {                        if (temp.WorkThread.IsRun && temp.Thread.IsAlive)                        {                            newList.Add(temp);                        }                    }                    //更新list中的线程                    this.list.RemoveRange(0, list.Count);                    list.AddRange(newList);                    int newCount = this._maxThread - this.list.Count;                    //加入其它新的线程,使list中的线程数达到_maxThread                    for (int i = 0; i < newCount; i++)                    {                        this.AddObjThread();                    }                    newList.RemoveRange(0, newList.Count);                    //System.Threading.Thread.Sleep(5 * 1000);                }                catch                { }                finally                {                                     }            }        }    }

在Cyh_AbsThreadManager中用到了一个类Cyh_ObjThread,它是一种线程的类型,看定义:

 

Cyh_ObjThread类:

internal class Cyh_ObjThread    {        private Cyh_WordThread _workThread;        internal Cyh_WordThread WorkThread        {            get { return _workThread; }            set { _workThread = value; }        }        private System.Threading.Thread _thread;        public System.Threading.Thread Thread        {            get { return _thread; }            set { _thread = value; }        }        }

 

以上为网络爬虫中的重要的几个类,还有几个用于客户端的类没有各处,如果需要源代码的可以在评论中提出,人员较多的话,我可以上传。

 

 

---------------------------------------------------2012年9月17日23:49:28

热点排行