首页 电脑 电脑学堂 查看内容

PHP爬虫:百万级别知乎用户数据爬取与分析

2016-1-21 12:06 2543 0

摘要: 这次抓取了110万的用户数据,数据分析结果如下:开发前的准备安装Linux系统(Ubuntu14.04),在VMWare虚拟机下安装一个Ubuntu;安装PHP5.6或以上版本;安装MySQL5.5或以上版本;安装curl、pcntl扩展。使用PHP的curl ...
关键词: nbsp curl 用户 页面 数据 一个 信息 cookie CURLOPT setopt

这次抓取了110万的用户数据,数据分析结果如下:

开发前的准备

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

安装PHP5.6或以上版本;

安装MySQL5.5或以上版本;

安装curl、pcntl扩展。

使用PHP的curl扩展抓取页面数据

PHP的curl扩展是PHP支持的允许你与各种服务器使用各种类型的协议进行连接和通信的库。

本程序是抓取知乎的用户数据,要能访问用户个人页面,需要用户登录后的才能访问。当我们在浏览器的页面中点击一个用户头像链接进入用户个人中心页面的时候,之所以能够看到用户的信息,是因为在点击链接的时候,浏览器帮你将本地的cookie带上一齐提交到新的页面,所以你就能进入到用户的个人中心页面。因此实现访问个人页面之前需要先获得用户的cookie信息,然后在每次curl请求的时候带上cookie信息。在获取cookie信息方面,我是用了自己的cookie,在页面中可以看到自己的cookie信息:

一个个地复制,以”__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' ;
}

爬取更多用户

抓取了自己的个人信息后,就需要再访问用户的关注者和关注了的用户列表获取更多的用户信息。然后一层一层地访问。可以看到,在个人中心页面里,有两个链接如下:

这里有两个链接,一个是关注了,另一个是关注者,以“关注了”的链接为例。用正则匹配去匹配到相应的链接,得到url之后用curl带上cookie再发一次请求。抓取到用户关注了的用于列表页之后,可以得到下面的页面:

分析页面的html结构,因为只要得到用户的信息,所以只需要框住的这一块的div内容,用户名都在这里面。可以看到,用户关注了的页面的url是:

不同的用户的这个url几乎是一样的,不同的地方就在于用户名那里。用正则匹配拿到用户名列表,一个一个地拼url,然后再逐个发请求(当然,一个一个是比较慢的,下面有解决方案,这个稍后会说到)。进入到新用户的页面之后,再重复上面的步骤,就这样不断循环,直到达到你所要的数据量。

Linux统计文件数量

脚本跑了一段时间后,需要看看究竟获取了多少图片,当数据量比较大的时候,打开文件夹查看图片数量就有点慢。脚本是在Linux环境下运行的,因此可以使用Linux的命令来统计文件数量:

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

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

插入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 
声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

最新评论

返回顶部