致力于技术进步

专注于编程艺术

博客园 首页 新随笔 联系 订阅 管理
这是一个C#语言编写的多线程网页自动采集程序。下面展示了主要类的代码。完整代码请点此下载
/**
软件工程过程实践:
--------------------------------------------
用例->数据模型->描述系统功能的接口->实际编码->测试->交付
文字  逻辑设计   顺序图(通讯图)                     定制
图    sql实现    类图(接口方法)                     配置
      c#实现



用例UC1: 网页采集
----------------------------------------------
范围: WSE应用
级别: 用户目标
主要参与者: 采集员
涉众及其关注点:
  ——采集员: 希望能够增加,删除监控URL,启动,停止监控URL,指定监控类型,查看监控URL的列表
  ——站长: 希望采集活动不要影响正常用户访问
前置条件: 采集员必须经过确认和认证
成功保证(或后置条件): 存储采集数据,确保没有重复的URL
主成功场景(或基本流程):
  1. 采集员增加新的监控URL,设定采集方式(本页,历遍本页,历遍网站),设定采集速度
  2. 采集员启动采集
  3. 采集员停止采集(如果有必要)
  4. 采集员删除某监控URL(如果有必要)
  5. 采集员浏览监控URL列表(如果有必要)



PageInfo数据模型
----------------------------
标识符        | id
创建时间      | createdTime
修改时间      | modifiedTime
创建人        | createdUser
修改人        | modifiedUser
              |
网址          | URL
网址128位MD5  | UrlMD5
IP地址        | IP
采集内容      | content
页面类型      | type
              |


采集流程图
-------------------------------

 初始化:载入已经采集的UrlMD5
|-------------------------------------------------------------------------------------------------------|
|Spider类                                                                                               |
|         |---------------------------------------------------------------------|    |----------------| |
|  |--|   |采集通道(线程)                         ------------------------------|--->| 已采集UrlMD5池 | |
|  |种|   |      |---------|    |--------|    |---|------|     |--------|       |    |----------------| |
|  |子|---|---|->| URL队列 |--->| 采集器 |--->|   |  |   |---->| 分析器 |---|   |                       |
|  |队|   |   |  |---------|    |--------|    |------|---|     |--------|   |   |                       |
|  |列|   |   |                                      |                      |   |                       |
|  |--|   |   |--------------------------------------|----------------------|   |    |----------------| |
|    |---<|                                          ---------------------------|--->| 已采集内容队列 | |
|    |    |---------------------------------------------------------------------|    |--------|-------| |
|    |                                                                                        |         |
|    |    |---------|                                                                         |         |
|    |---<|存储线程 |<------------------------------------------------------------------------|         |
|    |    |---------|                                                                                   |
|    |                                                                                                  |
|    |    |---------|                                                                                   |
|    |--->|日志线程 |                                                                                   |
|         |---------|                                                                                   |
|                                                                                                       |
|-------------------------------------------------------------------------------------------------------|


类设计
-------------------------------
控制器类
  SpiderHandler -- 控制台入口

采集核心类
  Spider     核心
  PageInfo   基础--数据结构
  Gatherer   基础--网页采集器
  Analyser   基础--url分析器

外部接口
  IStorage   数据存储接口
  ISpiderUI  用户界面
  ILogger    日志接口

*/

//==================================================================
//
//    该软件是一个由C#编写的基于控制台的多线程网页自动采集程序。
//    又称之为蜘蛛,机器人,爬行器等。
//
//    Copyright(C) 2009 themz.cn All rights reserved
//    author:   xml
//    email:    191081370@qq.com
//    blog:     http://programmingcanruinyourlife.themz.cn/
//    since:    .net2.0
//    version:  1.0
//    created:  2009-08-06
//    modified: 2009-10-10
//
//  版权与免责声明:本软件所有权归原作者,用户可自由免费使用于任何非商业环境。
//                  如果转载本文代码请不要删除这段版权声明。
//                  如果由于使用本软件而造成自己或他人的任何损失,均与本软件作者无关。
//                  特此声明!
//
//==================================================================
//    简单使用帮助:
//         1. 将下面代码保存到一个.cs后缀的文件中
//         2. 用.net2.0的编译环境编译成一个exe文件后,双击打开
//         3. 用 addSeeds命令添加采集种子, 例如: addSeeds http://url/
//         4. 用 start 命令开始采集
//         5. 反复使用 getContents 命令查看已采集到的内容
//         6. pause 命令可暂停采集, start 命令继续
//         7. stop 命令停止采集
//         8. exit 命令退出本软件
//
//

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

