这是一个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