用例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
// 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 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
{
Dictionary GetIndexeds(); //取得所有已下载的URL的MD5值
List GetSeeds();
int AddSeed( SeedInfo info );
void RemoveSeed( SeedInfo info );
void SaveContents( List 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();
}
// 构造函数2
public PageQueue( ref LinkedList 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 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 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 ToDictionary()
{
Dictionary dict = new Dictionary();
lock( _queue ){
LinkedListNode 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 _queue = null;
private static Dictionary _s_pages = new Dictionary();
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(); // 有多个线程
_shouldPause = new ManualResetEvent(true);
_shouldStop = false;
}
#endregion
#region Public Property
// 静态成员公开
public Dictionary IndexedPool
{
get{ return _s_indexedPool; }
}
public PageQueue SeedQueue
{
get{ return _s_seedQueue; }
}
// 当前采集队列
public PageQueue Queue
{
get{ return _queue; }
}
public List 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 urls = Analyzer.ParseToURLs(info);
PageInfo newInfo = null;
for( int i=0,len=urls.Count; i _threads; // 有多个线程
private ManualResetEvent _shouldPause; // 暂停
private bool _shouldStop; // 停止
private static Dictionary _s_indexedPool = new Dictionary(); // 已采集的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 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 infos = null;
while( !_shouldStop )
{
infos = DequeueSome( count );
try{
_store.SaveContents( infos );
}
catch( Exception ex ){
_log.Enqueue( ex.ToString() );
}
}
}
// 队列方法
private List DequeueSome( int count )
{
List infos = new List();
for( int i=0; i _threads = new List(); //线程
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 Queue
{
get{ return _s_queue; }
}
public List 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 _s_queue = new Queue(); // 一些标志性事件(异常或成功)
private List _threads = new List(); // 一个队列可以有多个线程
private bool _shouldStop;
private int _selfCheckInterval; // 日志模块自检间隔
private static bool _isSelfCheckRun = false;
#endregion
}
#endregion
} // end namespace My.WSE
C#多线程网页采集器(Spider,网页爬虫)
最新推荐文章于 2023-01-13 08:45:00 发布