新蒲京200.c软件下载-app官网网址 > 文章 >

数码拆解深入分析结果如下

本次抓取了110万的客户数量,数据拆解解析结果如下:

图片 1

开荒前的计划

安装Linux系统(Ubuntu14.04),在VMWare虚构机下安装三个Ubuntu;

安装PHP5.6或上述版本;

设置MySQL5.5或上述版本;

安装curl、pcntl扩展。

利用PHP的curl增加抓取页面数据

PHP的curl扩充是PHP扶植的同意你与各样服务器使用各类别型的协商进行一连和通讯的库。

本程序是抓取博客园的顾客数据,要能访谈客户个人页面,要求客商登入后的工夫访谈。当大家在浏览器的页面中式点心击四个客户头像链接步入客户个人基本页面包车型地铁时候,之所以能够看出客户的新闻,是因为在点击链接的时候,浏览器帮您将本地的cookie带上一起提交到新的页面,所以你就会进来到客商的民用基本页面。由此实现访谈个人页面早先要求先得到顾客的cookie消息,然后在历次curl央求的时候带上cookie音信。在得到cookie音信方面,小编是用了团结的cookie,在页面中得以看见自个儿的cookie消息:

图片 2

二个个地复制,以”__utma=?;__utmb=?;”那样的款型整合三个cookie字符串。接下来就足以行使该cookie字符串来发送央求。

始发的示范:

$url = 'http://www.zhihu.com/people/mora-hu/about'; //此处mora-hu代表用户ID
$ch = curl_init($url); //初始化会话
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_COOKIE, $this->config_arr['user_cookie']);  //设置请求COOKIE
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);  
$result = curl_exec($ch);
return $result;  //抓取的结果

运营方面包车型地铁代码能够赢得mora-hu顾客的民用宗旨页面。利用该结果再使用正则表明式对页面举办拍卖,就会博取到姓名,性别等所急需抓取的消息。

图表防盗链

在对回到结果进行正则管理后输出个人新闻的时候,发今后页面中输出顾客头像时不可能开采。经过查阅资料获悉,是因为腾讯网对图纸做了防盗链管理。应用方案就是倡议图片的时候在伏乞头里冒充多少个referer。

在选拔正则表明式获取到图片的链接之后,再发三次呼吁,那时候带上海体育地方片伏乞的来自,表明该央浼来自搜狐网址的转向。具体育赛事比如下:

function getImg($url, $u_id)
{
    if (file_exists('./images/' . $u_id . ".jpg"))
    {
        return "images/$u_id" . '.jpg';
    }
    if (empty($url))
    {
        return '';
    }
    $context_options = array(  
        'http' =>  
        array(
            'header' => "Referer:http://www.zhihu.com"//带上referer参数
      )
  );

    $context = stream_context_create($context_options);  
    $img = file_get_contents('http:' . $url, FALSE, $context);
    file_put_contents('./images/' . $u_id . ".jpg", $img);
    return "images/$u_id" . '.jpg';
}

爬取越来越多顾客

抓取了和谐的个人新闻后,就须要再拜望顾客的关心者和关切了的用户列表获取越来越多的客户新闻。然后一层一层地拜望。可以看见,在个人基本页面里,有四个链接如下:

图片 3

那边有四个链接,一个是关注了,另多少个是关切者,以“关切了”的链接为例。用正则相配去匹配到相应的链接,获得url之后用curl带上cookie再发一回倡议。抓取到顾客关心了的用于列表页之后,能够获得上边包车型地铁页面:

图片 4

解析页面包车型地铁html结构,因为一旦获得客商的新闻,所以只须求框住的这一块的div内容,客户名都在这里中间。能够看出,顾客关怀了的页面包车型客车url是:

图片 5

昔不近期的顾客的那些url大概是毫发不爽的,分歧的地点就在于客户名这里。用正则相配获得客户名列表,一个三个地拼url,然后再每一个发央求(当然,二个一个是比非常慢的,上边有实施方案,那些稍后会提起)。步向到新客户的页面之后,再另行下面包车型地铁步子,就那样持续循环,直到到达你所要的数据量。

Linux计算文件数量

脚本跑了一段时间后,须要探视到底取得了轻微图片,当数据量比十分的大的时候,张开文件夹查看图片数量就有一点慢。脚本是在Linux意况下运作的,因此能够运用Linux的命令来总结文件数量:

ls -l | grep "^-" | wc -l

其中, ls -l 是长列表输出该目录下的文件信息(这里的文件可以是目录、链接、设备文件等); grep "^-" 过滤长列表输出信息, "^-" 只保留一般文件,如果只保留目录是 "^d" ; wc -l 是统计输出信息的行数。下面是一个运行示例:

图片 6

插入MySQL时再也数据的拍卖

程序运行了一段时间后,发现成比超级多客户的数码是再一次的,由此供给在插入重复客户数量的时候做拍卖。管理方案如下:

