当前位置: 首页 > 科技观察

PHP开发:使用PHP抓取百万知乎用户和知识点笔记

时间:2023-03-12 00:02:27 科技观察

代码托管地址:https://github.com/hhqcontinue/zhihuSpider开发前准备安装Linux系统(Ubuntu14.04),在VMWare虚拟机中安装机器下的Ubuntu;安装PHP5.6或以上版本;安装curl和pcntl扩展。使用PHP的curl扩展获取页面数据PHP的curl扩展是PHP支持的一个库,它允许您使用各种类型的协议与各种服务器进行连接和通信。这个程序就是抓取知乎的用户数据。为了能够访问用户的个人页面,用户需要登录才能访问它。当我们在浏览器页面点击用户头像链接进入用户个人中心页面时,之所以能看到用户信息,是因为点击链接时,浏览器帮您带上了本地cookie,一起提交。到新的页面,这样就可以进入用户的个人中心页面了。因此,在访问个人页面之前,需要先获取用户的cookie信息,然后在每次curl请求时带上cookie信息。在获取cookie信息方面,我使用的是自己的cookie,在页面上可以看到自己的cookie信息:一个一个复制,组成cookie字符串,格式为“__utma=?;__utmb=?;”.然后可以使用cookie字符串发送请求。初始示例:$url='http://www.zhihu.com/people/mora-hu/about';//其中mora-hu代表用户ID$ch=curl_init($url);//初始化sessioncurl_setopt($ch,CURLOPT_HEADER,0);curl_setopt($ch,CURLOPT_COOKIE,$this->config_arr['user_cookie']);//设置请求COOKIEcurl_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。使用正则表达式获取到图片链接后,再发送一个请求,此时带上图片请求的来源,说明请求是从知乎网站转发过来的。具体例子如下:}if(empty($url)){return'';}$context_options=array('http'=>array('header'=>"Referer:http://www.zhihu.com"//带上refererparameter  )  );$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';}获取个人信息后,您需要访问该用户的关注者和关注用户列表,获取更多用户信息。然后逐层访问。可以看到,在个人中心页面,有如下两个链接:这里有两个链接,一个是followed,一个是followers,以“followed”链接为例。使用正则匹配匹配对应的链接,得到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)添加唯一索引,使用INSERTINTO...ONDUPLICATEKEYUPDATE...3)添加唯一索引,插入时使用INSERTINGNOREINTO。..4)添加唯一索引,插入时使用REPLACEINTO...使用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(WindowsNT6.1;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/44.0.2403.130Safari/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($iconnect('127.0.0.1','6379');$redis->set('tmp','value');if($redis->exists('tmp')){echo$redis->get('tmp')."\n";}使用PHP的pcntl扩展实现多进程,使用curl_multi函数实现多线程抓取users收到信息后,程序跑了一晚上,终于拿到了10W的数据。还是没能达到理想中的目标,于是继续优化,后来发现PHP中有一个pcntl扩展可以实现多进程编程。下面是一个多进程编程的例子://PHP多进程demo//fork10个进程for($i=0;$i<10;$i++){$pid=pcntl_fork();if($pid==-1){echo"Couldnotfork!\n";exit(1);}if(!$pid){echo"childprocess$irunning\n";//子进程执行完后退出,以免继续fork一个新的子进程exit($i);}}//等待子进程完成以避免僵尸进程while(pcntl_waitpid(0,$status)!=-1){$status=pcntl_wexitstatus($status);echo"Child$statuscompleted\n";}在linux下查看了系统的cpu信息,实现了多进程编程后,想着多开几个进程,持续抓取用户数据。到20W的数据,也没有太大的提升。于是查了资料,发现根据系统优化的CPU性能调优,程序的最大进程数不能随便给,应该根据CPU核数之和来给。最大进程数最好是CPU核数的两倍。所以需要查看cpu的信息,看cpu的核数。Linux下查看cpu信息的命令:cat/proc/cpuinfo其中modelname表示cpu类型信息,cpucores表示cpu核数。这里的核心数是1,因为是在虚拟机下运行,分配的CPU核心数比较少,所以只能开2个进程。最终结果是一个周末就抓取了110万用户数据。多进程编程Redis和MySQL连接问题多进程情况下,程序运行一段时间后,发现数据无法插入数据库,会报mysqltoomanyconnections的错误,redis也是如此。以下代码将失败: