多思多金

爬虫与反爬虫的较量:如何与恶意爬虫做斗争?

这篇文章我将归类到技术文档里,早前接触过爬虫因为我并不是什么技术栈,所以写的东西都比较粗陋,以前用过别人的爬虫拿来简单的练手,但是一般都会去人家网站点击几个广告,毕竟爬了人家的站,大家都是站长,明白这中间的不容易,因为网上拿来的爬虫大多数是别人写好的,我懂得不多,所以只是简单的测试玩玩,现在我手里好多网站都是基于Typecho开发或者二开,最近在你好污啊进行了扩展,新增了模块心灵毒鸡汤,为了引流就在V2ex发布了一下,然而从发布到本文起稿时,网站已经瘫痪,没有猜错,是有人在用爬虫不断的请求,导致资源被榨干,一直抛出502状态,因此导致新访客也无法访问。

那么如何区分访问者与恶意爬虫呢?我们分析上图可以看到首先对方频率很高,每秒执行的请求次数过高,其次每次请求头Header都没做处理显示的是 Python/3.6 这个只是本站的爬虫,大多数时候Header并不能作为判断依据,因为这个很容易伪造,再次我们看访问响应,可以看到该请求因为频率过高导致服务器瘫痪,其爬虫本身就收到无数的502状态,但是还在访问,这么基本可以断定不是访问者了,正常访问者在请求响应是502的状态看到的只是错误页面,最多刷新几次就会跳出,而这里的因为是爬虫,所以即使出错也会一直请求,当然这是一只不太人性化的爬虫,如果通过机器学习完全模拟人工频率那我们也就很难来控制了,但是爬虫到那个地步对网站影响已经不大了。

因为心灵毒鸡汤上线时间短,中途我休息了2个小时左右,才让爬虫有机可乘,我向来不反对大家对于心灵毒鸡汤包括你好污啊主站的爬取,本来作为公开的信息,在互联网化的环境,人人可取,但是这个爬虫比较恶劣,不做频率处理,直接导致网站崩溃,所以我直接封禁了IP。

使用命令查看日志中访问次数最多50个ip,然后对访问次数超常的IP采取措施,查看访问次数最多的IP的命令如下:

awk '{print $1}' /home/wwwlogs/www.nihaowua.com.log | sort|uniq -c | sort -nr | head -n 50

我们大多数人用的都是LNMP环境,那么Nginx服务器如何屏蔽指定IP呢,这里以我本地军哥LNMP为例,首先进入到 /usr/local/nginx/conf 目录,在该目录下新增 blockip.conf 配置文件,在 blockip.conf 中写入需要封禁的IP,写法如下:

deny 118.24.19.90; 
deny 61.190.32.52;

#nginx封禁IP段
deny 123.0.0.0/8;# 屏蔽整个段即从123.0.0.1到123.255.255.254访问的命令
deny 124.45.0.0/16;# 屏蔽IP段即从123.45.0.1到123.45.255.254访问的命令
deny 123.45.6.0/24;# 屏蔽IP段即从123.45.6.1到123.45.6.254访问的命令

deny表示屏蔽该地址,后面直接跟上你要屏蔽的IP即可,然后打开你的网站nginx配置文件,通常目录是 /usr/local/nginx/conf/domain.com/ 这里的domain.com是你的域名,修改该目录下的conf配置文件,在server段中写入刚才我们配置的blockip.conf,写法如下:

#IP地址屏蔽
include blockip.conf;

然后保存配置文件,并重启nginx服务,命令为 lnmp nginx restart 或者 lnmp nginx reload,至此,我们就完成了屏蔽非法IP的基本操作,后期有新的非法IP,直接写入到 blockip.conf 配置文件即可,记得每次修改 blockip.conf 文件以后都要重启 nginx 服务才能生效。

20180630更新:截止本日发现非法IP依然在请求,虽然通过Nginx屏蔽了IP,但是该IP在请求状态为403的时候依然不依不饶,看着逐渐增大的nginx日志文件,强迫症又犯了,直接在iptables里干掉了这个ip,命令操作方式如下:

iptables -I INPUT -s 211.1.0.0 -j DROP

#查看iptables命令
iptables -L -n

#解封iptables某个屏蔽的IP
iptables -D INPUT -s 211.1.0.0 -j REJECT 

也许上面的做法你觉得依然不够人性化,因为你不可能一直盯着nginx日志去观察这些非法IP,那么这时候该怎么操作呢?继续往下看。

反爬虫进阶手段:

我们通过上面的图片其实可以看出爬虫的几个基本通性就是模拟请求,模拟请求就会涉及到访问频率,频率,时长,请求头等一些因素,一个正常人工操作是不可能每秒请求网站很多次,也不可能一天24小时都在请求,他的请求头通常不应该是python,当他请求出错了以后不可能一直继续请求下去。所以我们设定一个阈值每分钟访问次数超过80次即判断为爬虫,这里的阈值需要大家根据具体情况设置,我们要做的就是写一个Nginx监控脚本,当网站请求达到阈值时我们就执行发送邮件给管理员,这样来做到对网站的自动化管理。

备注:本文操作是在日志文件比较小的情况下的操作,如果日志文件每天达到G的量级,或者百兆的量级都不建议使用本文的操作,因为日志文件太大读取起来就比较耗时了,针对日志文件较大的情况请选择专业工具,或者通过sed读取然后将日志存入数据库,程序从数据库中读取分析。

如果你们的系统比较大,做了分布式,访问量也很大,建议你使用ELK(ElasticSearch, Logstash, Kibana)搭建实时日志分析平台。

以下操作需要服务安装有Python,没有安装的请自行安装。

1、监控Nginx日志

该脚本主要用来监控Nignx产生的日志,看是否有异常IP活动,脚本如下:

