推荐设备MORE

微信抽奖微信小程序—青年大

微信抽奖微信小程序—青年大

行业新闻

php爬虫:百万级別知乎客户数据信息抓取与剖析

日期:2021-03-14
我要分享

php爬虫:百万级別知乎客户数据信息抓取与剖析


短视頻,自新闻媒体,达人种草1站服务

编码代管详细地址:

文/Hector

这次抓取了110万的客户数据信息,数据信息剖析結果以下:

 

开发设计前的提前准备

安裝linux系统软件(Ubuntu14.04),在VMWare虚似机下安裝1个Ubuntu;

安裝PHP5.6或以上版本号;

安裝curl、ptl拓展。

应用PHP的curl拓展抓取网页页面数据信息

PHP的curl拓展是PHP适用的容许你与各种各样服务器应用各种各样种类的协议书开展联接和通讯的库。

本程序流程是抓取知乎的客户数据信息,要能浏览客户本人网页页面,必须客户登陆后的才可以浏览。当大家在访问器的网页页面中点一下1个客户头像连接进到客户本人管理中心网页页面的情况下,之因此可以看到客户的信息内容,是由于在点一下连接的情况下,访问器帮你将当地的cookie带上1齐递交到新的网页页面,因此你就可以进到到客户的本人管理中心网页页面。因而完成浏览本人网页页面以前必须先得到客户的cookie信息内容,随后在每次curl恳求的情况下带上cookie信息内容。在获得cookie信息内容层面,我是用了自身的cookie,在网页页面中能够看到自身的cookie信息内容:

 

1个个地拷贝,以"__utma=?;__utmb=?;"这样的方式构成1个cookie标识符串。接下来便可以应用该cookie标识符串来推送恳求。

原始的示例:


 $url = ''; 
//此处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客户的本人管理中心网页页面。运用该結果再应用正则表达式表述式对网页页面开展解决,就可以获得到名字,性別等所必须抓取的信息内容。

照片防盗链

在对回到結果开展正则表达式解决后輸出本人信息内容的情况下,发如今网页页面中輸出客户头像时没法开启。历经查阅材料获知,是由于知乎对照片做了防盗链解决。处理计划方案便是恳求照片的情况下在恳求头里仿冒1个referer。

在应用正则表达式表述式获得到照片的连接以后,再发1次恳求,这时候候带上照片恳求的来源于,表明该恳求来自知乎网站的转发。实际事例以下:

function getImg($url, $u_id){ 
if (file_exists('./images/' . $u_id . ".jpg"))
{
return "images/$u_id" . '.jpg'; } if (empty($url))
{
return '';
}
$context_options = array(
'' = array(
'header' = "Referer:"//带上referer主要参数 )
);
$context = stream_context_create($context_options);
$img = file_get_contents(':' . $url, FALSE, $context);
file_put_contents('./images/' . $u_id . ".jpg", $img);
return "images/$u_id" . '.jpg';}

抓取更多客户

抓取了自身的本人信息内容后,就必须再浏览客户的关心者和关心了的客户目录获得更多的客户信息内容。随后1层1层地浏览。能够看到,在本人管理中心网页页面里,有两个连接以下:

 

这里有两个连接,1个是关心了,另外一个是关心者,以 关心了 的连接为例。用正则表达式配对去配对到相应的连接,获得url以后用curl带上cookie再发1次恳求。抓取到客户关心了的用于目录页以后,能够获得下面的网页页面:

 

剖析网页页面的html构造,由于要是获得客户的信息内容,因此只必须框住的这1块的div內容,客户名都在这里边。能够看到,客户关心了的网页页面的url是:

 

不一样的客户的这个url基本上是1样的,不一样的地区就在于客户名那里。用正则表达式配对拿到客户名目录,1个1个地拼url,随后再逐一发恳求(自然,1个1个是较为慢的,下面有处理计划方案,这个稍后会说到)。进到到新客户的网页页面以后,再反复上面的流程,就这样持续循环系统,直至做到你所要的数据信息量。

linux统计分析文档数量

脚本制作跑了1段時间后,必须看看到底获得了是多少照片,当数据信息量较为大的情况下,开启文档夹查询照片数量就有点慢。脚本制作是在linux自然环境下运作的,因而可使用linux的指令来统计分析文档数量:

在其中,ls -l是长目录輸出该文件目录下的文档信息内容(这里的文档能够是文件目录、连接、机器设备文档等);grep "^-"过虑长目录輸出信息内容,"^-" 只保存1般文档,假如只保存文件目录是"^d";wc -l是统计分析輸出信息内容的行数。下面是1个运作示例:

 

插进MySQL时反复数据信息的解决

程序流程运作了1段時间后,发现有许多客户的数据信息是反复的,因而必须在插进反复客户数据信息的情况下做解决。解决计划方案以下:

1)插进数据信息库以前查验数据信息是不是早已存在数据信息库;

2)加上唯1数据库索引,插进时应用INSERT INTO ... ON DUPliCATE KEY UPDATE...

3)加上唯1数据库索引,插进时应用INSERT INGNORE INTO...

4)加上唯1数据库索引,插进时应用REPLACE INTO...

应用curl_multi完成I/O复用抓取网页页面

一开始单过程并且单独curl去抓取数据信息,速率很慢,挂机爬了1个夜里只能抓到2W的数据信息,因而便想起能不可以在进到新的客户网页页面发curl恳求的情况下1次性恳求好几个客户,后来发现了curl_multi这个好物品。curl_multi这类涵数能够完成另外恳求好几个url,而并不是1个个恳求,这是1种I/O复用的体制。下面是应用curl_multi爬虫的示例:


 $mh = curl_multi_init(); //回到1个新cURL批解决句柄 for ($i = 0; $i $max_size; $i++)
{
$ch = curl_init(); //原始化单独cURL对话 curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_URL, '' . $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, '' . $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个恳求的情况下,发现许多恳求没法回到了,即发现了丢包的状况。进1步剖析,应用curl_getinfo涵数复印每一个恳求句柄信息内容,该涵数回到1个包括HTTP response信息内容的关系数字能量数组,在其中有1个字段是_code,表明恳求回到的HTTP情况码。看到有许多个恳求的_code全是429,这个回到码的意思是推送太多恳求了。我猜是知乎做了防爬虫的安全防护,因而我就拿别的的网站来做检测,发现1次性发200个恳求时没难题的,证实了我的猜想,知乎在这层面做了安全防护,即1次性的恳求数量是比较有限制的。因而我持续地降低恳求数量,发如今5的情况下就沒有丢包状况了。表明在这个程序流程里1次性数最多只能发5个恳求,尽管很少,但这也是1次小提高了。

应用Redis储存早已浏览过的客户

抓取客户的全过程中,发现一些客户是早已浏览过的,并且他的关心者和关心了的客户都早已获得过了,尽管在数据信息库的层面做了反复数据信息的解决,可是程序流程還是会应用curl发恳求,这样反复的推送恳求就有许多反复的互联网花销。也有1个便是待抓取的客户必须临时储存在1个地区便于下1次实行,一开始是放到数字能量数组里边,后来发现要在程序流程里加上多过程,在多过程程序编写里,子过程会共享资源程序流程编码、涵数库,可是过程应用的自变量与别的过程所应用的迥然不一样。不一样过程之间的自变量是分离出来的,不可以被别的过程载入,因此是不可以应用数字能量数组的。因而就想起了应用Redis缓存文件来储存早已解决好的客户和待抓取的客户。这样每次实行完的情况下都把客户push到1个already_request_queue序列中,把待抓取的客户(即每一个客户的关心者和关心了的客户目录)push到request_queue里边,随后每次实行前都从request_queue里pop1个客户,随后分辨是不是在already_request_queue里边,假如在,则开展下1个,不然就再次实行。

在PHP中应用redis示例:


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

应用PHP的ptl拓展完成多过程

改用了curl_multi涵数完成线程同步抓取客户信息内容以后,程序流程运作了1个夜里,最后获得的数据信息有10W。还不可以做到自身的理想化总体目标,因而便再次提升,后来发现php里边有1个ptl拓展能够完成多过程程序编写。下面是多程序编写程序编写的示例:


 //PHP多过程demo //fork10个过程 for ($i = 0; $i $i++) {
$pid = ptl_fork();
if ($pid == ⑴) {
echo "Could not fork!\n";
exit(1);
}
if (!$pid) {
echo "child process $i running\n";
//子过程实行结束以后就撤出,以防再次fork出新的子过程 exit($i);
}
}
//等候子过程实行结束,防止出現僵尸过程 while (ptl_waitpid(0, $status) != ⑴) {
$status = ptl_wexitstatus($status);
echo "Child $status pleted\n";
}

在linux下查询系统软件的cpu信息内容

完成了多过程程序编写以后,就想着多开几条过程持续地抓取客户的数据信息,后来开了8调过程跑了1个夜里后发现只能拿到20W的数据信息,沒有多大的提高。因而查阅材料发现,依据系统软件提升的CPU特性调优,程序流程的最大过程数不可以随意给的,要依据CPU的核数和来给,最大过程数最好是是cpu核数的2倍。因而必须查询cpu的信息内容看来看cpu的核数。在linux下查询cpu的信息内容的指令:

 

在其中,model name表明cpu种类信息内容,cpu cores表明cpu核数。这里的核数是1,由于是在虚似机下运作,分派到的cpu核数较为少,因而只能开2条过程。最后的結果是,用了1个周末就抓取了110万的客户数据信息。

多过程程序编写中Redis和MySQL联接难题

在多过程标准下,程序流程运作了1段時间后,发现数据信息不可以插进到数据信息库,会报mysql too many connections的不正确,redis也是这般。

下面这段编码会实行不成功:

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

压根缘故是在各个子过程建立时,就早已承继了父过程1份彻底1样的复制。目标能够复制,可是已建立的联接不可以被复制成好几个,由此造成的結果,便是各个过程都应用同1个redis联接,各干各的事,最后造成无缘无故的矛盾。

处理方式:

程序流程不可以彻底确保在fork过程以前,父过程不容易建立redis联接案例。因而,要处理这个难题只能靠子过程自身了。试想1下,假如在子过程中获得的案例只与当今过程有关,那末这个难题就不存在了。因而处理计划方案便是略微更新改造1下redis类案例化的静态数据方法,与当今过程ID关联起来。

更新改造后的编码以下:


 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 . "s\n";