最近遇到一个php爬虫的问题,在传统中,执行爬虫认为,php的file_get_contens每次只能爬取一个网页。因此爬虫运行中出现这样问题,前一秒执行爬取操作,网络下行满载,cpu负荷越0,下一秒钟cpu负荷满载,网络下行为0,这样就没有充分利用好网络和本地硬件资源,如果两者可以同时执行,那么原来2秒的操作优化后只要1秒来执行,效率提高100%。理论上cpui和网络同时满载是可行,下面思考下方案了。
1、爬取任务分摊
如果将100个网页爬取任务分为5份,然后分为t1.php、t2.php、t3.php、t4.php、t5.php这5个文件来处理,每个处理20个爬取任务,虽然整个过程不能提高400%的效率,但基本能最大限度利用网络资源和本地硬件资源。这种方式可以的,只是不够方便,需要人工开5个tab来执行。下面是依照上思路来的
2、php5的内置函数、模拟同时执行的多个请求
这里需要php5的两个函数stream_socket_client和stream_select,具体的可以查询手册,这里只说在此处的用法。
http://www.ibm.com/developerworks/cn/opensource/os-php-multitask/ ,这篇文章在介绍实现中比较详细,但是很可惜,这篇文章应该是大家从国外翻译过来的传抄者甚多,目前网上很多的相关代码基本上都不可用,这里的代码也有问题。经过研究和修改,这个给出可正常运行的代码。
<?php date_default_timezone_set('PRC'); echo "Program starts at ". date('h:i:s') . ".\n<br>"; $timeout=10; $result=array(); $sockets=array(); $convenient_read_block=8192; $delay=15; $id=0; while($delay>0) { $s=stream_socket_client("phaseit.net:80", $errno,$errstr,$timeout); if($s) { $http_message="GET /demonstration/delay?delay=".$delay." HTTP/1.0\r\nHost: phaseit.net\r\nAccept: */*\r\n\r\n"; fwrite($s,$http_message); $sockets[$id++]=$s; } else { echo "Stream " . $id . " failed to open correctly."; } $delay-= 3; //echo $delay; } $w=NULL; $e=NULL; while (count($sockets)) { $read=$sockets; stream_select($read,$w,$e,$timeout); if (count($read)) { foreach ($read as $r) { $id=array_search($r, $sockets); $data=fread($r,$convenient_read_block); if (strlen($data) == 0) { echo "Stream " . $id . " closes at " . date('h:i:s') . ".\n<br>"; //echo $result[$id]."<br>"; fclose($r); unset($sockets[$id]); } else { $result[$id].= $data; } } } else { echo "Time-out!\n"; break; } } ?>
可正常执行。
ps:过程中出现的问题:Strict Standards: Only variables should be passed by reference(原因:源代码中stream_select($read, $w=null, $e=null, $timeout); 这种写法标准,某些环境会出错。相关wiki:http://efreedom.com/Question/1-9601698/PHP-Ignores-Passing-Reference-Var-Assigned-Function-Call 。
stream_socket_client的作用是打开5个远程端口(这里是打开t1.php、t2.php等5个本地端口),替代fsockopen,兼容性较好。
stream_select来分配执行,特别之处是能模拟5个请求并发,fgets是不断读取这个5个网页返回的结果,直到返回=0,表示远程的脚本执行结束(5个php请求都如此操作),然后谁先返回结果,谁就显示出Stream X closes at XXXX 来。
fwrite可以对流操作,具体逻辑也一样,既在流后面加上一段字符。
fgets读取流中字符。当然fgets涉及指针操作。
没理解可继续参考:http://blog.sina.com.cn/s/blog_60b9ee7f0100qdmh.html http://num7.iteye.com/blog/706613 http://www.alixixi.com/program/a/2011041269527.shtml
总结:这个脚本可能不是特别能显示出效果,但是该方法在多个爬虫任务运行时候,就能显示出特点了,并行爬取100个页面,能极大提高效率,摆脱php单线程的短板。
windows的cmd命令行,整理成可执…