[root@nonamedaohao4ma ~]# cat /home/nginx_log_monit.sh
#!/bin/bash
#日志文件
logfile=/home/wwwlogs/nginx/www.nihaowua.com.log
   
#开始时间
start_time=`date -d"$last_minutes minutes ago" +"%H:%M:%S"`
   
#结束时间
stop_time=`date +"%H:%M:%S"`
   
#过滤出单位之间内的日志并统计最高ip数
tac $logfile | awk -v st="$start_time" -v et="$stop_time" '{t=substr($4,RSTART+14,21);if(t>=st && t<=et) {print $0}}' \
| awk '{print $1}' | sort | uniq -c | sort -nr > /home/log_ip_top10
ip_top=`cat /home/log_ip_top10 | head -1 | awk '{print $1}'`
# 单位时间[1分钟]内单ip访问次数超过80次,则触发邮件报警
if [[ $ip_top -gt 80 ]];then
 /usr/bin/python /home/send_mail.py &
fi

2、python报警脚本

监控到非法IP访问即报警给管理员发送邮件

[root@nonamedaohao4ma ~]# cat /home/send_mail.py
# -*- coding: utf-8 -*-
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from datetime import datetime
import os
import smtplib
def _format_addr(s):
 name, addr = parseaddr(s)
 return formataddr((Header(name, 'utf-8').encode(), addr))
# 邮箱定义
smtp_server = 'smtp.exmail.qq.com'
smtp_port = 465
from_addr = 'xxxxx@xxxx.com'
#这里的密码是邮箱客户端专属密码不是邮箱密码
#这里的密码注意安全,最好的做法是写入系统环境变量
#写入环境变量的代码是 export EMAILPWD=xxxxxx
#调用方式改成 password = os.environ.get('EMAILPWD')
#password = 'xxxxxxxx'
password = os.environ.get('EMAILPWD')
to_addr = ['xxxx@xxxxx.com']
# 邮件对象
msg = MIMEMultipart()
msg['From'] = _format_addr('发件人 <%s>' % from_addr)
msg['To'] = _format_addr('收件人 <%s>' % to_addr)
msg['Subject'] = Header('Warning:单ip请求次数异常', 'utf-8').encode()
# 获取系统中要发送的文本内容
with open('/home/log_ip_top10', 'r') as f:
  line = f.readline().strip()
  line = line.split(" ")
print(line)
# 邮件正文是MIMEText:
html = '<html><body><h2>一分钟内单ip请求次数超过阀值</h2>' + \
 '<p>ip:%s  请求次数/min:%s</p>' % (line[1],line[0]) + \
 '</body></html>'

msg.attach(MIMEText(html, 'html', 'utf-8'))
server = smtplib.SMTP_SSL(smtp_server, smtp_port)
server.login(from_addr, password)
server.sendmail(from_addr, to_addr, msg.as_string())
server.quit()

我在昨天测试该段代码使用的QQ企业邮箱,一直报错535,后来网上咨询发现大家的解决办法是给邮箱开通安全登陆生成客户端专属密码才测试通过。另外163邮箱也是一样,需要开通安全登陆并配置客户端专属密码,上面的密码也就是专属密码。

3、脚本警报触发测试

[root@nonamedaohao4ma ~]# cat /home/curl.sh
#!/bin/bash
#example:curl.sh https://www.nihaowua.com 100
usage()
{
 echo "usage: `basename $0` url count"
}
if [ $# -ne 2 ]; then
 usage
 exit 1
fi
for i in `seq 1 $2`;do
 http_code=`curl -o /dev/null -s -w %{http_code} $1`
 echo $1 $http_code
done

手动执行测试脚本

[root@nonamedaohao4ma ~]# /bin/bash /home/curl.sh https://www.nihaowua.com 300

4、加入定时任务

因为我们本次主要是监控用户IP一分钟的活动情况,所以需要给脚本新增每分钟执行一次

[root@nonamedaohao4ma ~]# crontab -e
* * * * * /bin/bash -x /home/nginx_log_monit.sh >/dev/null 2>&1

如果测试时没有收到邮件请检查定时任务日志查看是否成功:tail -f /var/log/cron

5、测试最终结果

以上就是无人值守自动IP监控的部分代码了,截止到本文完稿时,我发现还有非法IP在不停的请求,然而他只能请求到403。

本文也仅仅是实现了邮件报警功能,实际上还可以通过直接将非法ip写到我们上面新增的 blockip.conf 文件中来实现无人值守自动屏蔽恶意访问的ip或者也可以通过Linux系统防火墙iptables来屏蔽("iptables -I INPUT -s x.x.x.x -j DROP"方式)。

昨晚查看文档发现还有另外两种做法,一种做法是通过ipset来封禁非法IP,相对来说比直接iptables要人性化一些,第二种做法是给ngin安装模块ngx_http_limit_conn_module来屏蔽非法IP请求,该模块主要用来限制每秒请求数量,至于依据什么条件限制是由我们来自定义的,具体的大家可以参考网络文章nginx限制请求数ngx_http_limit_req_module模块

后记:本文没有谈到代理池的问题,因为对方真的想爬你的站,总是会有方法的,这个无法从源头控制非法者,只能通过简单的识别来过滤一些简单的非法访问者,对于代理池简单的监控就是判断一段时间不同IP的活动规律,如果一段时间内的IP访问都是有规律的,并且跳出率为100%,通常可以判定为爬虫,但是问题是我们作为站长是结果处理,当我们能判断结果时,爬虫已经完成了一次爬取,获得了数据并更换IP请求,这个就是成本控制的问题了,你的内容价值高,对方就会愿意去不断修改爬虫获取内容。

最后还是那句话,不反对采集,但是反对非法的榨干资源请求。

当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »