PHP CURL 并发 采集

来源:互联网 发布:ktv是什么意思网络语 编辑:程序博客网 时间:2024/05/16 10:12

2014年的第一缕阳光终于在2月27日早上照在了北京的街道上, 心情豁然开朗, 写个博客 以记录此重大事件.


说并发之前,先说防封锁吧.


近期工作中需要采集一些其它网站的数据(对同行兄弟表示歉意).


1. 先用了 file_get_contents ,结果 根本得不到任何内容.

2.换curl ,一点问题都没有,内容获取成功. 但 很快就被对方屏蔽了, 导致 整个公司 的同事都无法访问 目标网站. 由此可见,目标网站是根据访问者的IP进行的封锁.

3.使用代理,这个CURL支持.  上网搜了一堆代理服务器的地址(几千个), 

4.程序中轮换使用随机代理,  但有些代理不好用, 也没办法手工剔除.  解决方法: 所有代理地址写到数据库的表中, 根据抓取成功与失败, 剔除掉不好用的服务器.

5.随机轮换UserAgent.

6.完成以上后,采集是没问题了,但速度 不满意, 主要时间都在网络上(从代理访问目标网站). 开始研究并发.

7.查了一下资料,也不难.

8.并发数是个问题, 根据个人的电脑和网络情况吧.


说那么多,开始贴代码


class LSynCatch {
    //默认的超时设置 10 秒
    private static $timeout=10;
    
    //构造一个连接句柄
    public static function curlInit($url){
        $ch = curl_init ( $url );
        
        //随机取一个用户代理,  此变量请自行添加
        $agent=self::$agents[rand(0,count(self::$agents)-1)];
        
        //设置CURL参数
        curl_setopt_array($ch, array(
            CURLOPT_RETURNTRANSFER=>true, //要求返回结果
            CURLOPT_TIMEOUT=>self::$timeout,//超时
            CURLOPT_USERAGENT=> $agent, //用户代理
            CURLOPT_REFERER=>'', //上一次页面
            CURLOPT_COOKIE=>'', //COOKIE 无
            CURLOPT_FOLLOWLOCATION=>false, //不自动 跳转
            
            //以下是Header,用FireBug之类的抓取一个正常请求的Header数据就可以
            CURLOPT_HTTPHEADER=>array (

                       //.....略

            ),
        ));
        return $ch;
    }
    
    //全部请求地址的栈
    private $urlStack=array();
    
    //入栈
    public function push(array $info){
        array_push($this->urlStack, $info);
    }
    
    //全部入栈完成后,开始爬行
    public function run(){
        $chs = curl_multi_init();
        $map = array();
        
        //初始20个并发,  根据网络情况自行修改
        while(count($map)<20){
            $info=array_pop($this->urlStack);
            $url=$info['url'];
            $ch=self::curlInit($url);


             //代理的类,请根据上述原则行开发
            $info['proxy']=LProxy::get();
            curl_setopt($ch, CURLOPT_PROXY, $info['proxy']);
            curl_multi_add_handle($chs, $ch);
            $map[strval($ch)] = $info;
        }
        
        //同时发起网络请求,持续查看运行状态
        do{
            $status = curl_multi_exec($chs, $active);
            if ($status == CURLM_CALL_MULTI_PERFORM) {
                continue;
            }
            
            if ($status != CURLM_OK) {
                continue;
            } //如果没有准备就绪,就再次调用curl_multi_exec
            
            //终于有请求完成的
            while ($done = curl_multi_info_read($chs)) {
                $ch=$done['handle'];
                $params = $map[strval($ch)];
                $params['info'] = curl_getinfo($ch);
                $params['errno']=curl_errno($ch);
                $params['error'] = curl_error($ch);
                $params['code']=curl_getinfo($ch,CURLINFO_HTTP_CODE);
                $params['result'] = curl_multi_getcontent($ch);

                //请求出错了,应该是代理服务器的错,换代理
                if($params['errno']){
                    LProxy::failure($params['proxy']);
                    if($params['errno']==28){
                        $error='timeout of '.self::$timeout.' s';
                    }else{
                        $error=$params['error'];
                    }
                    echo 'URL : '.$params['url']."\r\n";
                    echo 'Curl errno:'.$params['errno'].'    Curl error: ' . $error."\r\n";
                    continue;
                }
                
                //目标网站出错,如: 302,504,404之类
                if($params['code']==0){
                    echo 'URL : '.$params['url']."\r\n";
                    echo 'Code : 0 Length : '.strlen($params['result'])."\r\n";
                    continue;
                }
                    
                //本次抓取成功
                LProxy::success($params['proxy']);
                echo 'URL : '.$params['url']."\r\n";
                echo 'Http Code : '.$params['code']. " \tUsed : ".round($params['info']['total_time'],2)." \tLength : ".strlen($params['result'] )."\r\n";
                
                //调用 回调方法,对采集的内容进行处理
                if ($params['callback']) {
                    $params['callback']($params);
                }
                
                //从并发中去除此句柄
                curl_multi_remove_handle($chs, $ch);
                curl_close($ch);
                    
                //从栈里再拿一个放到并发中
                $info=array_pop($this->urlStack);
                if($info){
                    $ch=self::curlInit($info['url']);
                    $info['proxy']=LProxy::get();
                    curl_setopt($ch, CURLOPT_PROXY, $info['proxy']);
                    curl_multi_add_handle($chs, $ch);
                    $map[strval($ch)] = $info;
                }
                    
                //如果仍然有未处理完毕的句柄,那么就select
                if ($active > 0) {
                    curl_multi_select($chs, 0.5); //此处会导致阻塞大概0.5秒。
                }
            }
        }while($active > 0); //还有句柄处理还在进行中
        
        //全部结束
        curl_multi_close($chs);
    }


以上是并发采集类的主体代码,其它无关代码我就不贴了.


以下是调用 时的 代码


$syn=new LSynCatch();


           foreach ( $articles as $k => $v ) {
   
                // 分析文章内容
                $syn->push(array('url'=>$v,'callback'=>function(array $info){
                    self::process($info);
                }));
            }
            
            $syn->run();


0 0