1)插入数据库以前检查数据是或不是业已存在数据库;

2)增添唯一索引,插入时接收 INSERT INTO ... ON DUPLICATE KEY UPDATE...

3)增添独一索引,插入时选取 INSERT INGNORE INTO...

4)增加独一索引,插入时使用 REPLACE INTO...

先是种方案是最简单易行但也是效率最差的方案,由此不使用。二和四方案的实行结果是同样的,不一样的是,在遇见相似的数目时, INSERT INTO … ON DUPLICATE KEY UPDATE 是一直更新的,而 REPLACE INTO 是先删除旧的数码然后插入新的,在此个历程中,还亟需再一次维护索引,所以速度慢。所以在二和四两个间接选举用了第二种方案。而第两种方案, INSERT INGNORE 会忽视推行INSERT语句现身的错误,不会忽视语法难点,不过忽视主键存在的情景。那样一来,使用 INSERT INGNORE 就越来越好了。最后,思考到要在数据库中记录重复数据的条数,因而在前后相继中应用了第二种方案。

使用curl_multi完成七十五线程抓取页面

刚早先单进度并且单个curl去抓取数据,速度非常慢,挂机爬了八个夜晚只可以抓到2W的数码,于是便想到能否在步入新的客户页面发curl乞求的时候一回性央求四个客户,后来察觉了curl_multi这一个好东西。curl_multi那类函数能够落成相同的时间伸手八个url,并非三个个伸手,那仿佛于linux系统中一个进度开多条线程试行的功能。下边是使用curl_multi实现四线程爬虫的示范:

    $mh = curl_multi_init(); //返回一个新cURL批处理句柄
    for ($i = 0; $i < $max_size; $i++)
    {
        $ch = curl_init();  //初始化单个cURL会话
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_URL, 'http://www.zhihu.com/people/' . $user_list[$i] . '/about');
        curl_setopt($ch, CURLOPT_COOKIE, self::$user_cookie);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        $requestMap[$i] = $ch;
        curl_multi_add_handle($mh, $ch);  //向curl批处理会话中添加单独的curl句柄
    }

    $user_arr = array();
    do {
                    //运行当前 cURL 句柄的子连接
        while (($cme = curl_multi_exec($mh, $active)) == CURLM_CALL_MULTI_PERFORM);

        if ($cme != CURLM_OK) {break;}
                    //获取当前解析的cURL的相关传输信息
        while ($done = curl_multi_info_read($mh))
        {
            $info = curl_getinfo($done['handle']);
            $tmp_result = curl_multi_getcontent($done['handle']);
            $error = curl_error($done['handle']);

            $user_arr[] = array_values(getUserInfo($tmp_result));

            //保证同时有$max_size个请求在处理
            if ($i < sizeof($user_list) && isset($user_list[$i]) && $i < count($user_list))
            {
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_HEADER, 0);
                curl_setopt($ch, CURLOPT_URL, 'http://www.zhihu.com/people/' . $user_list[$i] . '/about');
                curl_setopt($ch, CURLOPT_COOKIE, self::$user_cookie);
                curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36');
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
                $requestMap[$i] = $ch;
                curl_multi_add_handle($mh, $ch);

                $i++;
            }

            curl_multi_remove_handle($mh, $done['handle']);
        }

        if ($active)
            curl_multi_select($mh, 10);
    } while ($active);

    curl_multi_close($mh);
    return $user_arr;

HTTP 429 Too Many Requests

使用curl_multi函数能够况兼发三个需要,可是在施行进度中使同有时间发200个央浼的时候,发现大多号令不可能重回了,即开掘了丢包的情事。进一层解析,使用 curl_getinfo 函数打字与印刷每个哀告句柄消息,该函数重临贰个分包HTTP response新闻的关联数组,个中有一个字段是http_code,表示诉求再次来到的HTTP状态码。看见有为数不菲个央浼的http_code都以429,那个重回码的情致是殡葬太多央浼了。笔者猜是天涯论坛做了防爬虫的卫戍,于是自个儿就拿任何的网址来做测量试验,开采叁回性发200个央浼时没问题的,申明了本身的疑惑,博客园在这里地点做了防备,即贰遍性的央求数量是有约束的。于是本身不断地压缩央求数量,发以往5的时候就从不丢包意况了。表达在这里个顺序里贰回性最四只可以发5个央求,纵然少之甚少,但那也是叁回小提高了。

行使Redis保存已经访谈过的客商

抓取客商的进度中,开采成点顾客是早就访谈过的,而且他的关心者和关心了的顾客都早就获取过了,即便在数据库的层面做了再也数据的管理,但是程序依然会使用curl发乞请,那样重复的出殡和下葬乞请就有成百上千再一次的网络开垦。还会有叁个就是待抓取的客商需求临时保留在贰个地点以便下叁遍推行,刚开端是松开数组里面,后来发觉要在前后相继里添扩充种经营过,在多进程编制程序里,子进度会共享程序代码、函数库,可是经过使用的变量与别的进度所运用的完全分歧。不一样进程之间的变量是分离的,无法被其余进度读取,所以是不可能采纳数组的。因而就想到了使用Redis缓存来保存已经管理好的顾客以至待抓取的顾客。那样每一回实行完的时候都把顾客push到贰个already_request_queue队列中,把待抓取的客商(即每一种客商的关心者和关切了的顾客列表)push到request_queue里面,然后每一回实践前都从request_queue里pop多个客商,然后判别是否在already_request_queue里面,假设在,则实行下一个,不然就继续推行。

在PHP中使用redis示例:

<?php
    $redis = new Redis();
    $redis->connect('127.0.0.1', '6379');
    $redis->set('tmp', 'value');
    if ($redis->exists('tmp'))
    {
        echo $redis->get('tmp') . "n";
    }

行使PHP的pcntl扩大完毕多进度

改用了curl_multi函数完成多线程抓取客商音信之后,程序运维了贰个晚间,最后收获的数量有10W。还无法达到和睦的上佳对象,于是便三番两次优化,后来发觉php里面有一个pcntl扩充能够达成多进度编制程序。上面是多编制程序编程的演示:

//PHP多进程demo
//fork10个进程
for ($i = 0; $i < 10; $i++) {
    $pid = pcntl_fork();
    if ($pid == -1) {
        echo "Could not fork!n";
        exit(1);
    }
    if (!$pid) {
        echo "child process $i runningn";
        //子进程执行完毕之后就退出,以免继续fork出新的子进程
        exit($i);
    }
}

//等待子进程执行完毕,避免出现僵尸进程
while (pcntl_waitpid(0, $status) != -1) {
    $status = pcntl_wexitstatus($status);
    echo "Child $status completedn";
}

在Linux下查看系统的cpu音信

达成了多进度编制程序之后,就想着多开几条长河不断地抓取客商的多少,后来开了8调进度跑了多少个夜间后意识只可以得到20W的数据,未有多大的升迁。于是查阅资料发掘,根据系统优化的CPU质量调优,程序的最大进度数不能够忽视给的,要依据CPU的核数和来给,最大进度数最棒是cpu核数的2倍。因而须要查阅cpu的音讯来拜望cpu的核数。在Linux下查看cpu的音讯的一声令下:

cat /proc/cpuinfo

结果如下:

图片 7

在那之中,model name表示cpu类型音讯,cpu cores表示cpu核数。这里的核数是1,因为是在设想机下运维,分配到的cpu核数比比较少,因而必须要开2条长河。最终的结果是,用了八个星期天就抓取了110万的顾客数据。

多进度编制程序中Redis和MySQL连接难题

在多进度条件下,程序运营了一段时间后,开采数目无法插入到数据库,会报mysql too many connections的大谬不然,redis也是那样。

下边这段代码会实行停业:

<?php
     for ($i = 0; $i < 10; $i++) {
          $pid = pcntl_fork();
          if ($pid == -1) {
               echo "Could not fork!n";
               exit(1);
          }
          if (!$pid) {
               $redis = PRedis::getInstance();
               // do something     
               exit;
          }
     }

根本原因是在相继子进度创制时,就已经三番两次了父进度一份别无二致的正片。对象能够拷贝,不过已创立的连续几天不可能被拷贝成多个,由此产生的结果,正是逐条进度都施用同二个redis连接,各干各的事,最后爆发不堪虚构的冲突。

斩草除根办法: >程序无法一心保障在fork进度早前,父进程不会创制redis连接实例。由此,要化解那个标题只可以靠子进度本人了。试想一下,若是在子进度中获取的实例只与当前经过有关,那么这么些标题就不设有了。于是建设方案就是有一些更动一下redis类实例化的静态情势,与这两天路程ID绑定起来。

校正后的代码如下:

<?php
     public static function getInstance() {
          static $instances = array();
          $key = getmypid();//获取当前进程ID
          if ($empty($instances[$key])) {
               $inctances[$key] = new self();
          }

          return $instances[$key];
     }

PHP总括脚本试行时间

因为想精晓各种进程开销的年华是微微,因而写个函数总括脚本实践时间:

function microtime_float()
{
     list($u_sec, $sec) = explode(' ', microtime());
     return (floatval($u_sec) + floatval($sec));
}

$start_time = microtime_float();

//do something
usleep(100);

$end_time = microtime_float();
$total_time = $end_time - $start_time;

$time_cost = sprintf("%.10f", $total_time);

echo "program cost total " . $time_cost . "sn";

若文中有不科学的地点,望各位提议以便改进。

代码托管地址:

上一篇:付费投稿陈设
下一篇:没有了