//using System.Configuration;
//using System.Diagnostics;
//[Serializable()]
namespace My.WSE.Spider
{
  #region 线程模式接口
    /**
    线程类模式

    接口  参数

    队列  属性
    线程  属性

    入队  方法
    出队  方法

    增加/启动  方法
    暂停       方法
    停止       方法
    */
    public interface IThread
    {
        //T Queue{ get; }
        //List<Thread> Threads{ get; }
        //
        //void Enqueue( T t );
        //T Dequeue();

        Thread AddThread();
        void RemoveThread();
        void RequestThreadPause();
        void RequestThreadPause( bool pauseOrContinue );
        void RequestThreadStop();
    }
  #endregion

  #region  外部接口
    // 采集接口
    public interface IGatherer
    {
        void Download( ref PageInfo info,string contentType,int timeout );
        void Download( ref PageInfo info,int timeout );
    }

    // 存储接口
    public interface IStorage
    {
        List<string> GetIndexeds();                      //取得所有已下载的URL的MD5值

        List<SeedInfo> GetSeeds();
        int AddSeed( SeedInfo info );
        void RemoveSeed( SeedInfo info );

        void SaveContents( List<PageInfo> info );        //保存采集到的内容
    }

    // 日志接口
    public interface ILogger
    {
        void Write( string content );
        string Read( string filename );

        string ToString( Exception ex );
    }

  #endregion

  #region 异常类
    public class ContentTypeException : Exception
    {
        public ContentTypeException( string message ) : base( message ){}
    }

    public class ContentSizeException : Exception
    {
        public ContentSizeException( string message ) : base( message ){}
    }

    public class NotOnlyException : Exception
    {
        public NotOnlyException( string message ) : base( message ){}
    }

    public class KeyHasExistsException : Exception
    {
        public KeyHasExistsException( string message ) : base( message ){}
    }
  #endregion

  #region PageInfo队列
    public class PageQueue
    {
        // 构造函数1
        public PageQueue()
        {
            _queue = new LinkedList<string>();
        }
        // 构造函数2
        public PageQueue( ref LinkedList<string> queue ) : this()
        {
            if( null != queue ){
                _queue = queue;
            }
        }


      #region 队列方法
        public int Count
        {
            get{  return _queue.Count;  }
        }
        public bool Contains( PageInfo info )
        {
            return _queue.Contains( info.UrlMD5 );
        }
        public void Enqueue( PageInfo info )   //等同于AddLast
        {
            AddLast( info );
        }
        public PageInfo Dequeue()              //等同于RemoveFirst
        {
            return RemoveFirst();
        }

        public void AddFirst( PageInfo info )
        {
            lock( _queue ){
                _queue.AddFirst( info.UrlMD5 );
                AddData( info );
                Monitor.Pulse( _queue );
            }
        }
        public void AddLast( PageInfo info )
        {
            lock( _queue ){
                _queue.AddLast( info.UrlMD5 );
                AddData( info );
                Monitor.Pulse( _queue );
            }
        }
        public PageInfo RemoveFirst()
        {
            PageInfo info = null;
            lock( _queue ){
                LinkedListNode<string> node = _queue.First;
                if( null == node ){
                    Monitor.Wait( _queue );
                    node = _queue.First;
                }

                string key = node.Value;
                _queue.RemoveFirst();
                info = GetData(key);
                RemoveData(key);    // 释放内存中的数据
            }
            return info;
        }
        public PageInfo RemoveLast()
        {
            PageInfo info = null;
            lock( _queue ){
                LinkedListNode<string> node = _queue.First;
                if( null == node ){
                    Monitor.Wait( _queue );
                }
                else{
                    string key = node.Value;
                    _queue.RemoveFirst();
                    info = GetData(key);
                    RemoveData(key);    // 释放内存中的数据
                }
            }
            return info;
        }
        public PageInfo Remove( PageInfo info )
        {
            lock( _queue ){
                if( _queue.Remove(info.UrlMD5) ){
                    info = GetData(info.UrlMD5);
                    RemoveData(info.UrlMD5);    // 释放内存中的数据
                }
                else{
                    info = null;
                }
            }
            return info;
        }

