首页 »DotNet » 爬虫程序:用C#实现蜘蛛/爬虫程序的多线程控制 »正文
爬虫程序:用C#实现蜘蛛/爬虫程序的多线程控制
来源: 发布时间:星期五, 2009年1月9日 浏览:25次 评论:0
在 ![](/icons/26008smhl.gif) 爬虫/蜘蛛 ![](/icons/26008chengxu.gif) ![](/icons/26008de.gif) 制作(C#语言) ![](/icons/26008smhr.gif) ![](/icons/26008yi.gif) 文中 ![](/icons/26008dou.gif) 已经介绍了爬虫 ![](/icons/26008chengxu.gif) 实现 ![](/icons/26008de.gif) 基本思路方法 ![](/icons/26008dou.gif) 可以说 ![](/icons/26008dou.gif) 已经实现了爬虫 ![](/icons/26008de.gif) 功能 ![](/icons/26008dou2.gif) 只是它存在 ![](/icons/26008yi.gif) 个效率问题 ![](/icons/26008dou.gif) 下载速度可能很慢 ![](/icons/26008dou2.gif) 这是两方面 ![](/icons/26008de.gif) 原因造成 ![](/icons/26008de.gif) : 1.分析和下载不能同步进行 ![](/icons/26008dou2.gif) 在 ![](/icons/26008smhl.gif) 爬虫/蜘蛛 ![](/icons/26008chengxu.gif) ![](/icons/26008de.gif) 制作(C#语言) ![](/icons/26008smhr.gif) 中已经介绍了爬虫 ![](/icons/26008chengxu.gif) ![](/icons/26008de.gif) 两个步骤:分析和下载 ![](/icons/26008dou2.gif) 在单线程 ![](/icons/26008de.gif) ![](/icons/26008chengxu.gif) 中 ![](/icons/26008dou.gif) 两者是无法同时进行 ![](/icons/26008de.gif) ![](/icons/26008dou2.gif) 也就是说 ![](/icons/26008dou.gif) 分析时会造成网络空闲 ![](/icons/26008dou.gif) 分析 ![](/icons/26008de.gif) 时间越长 ![](/icons/26008dou.gif) 下载 ![](/icons/26008de.gif) 效率越低 ![](/icons/26008dou2.gif) 反的也是 ![](/icons/26008yi.gif) 样 ![](/icons/26008dou.gif) 下载时无法同时进行分析 ![](/icons/26008dou.gif) 只有停下下载后才能进行下 ![](/icons/26008yi.gif) 步 ![](/icons/26008de.gif) 分析 ![](/icons/26008dou2.gif) 问题浮出水面 ![](/icons/26008dou.gif) 我想大家都会想到:把分析和下载用区别 ![](/icons/26008de.gif) 线程进行 ![](/icons/26008dou.gif) 问题不就解决了吗? 2.只是单线程下载 ![](/icons/26008dou2.gif) 相信大家都有用过网际快车等下载资源 ![](/icons/26008de.gif) 经历 ![](/icons/26008dou.gif) 它里面是可以设置线程数 ![](/icons/26008de.gif) (近年版本默认是10 ![](/icons/26008dou.gif) 曾经默认是5) ![](/icons/26008dou2.gif) 它会将文件分成和线程数相同 ![](/icons/26008de.gif) 部分 ![](/icons/26008dou.gif) 然后每个线程下载自己 ![](/icons/26008de.gif) 那 ![](/icons/26008yi.gif) 部分 ![](/icons/26008dou.gif) 这样下载效率就有可能提高 ![](/icons/26008dou2.gif) 相信大家都有加多线程数 ![](/icons/26008dou.gif) 提升下载效率 ![](/icons/26008de.gif) 经历 ![](/icons/26008dou2.gif) 但细心 ![](/icons/26008de.gif) 用户会发现 ![](/icons/26008dou.gif) 在带宽 ![](/icons/26008yi.gif) 定 ![](/icons/26008de.gif) 情况下 ![](/icons/26008dou.gif) 并不是线程越多 ![](/icons/26008dou.gif) 速度越快 ![](/icons/26008dou.gif) 而是在某 ![](/icons/26008yi.gif) 点达到峰值 ![](/icons/26008dou2.gif) 爬虫作为特殊 ![](/icons/26008de.gif) 下载工具 ![](/icons/26008dou.gif) 不具备多线程 ![](/icons/26008de.gif) 能力何以有效率可谈?爬虫在信息时代 ![](/icons/26008de.gif) 目 ![](/icons/26008de.gif) ![](/icons/26008dou.gif) 难道不是快速获取信息吗?所以 ![](/icons/26008dou.gif) 爬虫需要有多线程(可控数量)同时下载网页 好了 ![](/icons/26008dou.gif) 认识、分析完问题 ![](/icons/26008dou.gif) 就是解决问题了: 多线程在C#中并不难实现 ![](/icons/26008dou2.gif) 它有 ![](/icons/26008yi.gif) 个命名空间: ![](/icons/26008System.gif) .Threading ![](/icons/26008dou.gif) 提供了多线程 ![](/icons/26008de.gif) 支持 ![](/icons/26008dou2.gif) 要开启 ![](/icons/26008yi.gif) 个新线程 ![](/icons/26008dou.gif) 需要以下 ![](/icons/26008de.gif) ![](/icons/26008chushi.gif) 化: ThreadStart startDownload = ThreadStart( DownLoad ); //线程起始设置:即每个线程都执行DownLoad![](/icons/26008kh.gif) 注意:DownLoad 必须为不带有参数 思路方法 Thread downloadThread = Thread( startDownload ); //例子化要开启 新类 downloadThread.Start ;//开启线程 由于线程起始时启动 ![](/icons/26008de.gif) 思路方法不能带有参数 ![](/icons/26008dou.gif) 这就为多线程共享资源添加了麻烦 ![](/icons/26008dou2.gif) 不过我们可以用类级变量(当然也可以使用其它思路方法 ![](/icons/26008dou.gif) 笔者认为此思路方法最简单易用)来解决这个问题 ![](/icons/26008dou2.gif) 知道开启多线程下载 ![](/icons/26008de.gif) 思路方法后 ![](/icons/26008dou.gif) 大家可能会产生几个疑问:
1.如何控制线程 ![](/icons/26008de.gif) 数量? 2.如何防止多线程下载同 ![](/icons/26008yi.gif) 网页? 3.如何判断线程结束? 4.如何控制线程结束? 下面就这几个问题提出解决思路方法: 1.线程数量我们可以通过for循环来实现 ![](/icons/26008dou.gif) 就如同当年初学编程 ![](/icons/26008de.gif) 打点 ![](/icons/26008chengxu.gif) ![](/icons/26008yi.gif) 样 ![](/icons/26008dou2.gif) 比如已知用户指定了n(它是 ![](/icons/26008yi.gif) 个 ![](/icons/26008int.gif) 型变量)个线程吧 ![](/icons/26008dou.gif) 可以用如下思路方法开启 5个线程 ![](/icons/26008dou2.gif) Thread downloadThread; //声名下载线程 这是C# 优势 即![](/icons/26008shuzu.gif) 化时 不需要指定其长度 可以在使用时才指定![](/icons/26008dou2.gif) 这个声名应为类级 这样也就为其它思路方法Control控件它们提供了可能 ThreadStart startDownload = ThreadStart( DownLoad ); //线程起始设置:即每个线程都执行DownLoad![](/icons/26008kh.gif) downloadThread = Thread[ n ];//为线程申请资源 确定线程总数 for( i = 0; i < n; i )//开启指定数量 线程数 { downloadThread[i] = Thread( startDownload );//指定线程起始设置 downloadThread[i].Start ;//逐个开启线程 } 好了 ![](/icons/26008dou.gif) 实现控制开启线程数是不是很简单啊? 2.下面出现 ![](/icons/26008de.gif) ![](/icons/26008yi.gif) 个问题:所有 ![](/icons/26008de.gif) 线程都 ![](/icons/26008diaoyong.gif) DonwLoad ![](/icons/26008kh.gif) 思路方法 ![](/icons/26008dou.gif) 这样如何避免它们同时下载同 ![](/icons/26008yi.gif) 个网页呢? 这个问题也好解决 ![](/icons/26008dou.gif) 只要建立 ![](/icons/26008yi.gif) 下Url地址表 ![](/icons/26008dou.gif) 表中 ![](/icons/26008de.gif) 每个地址只允许被 ![](/icons/26008yi.gif) 个线程申请即可 ![](/icons/26008dou2.gif) 具体实现: 可以利用数据库 ![](/icons/26008dou.gif) 建立 ![](/icons/26008yi.gif) 个表 ![](/icons/26008dou.gif) 表中有 4列 ![](/icons/26008dou.gif) 其中 ![](/icons/26008yi.gif) 列专门用于存储Url地址 ![](/icons/26008dou.gif) 另外两列分别存放地址对应 ![](/icons/26008de.gif) 线程以及该地址被申请 ![](/icons/26008de.gif) 次数 ![](/icons/26008dou.gif) 最后 ![](/icons/26008yi.gif) 列存放下载 ![](/icons/26008de.gif) 内容 ![](/icons/26008dou2.gif) (当然 ![](/icons/26008dou.gif) 对应线程 ![](/icons/26008yi.gif) 列不是必要 ![](/icons/26008de.gif) ) ![](/icons/26008dou2.gif) 当有线程申请后 ![](/icons/26008dou.gif) 将对应线程 ![](/icons/26008yi.gif) 列设定为当前线程编号 ![](/icons/26008dou.gif) 并将是否申请过 ![](/icons/26008yi.gif) 列设置为申请 ![](/icons/26008yi.gif) 次 ![](/icons/26008dou.gif) 这样 ![](/icons/26008dou.gif) 别 ![](/icons/26008de.gif) 线程就无法申请该页 ![](/icons/26008dou2.gif) 如果下载成功 ![](/icons/26008dou.gif) 则将内容存入内容列 ![](/icons/26008dou2.gif) 如果不成功 ![](/icons/26008dou.gif) 内容列仍为空 ![](/icons/26008dou.gif) 作为是否再次下载 ![](/icons/26008de.gif) 依据的 ![](/icons/26008yi.gif) ![](/icons/26008dou.gif) 如果反复不成功 ![](/icons/26008dou.gif) 则进程将于达到重试次数(对应该地址被申请 ![](/icons/26008de.gif) 次数 ![](/icons/26008dou.gif) 用户可设)后 ![](/icons/26008dou.gif) 申请下 ![](/icons/26008yi.gif) 个Url地址 ![](/icons/26008dou2.gif) 主要 ![](/icons/26008de.gif) 代码如下(以VFP为例):
<建立表> CREATE TABLE (ctablename) ( curl M , ctext M , ldowned I , threadNum I ) &&建立 个表ctablename.dbf 含有地址、文本内容、已经尝试下载次数、 线程标志(初值为-1 线程标志是从0开始 整数) 4个字段 <提取Url地址> cfullname = (ctablename) + '.dbf'&&为表添加扩展名 USE (cfullname) GO TOP LOCATE FOR (EMPTY( ALLTRIM( ctext ) ) AND ldowned < 2 AND ( threadNum = thisNum OR threadNum = - 1) ) &&查找尚未下载成功且应下载 属于本线程权限 Url地址 thisNum是当前线程 编号![](/icons/26008dou.gif) 可以通过参数传递得到 gotUrl = curl recNum = RECNO![](/icons/26008kh.gif) IF recNum <= RECCOUNT THEN &&如果在列表中找到这样 Url地址 UPDATE (cfullname) SET ldowned = ( ldowned + 1 ) , threadNum = thisNum WHERE RECNO = recNum &&更新表 将此记录更新为已申请 即下载次数加1![](/icons/26008dou.gif) 线程标志列设为本线程 编号![](/icons/26008dou2.gif) <下载内容> cfulltablename = (ctablename) + '.dbf' USE (cfulltablename) SET EXACT _disibledevent= 0; i < n; i )//关闭指定数量n 线程数 { downloadThread[i].Abort ;//逐个关闭线程 } 好了 ![](/icons/26008dou.gif) ![](/icons/26008yi.gif) 个蜘蛛 ![](/icons/26008chengxu.gif) 就这样完成了 ![](/icons/26008dou.gif) 在C#面前 ![](/icons/26008dou.gif) 它 ![](/icons/26008de.gif) 实现原来如此简单 这里笔者还想提醒读者:笔者只是提供了 ![](/icons/26008yi.gif) 个思路及 ![](/icons/26008yi.gif) 个可以实现 ![](/icons/26008de.gif) 解决方案 ![](/icons/26008dou.gif) 但它并不是最佳 ![](/icons/26008de.gif) ![](/icons/26008dou.gif) 即使这个方案本身 ![](/icons/26008dou.gif) 也有好多可以改进 ![](/icons/26008de.gif) 地方 ![](/icons/26008dou.gif) 留给读者研究 最后介绍说明 ![](/icons/26008yi.gif) 下我所使用 ![](/icons/26008de.gif) 环境: winXP sp2 Pro VFP 9.0 Visual Studio 2003 .net中文企业版
相关文章
读者评论
发表评论
|
|