在互联网上流传着这么一篇文章《我用爬虫一天时间“偷了”知乎一百万用户,只为证明PHP是世界上最好的语言》,不知道知乎的网站管理人员怎么想的,万一给他的领导知道了……
看了他的源码,发现worker.php不就是简化版的workerman框架么,派生出子进程用于采集。
然后我在他的源码包里找到了phpQuery.php,可是没找到调用的地方,倒是在cls_query.php和user.php找到了一大堆正则表达式,我只能说人才啊,放着这么好的工具不用,非要自己用正则实现。
最厉害的是rolling_curl.php中的实现方法,功能全,支持多线程请求。
数据库方面使用了mysql和redis缓存,一天能扒这么多用户说明性能上不是问题。
感慨:
站在知乎的角度:互联网其实充满了机器人,只要有网页,都是公开的,即使是登陆后才能查看,也会暴露给登陆过的机器人。最简单的办法就是抗DDOS自动封IP,cdn厂商都提供这个功能,另外nginx有限速和屏蔽IP的模块,虽然这些办法无法封锁采集,但也能拖慢采集速度以减小损失。
站在旁观者的角度:代码很厉害啊,几个类收下了,有时间可以上上知乎,很良心的网站,不要拿我们的用户数据做坏事。采集开慢点,不能采集太快,这样影响别人访问网站的速度,采集过度就是攻击行为,被封IP也是活该!
给采集者的一点建议:phpQuery.php真心不错,比自己写的正则表达式要容易太多了,万一采集的网页格式发生变化了改起来很麻烦,正则表达式的成本太高。
刚学PHP的时候用的是火车头采集软件,通过界面设置可以获取到一堆采集的数据,处理起来并不轻松。自从认识phpQuery,再也不用火车头了,缺点是占用内存有点高,用完释放内存会好很多。现在作为一名雇员,不得不从别人的网站采集数据,用到phpQuery的地方太多了。
PS:虽然phpQuery很方便也很强大,但是有些情况下不能正确解析某个页面,我猜那篇偷知乎数据的作者可能是遇到了这样的情况,后来我又掌握了一门新的框架《phpQuery和simple_html_dom DOM解析器对比》,操作是复杂了些但是对页面的兼容性要强于phpQuery。再后来,使用nodejs来分析页面更方便了些,毕竟nodejs才是真正的爬虫王者。
另外,这里贴3个函数代码,可以方便处理采集来的数据。
第1个是str_replace的单次替换版本,PHP自带的str_replace只能全部替换://仅替换一次
function str_replace_once($needle, $replace, $haystack) {
// Looks for the first occurence of $needle in $haystack
// and replaces it with $replace.
$pos = strpos($haystack, $needle);
if ($pos === false) {
return $haystack;
}
return substr_replace($haystack, $replace, $pos, strlen($needle));
}
第2个是截取函数,在火车头采集软件中有类似的功能,可以去掉左侧字符和右侧字符,如果找不到左侧和右侧字符则默认不截取。/**
* 用于字符串截取,用于截取2个字符串之间的内容,不含边界
* @param string $haystack
* @param string $left
* @param string $right
* @return string
*/
function subByString($haystack, $left = '', $right = '')
{
$left_pos = false;
$right_pos = false;
if($left == '' || ($left_pos = strpos($haystack, $left)) === false)
{
$start_pos = 0;
}else{
$start_pos = $left_pos;
}
$right_data = substr($haystack, $start_pos + ($left_pos === false ? 0 : strlen($left)));
if($right == '' || ($right_pos = strpos($right_data, $right)) === false)
{
return substr($haystack, $left_pos === false ? 0 : $left_pos+strlen($left));
}else{
$end_pos = $start_pos + $right_pos;
return substr($haystack, $left_pos === false ? 0 : $left_pos+strlen($left), $end_pos-$start_pos);
}
}
第3个是逆向截取函数,依赖上面的subByString,用于逆向截取,实现更变态的功能://逆向截取
function get_middle_reverse($haystack, $left = '', $right = '')
{
$revhtml = strrev($haystack);
$revsuffix = strrev($right);
$revpreffix = strrev($left);
$middle = subByString($revhtml, $revsuffix, $revpreffix);
$result = strrev($middle);
return $result;
}
subByString是一个非常赞的截取函数,只需要传入完整的字符串,左侧字符串(如果没查找到则置为空),右侧字符串(如果没查找到则置为空)进行截取,适合任意编码的字符串(前提是你传入的字符串参数编码一致)。例如:<?php
header('Content-type:text/html;charset=utf-8');
$str = '我是“ABC”长度为3个letter';
//正常截取,左侧和右侧均能查找到
print_r('
');
print_r(subByString($str, '“ABC”长', '3个l'));
print_r('
');print_r('
');
//非正常截取,不传后2个参数则原字符串返回
print_r(subByString($str));
print_r('
');
print_r('
');//只传入左侧字符,右侧全部返回
print_r(subByString($str, '“ABC”长'));
print_r('
');
print_r('
');//只传入右侧字符,左侧全部返回
print_r(subByString($str, '', '3个l'));
print_r('
');get_middle_reverse是一个更赞的截取函数,可以实现逆向截取。例如:<?php
header('Content-type:text/html;charset=utf-8');
$str = '我是“ABC”长度为3个letter';
print_r('
');
//截取字母l和t之间的字符e,第三个参数定位的是靠左侧的t
print_r(subByString($str, 'l', 't'));
print_r('
');print_r('
');
//如果我们在截取过程中需要保证右侧的t是最后一个t则需要用到逆向截取
//打印出et,第三个参数定位的是最右侧的t
print_r(get_middle_reverse($str, 'l', 't'));
print_r('
');逆向截取在网页采集中非常管用,因为你需要的数据可能在标识字符串(网页中唯一字符串)的左侧,例如:<?php
header('Content-type:text/html;charset=utf-8');
//最后一页的超链接,如何才能获取30这个数字
$str = 'lastPage';
//使用逆向截取,将标识字符串功能转移到第三个参数
print_r('
');
//">lastPage是仅有一次的标识字符串,而page=却多次出现
//打印出30
print_r(get_middle_reverse($str, 'page=', '">lastPage'));
print_r('
');注意:这里的逆向截取依赖strrev函数,我们知道strrev函数仅支持英文字符和英文标点,如果需要中文字符逆向截取请看后面的中文修复方案:
随便收藏,请保留本文地址!