        public Dictionary<string,PageInfo> ToDictionary()
        {
            Dictionary<string,PageInfo> dict = new Dictionary<string,PageInfo>();

            lock( _queue ){
                LinkedListNode<string> node = _queue.First;
                while( null != node ){
                    dict[node.Value] = GetData(node.Value);
                    node = node.Next;
                }
            }
            return dict;
        }
      #endregion

      #region 词典方法
        public PageInfo GetData( string key )
        {
            lock( _s_pages ){
                if( _s_pages.ContainsKey(key) ){
                    return _s_pages[key];
                }else{
                    _log.Enqueue( string.Format( "wse.spider.cs GetData,Dictionary键{0}没有找到",key) );
                    return null;
                }
            }
        }
        public void AddData( PageInfo info )
        {
            lock( _s_pages ){
                _s_pages[info.UrlMD5] = info;
            }
        }
        public void RemoveData( string key )
        {
            lock( _s_pages ){
                if( _s_pages.ContainsKey(key) ){
                    _s_pages.Remove(key);
                }
            }
        }
        public bool ContainsData( PageInfo info )
        {
            return _s_pages.ContainsKey(info.UrlMD5);
        }
      #endregion

      #region Private Members

        private LinkedList<string> _queue = null;
        private static Dictionary<string,PageInfo> _s_pages = new Dictionary<string,PageInfo>();

        private EventLogger _log = new EventLogger();
      #endregion
    }
  #endregion

  #region 采集线程类
    public class PageGatherer : IThread
    {
      #region 构造函数
        // 构造函数1
        public PageGatherer(){}

        // 构造函数2
        public PageGatherer( IGatherer gather )
        {
            _log = new EventLogger();
            _store = new PageStorage();

            _gather = gather;
            _queue = new PageQueue();        // 每个队列可以
            _threads = new List<Thread>();   // 有多个线程

            _shouldPause = new ManualResetEvent(true);
            _shouldStop = false;
        }
      #endregion

      #region Public Property
        // 静态成员公开
        public Dictionary<string,string> IndexedPool
        {
            get{ return _s_indexedPool; }
        }
        public PageQueue SeedQueue
        {
            get{  return _s_seedQueue;  }
        }

        // 当前采集队列
        public PageQueue Queue
        {
            get{  return _queue;  }
        }
        public List<Thread> Threads
        {
            get{  return _threads;  }
        }
        // 线程总数
        public int ThreadCount
        {
            get{  return _threadCount;  }
        }
      #endregion

      #region 线程方法(Thread Method)
        // 增加线程
        public Thread AddThread()
        {
            Thread t = new Thread( new ThreadStart(ThreadRun) );
            t.IsBackground = true;
            t.Start();
            _threads.Add(t);
            _threadCount++;
            return t;
        }
        // 减少线程
        public void RemoveThread()
        {
            // 尚未实现
        }
        // 请求线程暂停
        public void RequestThreadPause()
        {

        }
        // 请求线程继续
        public void RequestThreadPause( bool pauseOrContinue )
        {
            if( !pauseOrContinue ){
                _shouldPause.Set();
            }else{
                _shouldPause.Reset();
            }
        }
        // 请求线程停止
        public void RequestThreadStop()
        {
            _shouldStop = true;
        }
      #endregion

      #region Private Methods
        // 采集线程方法
        private void ThreadRun()
        {
            PageInfo info = null;

            // 循环: URL->下载->存储->分析->|URL->下载....
            while( !_shouldStop )
            {
                _shouldPause.WaitOne();              // 是否暂停
                if( _queue.Count < 1 ){
                    _queue.Enqueue( _s_seedQueue.Dequeue() );     // 自动取得种子
                }

                info = _queue.Dequeue();
                if( null == info ){  continue;  }

                //1 下载
                string url = info.URL;
                try{
                    _gather.Download(ref info,"text/html",90000);
                }
                catch( Exception ex ){
                    _log.Enqueue( info.URL + " " + ex.ToString() );
                    continue;
                }

                //2 把当前url加入_s_indexedPool
                AddIndexed( info.UrlMD5 );

                //3 保存:加入_dataPool
                _store.Queue.Enqueue( info );

                //4 分析:加入下载队列queue
                AnalyzeToQueue( info, ref _queue );
            }
        }
        // 分析出页面中的url,并把它们加进队列中
        private void AnalyzeToQueue( PageInfo info, ref PageQueue queue )
        {
            PageQueue _queue = queue;

            List<string[]> urls = Analyzer.ParseToURLs(info);
            PageInfo newInfo = null;

            for( int i=0,len=urls.Count; i<len; i++ ){
                newInfo = new PageInfo( urls[i][0],info.SeedID );

                if( !_queue.ContainsData(newInfo) && !_s_indexedPool.ContainsKey(newInfo.UrlMD5) ){
                    newInfo.Title = urls[i][1];
                    newInfo.Referer = info.URL;

                    _queue.Enqueue( newInfo );
                }
            }
        }
        // 加入已采集队列
        private void AddIndexed( string urlMD5 )
        {
            lock( _s_indexedPool ){
                if( !_s_indexedPool.ContainsKey(urlMD5) ){
                    _s_indexedPool.Add( urlMD5, null );
                }
            }
        }
      #endregion

      #region Private Members
        private EventLogger _log = null;
        private PageStorage  _store = null;

        private IGatherer _gather = null;       // 接口
        private PageQueue _queue;               // 每个队列可以
        private List<Thread> _threads;          // 有多个线程

        private ManualResetEvent _shouldPause;  // 暂停
        private bool _shouldStop;               // 停止

        private static Dictionary<string,string> _s_indexedPool = new Dictionary<string,string>();      // 已采集的URL
        private static PageQueue _s_seedQueue = new PageQueue();   // 种子队列

        private static int _threadCount = 0;     // 运行的线程的总数
      #endregion
    }
  #endregion

  #region 存储线程类
    public class PageStorage  : IThread
    {
      #region 构造函数
        // 构造函数1
        public PageStorage(){}

        // 构造函数2
        public PageStorage( IStorage store )
        {
            _log = new EventLogger();

            _store = store;
            _shouldStop = false;
        }
      #endregion

      #region Public Property
        // 对列对象
        public PageQueue Queue
        {
            get{ return _s_queue;  }
        }
        // 线程对象集合
        public List<Thread> Threads
        {
            get{ return _threads;  }
        }
      #endregion

      #region 线程方法(Thread Method)
        // 增加线程
        public Thread AddThread()
        {
            Thread t = new Thread( new ThreadStart(ThreadRun) );
            t.IsBackground = true;
            t.Start();
            return t;
        }
        // 减少线程
        public void RemoveThread()
        {
            // 尚未实现
        }
        // 请求线程暂停
        public void RequestThreadPause()
        {
            // 尚未实现
        }
        // 请求线程继续
        public void RequestThreadPause( bool pauseOrContinue )
        {
            // 尚未实现
        }
        // 请求线程停止
        public void RequestThreadStop()
        {
            _shouldStop = true;
        }
      #endregion

