C#多线程网页采集器(Spider,网页爬虫)

用例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#编写的多线程异步抓取网页的网络爬虫控制台程序 功能:目前只能提取网络链接,所用的两个记录文件并不需要很大。网页文本、图片、视频和html代码暂时不能抓取,请见谅。 但需要注意,网页的数目是非常庞大的,如下代码理论上大概可以把整个互联网网页链接都抓下来。 但事实上,由于处理器功能和网络条件(主要是网速)限制,一般的家用电脑最多能胜任12个线程左右的抓取任务,抓取速度有限。可以抓取,但需要时间和耐心。 当然,这个程序把所有链接抓下来是可能的,因为链接占系统空间并不多,而且有记录文件的帮助,已抓取网页的数量可以堆积下去, 甚至可以把所有的互联网网络链接都存取下来,当然,最好是分批次。建议设置maxNum为500-1000左右,慢慢累积下去。 另外因为是控制台程序,有时候显示字符过多会系统会暂停显示,这时候只要点击控制台按下回车键就可以了。程序假死的时候,可以按回车键(Enter)试试。 /// 使用本程序,请确保已创建相应的记录文件,出于简化代码的考虑,本程序做的并不健壮,请见谅。 /// 默认的文件创建在E盘根目录“已抓取网址.txt”和“待抓取网址.txt”这两个文本文件中,使用者需要自行创建这两个文件,注意后缀名不要搞错。 这两个文件里面的链接基本都是有效链接,可以单独处理使用。 本爬虫程序的速度如下: 10线程最快大概500个链接每分钟 6-8线程最快大概400-500个链接每分钟 2-4线程最快大概200-400个链接每分钟 单线程最快大概70-100个链接每分钟 之所以用多线程异步抓取完全是出于效率考虑,本程序多线程同步并不能带来速度的提升,只要抓取的网页不要太多重复和冗余就可以,异步并不意味着错误。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值