一个C#写的爬虫程序
CodeProject上看见的感兴趣的文章,先研究着,有空翻译一下:
简介
网页爬虫(也被称做蚂蚁或者蜘蛛)是一个自动抓取万维网中网页数据的程序.网页爬虫一般都是用于抓取大量的网页,为日后搜索引擎处理服务的.抓取的网页由一些专门的程序来建立索引(如:Lucene,DotLucene),加快搜索的速度.爬虫也可以作为链接检查器或者HTML代码校验器来提供一些服务.比较新的一种用法是用来检查E-mail地址,用来防止Trackback spam.
爬虫概述
在这篇文章中,我将介绍一个用C#写的简单的爬虫程序.这个程序根据输入的目标URL地址,来有针对性的进行网页抓取.用法相当简单,只需要输入你想要抓取的网站地址,按下"GO"就可以了.
这个爬虫程序有一个队列,存储要进行抓取的URL,这个设计和一些大的搜索引擎是一样的.抓取时是多线程的,从URL队列中取出URL进行抓取,然后将抓取的网页存在指定的存储区(Storage 如图所示).用C#Socket库进行Web请求.分析当前正在抓取的页面中的链接,存入URL队列中(设置中有设置抓取深度的选项)
状态查看
这个程序提供三种状态查看:
抓取线程列表
每个抓取线程的详细信息
查看错误信息
线程查看
线程列表查看显示所有正在工作中的线程.每个线程从URI队列中取出一个URI,进行链接.
.
.
请求查看
请求查看显示了所有的最近下载的页面的列表,也显示了HTTP头中的详细信息.
每个请求的头显示类似下面的信息:
GET / HTTP/1.0
Host: www.cnn.com
Connection: Keep-Alive
Response头显示类似如下信息:
HTTP/1.0 200 OK
Date: Sun, 19 Mar 2006 19:39:05 GMT
Content-Length: 65730
Content-Type: text/html
Expires: Sun, 19 Mar 2006 19:40:05 GMT
Cache-Control: max-age=60, private
Connection: keep-alive
Proxy-Connection: keep-alive
Server: Apache
Last-Modified: Sun, 19 Mar 2006 19:38:58 GMT
Vary: Accept-Encoding,User-Agent
Via: 1.1 webcache (NetCache NetApp/6.0.1P3)
还有一个最近下载的页面的列表Parsing page
Found: 356 ref(s)
http://www.cnn.com/
http://www.cnn.com/search/
http://www.cnn.com/linkto/intl.html
设置
这个程序提供一些参数的设置,包括:
MIME types
存储目的文件夹
最大抓取线程数
等等...
文件类型
爬虫支持的下载下来的文件类型,用户可以添加MIME类型,支持可下载的文件包括一个默认类型用来用户可添加,编辑和删除MIME类型. 用户可以选择让所有MIME类型为下列数字.
输出
输出设定包括下载文件夹, 而请求的数目应保持在要求查看复审请求细节.
连接
连线设定包含:
Thread count: 多个并行工作的线程爬虫;
Thread sleep time when refs queue empty: 当时每个threadsleeps当refs队列空;
Thread sleep time between two connection: 时间,每一线程睡眠后处理的任何请求, 这是非常重要的参考价值,以防止主机堵爬虫由于沉重的负荷.
Connection timeout: 代表连接超时的时间;
Navigate through pages to a depth of: 代表深度;
Keep same URL server: 限制爬行的过程中,以同样的主机了原来的URL.
keepconnectionalive:手段不断Socket连接打开以后请避免reconnect时间.
高级
高级设置:
代码页编码的文本下载页面列出了用户定义列表限制词,让用户 防止任何坏页面列出了用户定义列表限制主机分机以免堵塞这类主机 名单一使用者定义限制档案清单分机避免paring非文字资料
兴趣要点
保持活动连接:
保持活动连接是一种形式,要求客户端和服务器保持连接打开后,反应完毕后, 可以通过添加一个HTTPheader的请求到服务器,在以下要求:
GET /CNN/Programs/nancy.grace/ HTTP/1.0
Host: www.cnn.com
Connection: Keep-Alive
"连接:保活"告诉服务器不会关闭连接, 但服务器的选择,把它打开或关闭它, 但应回复到客户端socket的决定. 所以服务器可以不断告诉客户,他将它打开了包括"连接: 保活"在他的replay如下: HTTP/1.0 200 OK
Date: Sun, 19 Mar 2006 19:38:15 GMT
Content-Length: 29025
Content-Type: text/html
Expires: Sun, 19 Mar 2006 19:39:15 GMT
Cache-Control: max-age=60, private
Connection: keep-alive
Proxy-Connection: keep-alive
Server: Apache
Vary: Accept-Encoding,User-Agent
Last-Modified: Sun, 19 Mar 2006 19:38:15 GMT
Via: 1.1 webcache (NetCache NetApp/6.0.1P3)
或者也可以告诉客户它拒绝如下:
HTTP/1.0 200 OK
Date: Sun, 19 Mar 2006 19:38:15 GMT
Content-Length: 29025
Content-Type: text/html
Expires: Sun, 19 Mar 2006 19:39:15 GMT
Cache-Control: max-age=60, private
Connection: Close
Server: Apache
Vary: Accept-Encoding,User-Agent
Last-Modified: Sun, 19 Mar 2006 19:38:15 GMT
Via: 1.1 webcache (NetCache NetApp/6.0.1P3)
WebRequest and WebResponse 问题:
当我开始这篇文章典我所用webrequest类以及webresponse像以下代码:
WebRequest request = WebRequest.Create(uri);
WebResponse response = request.GetResponse();
Stream streamIn = response.GetResponseStream();
BinaryReader reader = new BinaryReader(streamIn, TextEncoding);
byte[] RecvBuffer = new byte[10240];
int nBytes, nTotalBytes = 0;
while((nBytes = reader.Read(RecvBuffer, 0, 10240)) > 0)
{
nTotalBytes += nBytes;
}
reader.Close();
streamIn.Close();
response.Close();
本程序运作良好,但它有一个非常严重的问题,因为webrequest类函数getresponse门锁进入 所有其他进程webrequest告诉纳莉反应关闭的最后一道防线,在前面的代码. 等我看到总是一个线程下载,而其他人正等着getresponse. 要解决这个严重的问题,我有我的执行两班mywebrequest和mywebresponse. mywebrequest和mywebresponse使用Socket类来管理连接,而是类似webrequest和webresponse但却支持 并行反应在同一时间. 此外mywebrequest支持建旗keepalive支持keep-alive连接. 所以,我的新的代码whould像:
request = MyWebRequest.Create(uri, request/**//*to Keep-Alive*/, KeepAlive);
MyWebResponse response = request.GetResponse();
byte[] RecvBuffer = new byte[10240];
int nBytes, nTotalBytes = 0;
while((nBytes = response.socket.Receive(RecvBuffer, 0, 10240, SocketFlags.None)) > 0)
{
nTotalBytes += nBytes;
if(response.KeepAlive && nTotalBytes >= response.ContentLength && response.ContentLength > 0)
break;
}
if(response.KeepAlive == false)
response.Close();
刚刚更换getresponsestream与直接获取套接字会员mywebresponse阶层. 这样做,我做了简单的伎俩,使socket读明年开始后的头回答, 读一字节的时间告诉header完成,如下列代码:
/**//* reading response header */
Header = "";
byte[] bytes = new byte[10];
while(socket.Receive(bytes, 0, 1, SocketFlags.None) > 0)
{
Header += Encoding.ASCII.GetString(bytes, 0, 1);
if(bytes[0] == '/n' && Header.EndsWith("/r/n/r/n"))
break;
}
因此,用户myresponse类只会继续接收从第一位置的页面. 线程管理:线索数目的履带式是指用户通过设置. 它的默认值是10线程,但它可改变的设置选项连接. 履带代码处理这种改变的财产threadcount如下列代码:
private int ThreadCount
{
get { return nThreadCount; }
set
{
Monitor.Enter(this.listViewThreads);
for(int nIndex = 0; nIndex < value; nIndex ++)
{
if(threadsRun[nIndex] == null || threadsRun[nIndex].ThreadState != ThreadState.Suspended)
{
threadsRun[nIndex] = new Thread(new ThreadStart(ThreadRunFunction));
threadsRun[nIndex].Name = nIndex.ToString();
threadsRun[nIndex].Start();
if(nIndex == this.listViewThreads.Items.Count)
{
ListViewItem item = this.listViewThreads.Items.Add((nIndex+1).ToString(), 0);
string[] subItems = { "", "", "", "0", "0%" };
item.SubItems.AddRange(subItems);
}
}
else if(threadsRun[nIndex].ThreadState == ThreadState.Suspended)
{
ListViewItem item = this.listViewThreads.Items[nIndex];
item.ImageIndex = 1;
item.SubItems[2].Text = "Resume";
threadsRun[nIndex].Resume();
}
}
nThreadCount = value;
Monitor.Exit(this.listViewThreads);
}
}
如果theadcode增加了用户的代码创建一个新的线程,或暂时中止线程. 其他,树叶制的过程suppending额外的工作线程threads自己如下. 每个工作线程有一个名字等于其指数的线程数组. 如果线程名称值大于threadcount继续其工作,并进入中止模式. 爬行深度:它是深入到履带goes,在航行过程. 每个url已有初步深度相当于母公司深度加一, 在深度为0头url插入的用户. 在成交的URL从任何页都插入后,在年底的URL队列 意思是"先入先出"的行动. 和所有线程可插入排队随时在以下部分代码:
void EnqueueUri(MyUri uri)
{
Monitor.Enter(queueURLS);
try
{
queueURLS.Enqueue(uri);
}
catch(Exception)
{
}
Monitor.Exit(queueURLS);
}
而每个线程可以取出第一url在排队,要求它在以下部分代码:
MyUri DequeueUri()
{
Monitor.Enter(queueURLS);
MyUri uri = null;
try
{
uri = (MyUri)queueURLS.Dequeue();
}
catch(Exception)
{
}
Monitor.Exit(queueURLS);
return uri;
}