`
hyshucom
  • 浏览: 808264 次
文章分类
社区版块
存档分类
最新评论

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.如何获取指定urlhtml

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

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

HttpWebResponse response = (HttpWebResponse)request.GetResponse();

Stream reader = response.GetResponseStream();

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

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

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_HttpServerGetResponse方法取出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
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics