我们知道,要想对数据进行检索,最基本也是最重要的东西就是数据本身了。
本章介绍如何获取大量的网页信息。
相信大家都听说过‘网络爬虫’,我们正是通过这种方式搜集网页的。
一、下面首先简单的介绍一下网络爬虫的基本结构:
简单的讲就是:
1、从一个url开始搜索,将这个页面上的所有链接保存,放入一个queue中。
2、接着从这个queue中取出一个url,重复第1步
这个过程类似于BFS(广度优先搜索)。(为了防止url被重复使用,这里可以用两个集合分别存放已下载与未下载的url)。
由于下载网页的速度与网速有关,cpu的时间大部分时间都消耗到了等待上面,因此,这里的网络爬虫采用的是多线程的方式。
二、分析网络爬虫的源码:
首先对程序各个类进行简要的讲解:
Cyh_HttpServer类:
该类中只有一个方法public string GetResponse(string url)功能是对指定的url获取该页面的html,实现该功能必须解决以下几个问题:
1.如何获取指定url的html?
其实实现该功能很简单,在C#中通过HttpWebResponse类的调用就能实现,具体方法是:
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream reader = response.GetResponseStream();
然后从reader流中读取内容就行了
2.编码问题,网页通常使用utf-8或gb2312进行编码,如果程序在读取流时使用了错误的编码会导致中文字符的错误
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_HttpServer的GetResponse方法取出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