奥门新浦京官方网站PHP爬虫:百万级别知乎用户数据爬取与分析

此次抓取了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";

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

代码托管地址:

发表评论

电子邮件地址不会被公开。 必填项已用*标注