网站爬虫预警及Redis漏斗添加反爬虫机制

897 阅读4分钟

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

前言

作为一个长期从事seo工作的开发者,网站的原创资源就是我们的资本,但很难避免被其他爬虫程序,几个小时之内全部爬走,与爬虫之间的博弈也就变成了一场持久的拉锯战。在长期的博弈中,也总结了一个比较实用的反爬虫方法,虽然不能保证防住百分之百的爬虫程序,但也能防住大部分,或者增加爬虫者的采集成本。

爬虫程序识别

识别爬虫程序最主要的标识有两个:

1、user-agent,用户头信息,正常的用户访问都会带有浏览器信息,例如

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36

爬虫程序在请求的时候,如果没有刻意伪造,会带上所使用的语言标识,例如python request请求头信息为:

python-requests/2.25.1

PHP curl请求,如果php.ini配置文件中的user_agent打开的话会为: PHP,没打开会没有user-agent

user-agent的伪造应该是最简单也是0成本的,所以除非是特别明显的标识直接拦截,一般不根据这个去做限制。

2、ip,用户访问都会携带用户ip,我们可以根据同一个ip在短时间内访问的次数去判断是不是爬虫程序,当然ip也可以使用代理ip进行伪造。

网站爬虫预警

主要利用kafka高并发和稳定的特性,在不影响业务的前提下,实现访问量的统计。用redis的有序列表,统计出指定时间段内访问量最高的ip,达到预警的目的。 以PHP网站为例

image.png 1、在网站程序的入口处判断用户IP是否在违禁IP的Redis集合里

$ip = Tools::getClientIp();
$res = $redis->get('ip:f:' . $ip);
if ($res) {
    return false;
}

Tools::getClientIp()的代码为:

public static function getClientIp()
{
    $ip = '';
    if (getenv('HTTP_CLIENT_IP')) {
        $ip = getenv('HTTP_CLIENT_IP');
    } else if (getenv('HTTP_X_FORWARDED_FOR')) {
        $ip = getenv('HTTP_X_FORWARDED_FOR');
        $ips = explode(',', $ip);
        $ip = $ips[0];
    } else if (getenv('REMOTE_ADDR')) {
        $ip = getenv('REMOTE_ADDR');
    }
    return $ip;
}

2、将合法的ip推送到kafka消息队列。不用kafka也可以,可以使用其他的消息队列,例如RabbitMq,但由于是网站入口,并发量比较大,kafka在稳定性和处理大并发量上具有更大的优势,所以选择了kafka。

3、在kafka消费者程序中,将接收到的用户ip推到redis以小时为单位的有序集合中,这一步主要是为了统计每个时间段,访问量最高的ip,通过人为观察或者脚本定时统计,封禁不合法的ip或者发邮件提醒。

// 集合key
$key = 'list:' . date('YmdH');
$redis->zincrby($key, 1, $ip);

接下来使用redis漏斗添加限制不正常ip的访问速率。

Redis漏斗添加反爬虫机制

Redis4.0之后提供了一个限流模块:redis-cell,该模块使用漏斗算法并提供原子性的限流指令。 这个模块需要单独安装,具体安装方法可参照网上的一些安装方法,这里就不细说了。 说一下这个模块的参数

> cl.throttle 127.0.0.1 14 30 60 1
1) (integer) 0    # 0表示允许,1表示拒绝
2) (integer) 15    # 漏斗容量capacity
3) (integer) 14    # 漏斗剩余空间left_quota
4) (integer) -1    # 如果拒绝了,需要多长时间后再重试,单位秒
5) (integer) 2    # 多长时间后,漏斗完全空出来,单位秒

该指令意思为,允许127.0.0.1的ip 行为的频率为每60s最多30次,漏斗初始容量为15(因为是从0开始计数,到14为15个),默认每个行为占据的空间为1(可选参数)。 如果被拒绝,取返回数组的第四个值进行sleep即可作为重试时间,也可以异步定时任务来重试。

$res = $redis->executeRaw(['cl.throttle', $ip, 14, 30, 60]);
if ($res[0]) {
    $redis->setex('ip:f:' . $ip, $res[3], 1);
}

总结

1、每天各个时间段ip访问次数的有序集合数据,访问量最高的数据应定期写入到数据库,并定期删除。

2、ip漏斗限流是个非常危险的操作,还是应该经常观察访问日志找出爬虫程序的规律,确定合理的限流参数。