      #region Private Methods
        // 线程方法
        private void ThreadRun()
        {
            if( null == _store ){ return;  }

            int count = 10;
            List<PageInfo> infos = null;

            while( !_shouldStop )
            {
                infos = DequeueSome( count );
                try{
                    _store.SaveContents( infos );
                }
                catch( Exception ex ){
                    _log.Enqueue( ex.ToString() );
                }
            }
        }
        // 队列方法
        private List<PageInfo> DequeueSome( int count )
        {
            List<PageInfo> infos = new List<PageInfo>();

            for( int i=0; i<count; i++ )  // 按每10条记录一组进行存储
            {
                infos.Add( _s_queue.Dequeue() );
            }

            return infos;
        }
      #endregion

      #region Private Members
        private EventLogger _log;    //日志

        private IStorage _store;                              //接口
        private static PageQueue _s_queue = new PageQueue();  //队列
        private List<Thread> _threads = new List<Thread>();   //线程

        private bool _shouldStop;
      #endregion
    }
  #endregion

  #region 日志线程类
    public class EventLogger : IThread
    {
        // 构造函数1
        public EventLogger(){}

        // 构造函数2
        public EventLogger( ILogger logger )
        {
            _logger = logger;
            _shouldStop = false;
            _selfCheckInterval = 300000;    // 5分钟
        }
      #region Public Properties
        public Queue<string> Queue
        {
            get{  return _s_queue;  }
        }
        public List<Thread> Threads
        {
            get{  return _threads;  }
        }
      #endregion

      #region 队列方法(Queue Method)
        public void Enqueue( string s )
        {
            lock( _s_queue ){
                _s_queue.Enqueue( s );
                Monitor.Pulse( _s_queue );
            }
        }
        public string Dequeue()
        {
            lock( _s_queue )
            {
                if( 1 > _s_queue.Count ){
                    Monitor.Wait( _s_queue );
                }
                return _s_queue.Dequeue();
            }
        }
      #endregion

      #region 线程方法(Thread Method)
        //
        public Thread AddThread()
        {
            Thread t = new Thread( new ThreadStart(ThreadRun) );
            t.IsBackground = true;
            t.Start();
            _threads.Add(t);
            return t;
        }
        // 减少线程
        public void RemoveThread()
        {
            // 尚未实现
        }
        // 请求线程暂停
        public void RequestThreadPause()
        {
            // 尚未实现
        }
        // 请求线程继续
        public void RequestThreadPause( bool pauseOrContinue )
        {
            // 尚未实现
        }
        // 请求线程停止
        public void RequestThreadStop()
        {
            _shouldStop = true;
        }
        // 增加自检线程
        public void AddSelfCheckThread()
        {
            if( false == _isSelfCheckRun ){
                Thread t = new Thread( new ThreadStart(SelfCheck) );
                t.IsBackground = true;
                t.Start();
                _isSelfCheckRun = true;
            }
        }
      #endregion

      #region Private Methods
        // 日志主线程函数
        private void ThreadRun()
        {
            if( null == _logger ){ return;  }

            while( !_shouldStop )
            {
                try{
                    _logger.Write( Dequeue() );
                }
                catch( Exception ex ){
                    Console.WriteLine( string.Format( "警告:日志写入发生错误{0}",ex.ToString() ) );
                }
            }
        }
        // 日志自检子线程函数
        private void SelfCheck()
        {
            if( null == _logger ){ return;  }

            while( !_shouldStop )
            {
                try{
                    _logger.Write( "日志自检完成" );
                    Thread.Sleep( _selfCheckInterval );
                }
                catch( Exception ex ){
                    Console.WriteLine( string.Format( "警告:日志自检发生错误{0}",ex.ToString() ) );
                }
            }
        }
      #endregion

      #region Private Members
        private ILogger _logger = null;                               // 接口
        private static Queue<string> _s_queue = new Queue<string>();  // 一些标志性事件(异常或成功)
        private List<Thread> _threads = new List<Thread>();           // 一个队列可以有多个线程

        private bool _shouldStop;

        private int _selfCheckInterval;   // 日志模块自检间隔
        private static bool _isSelfCheckRun = false;
      #endregion
    }
  #endregion

} // end namespace My.WSE
posted on 2010-04-14 13:34  stephen&amp;#183;周  阅读(4206)  评论(5编辑  收藏  举报