这几天服务器带宽无缘无故升高了很多, 看了一眼nginx日志,发现有很多Baiduspider的请求,百度啥时候这么给力了?
随即在nginx配置中过滤掉了所有带有Baiduspider字样UA的请求,带宽瞬间降低
怀疑网站遭受了伪装成爬虫的CC攻击
在以往的经验中,大家大部分是使用IP段来进行判断的,不过在百度官方文档有提到这么一句
百度蜘蛛IP是不断变的,现在网上的确有一些白名单的说法,暂时是有效的,但不保证今后不会变,所以建议站点还是通过ua进行判断
然而,通过UA判断在现如今的大环境下,已经是杯水车薪,毕竟伪造一个UA,太简单了。
继续查阅官方文档,又发现了下面这一段话。
上周百度站长平台接到某站长求助,表示误封禁了Baiduspider的IP,询问是否有办法获得Baiduspider的所有IP,打算放入白名单加以保护,防止再次误封。在此要告诉各位站长,Baiduspider的IP池是不断变动的,我们无法提供IP全集。
除此之外,之前还有站长发来质疑说Baiduspider光顾过于频繁,已超越服务器承受能力。而百度站长平台追查发现,Baiduspider对该站点的抓取并无异常,那只spider极有可能是个李鬼。
那么,站长该如何通过IP来判断此spider是不是来自百度搜索引擎的呢?可以通过DNS反查方式来解决这个问题。
这里提到了DNS反查,就简单科普一下;一般情况我们都是通过域名去解析IP地址,实际上在支持的条件下,也是可以通过IP去反查到域名的。想了解DNS反查请百度: 反向DNS查询 - T_Tzz的博客
现在先用这两条数据作为测试
123.125.71.82 - - [10/Dec/2018:14:08:20 +0800] "GET /video/av2326479 HTTP/1.1" 301 191 "-" "Mozilla/5.0 (Linux;u;Android 4.2.2;zh-cn;) AppleWebKit/534.46 (KHTML,like Gecko) Version/5.1 Mobile Safari/10600.6.3 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"
168.235.86.231 - - [10/Dec/2018:14:09:29 +0800] "GET /static/js/modern_video.min.js?v=71 HTTP/1.1" 301 191 "http://www.bilibilijj.com/Video/Av25779451" "Mozilla/5.0 (compatible; Baiduspider-render/2.0; +http://www.baidu.com/search/spider.html)"
从中可以拿到2个IP地址,现在先用123.125.66.120这个IP为例
在终端中键入host 123.125.66.120
得到返回结果120.66.125.123.in-addr.arpa domain name pointer baiduspider-123-125-66-120.crawl.baidu.com.
,那我们可以判定它是一只正常的爬虫
继续用host 168.235.86.231
得到返回结果Host 231.86.235.168.in-addr.arpa. not found: 3(NXDOMAIN)
,好,抓到一只李鬼。
至此,如何判定增加蜘蛛的方式就说完了,接下来就是实现自动批量去检测的方式。
在这里先列出一些我用到的东西以及一些可能出现的问题。
需要用到东西如下
- mongodb
- python3
- iptables(debian 自带的防火墙)
可能面临的问题
- python如何在读取大文件时如何避免内存过高
- python如何执行shell并返回结果
- iptables如何使用
python如何在读取大文件时如何避免内存过高
一般情况下,使用python读取文件大家都是直接读到内存中,譬如data = open('xxx.txt', 'r').read()
,毕竟使用这种方式的时候,往往文件都比较小,也不用担心内存爆炸的问题,然而今天我们需要去读取并遍历一个G级的nginx日志文件,显然这种方式是不可取的,好在在python中有很简单也完美的解决方案
//对可迭代对象file进行迭代,这样会自动的使用buffered IO以及内存管理,这样就不必担心大文件问题了。
with open(nginx_log_path, 'r') as _flie:
for line in _flie:
print(line)
python如何执行shell并返回结果
这就是个超级简单的问题了。
//执行shell命令并返回0、1
os.system('iptables -I INPUT -s 192.168.0.111 -j DROP')
//执行shell命令并返回结果
host_str = os.popen('host 168.235.86.231').read()
iptables如何使用
说实话,在这里要讲通iptables的所有使用方法,的确是个大难题,毕竟这不是一言两句就能讲通的东西,所以这里就只做最简单的使用介绍。
//显示iptables所有配置
iptables -L
//如果使用iptables提示"-bash: iptables: command not found", 请使用whereis iptables查询具体位置一般在/usr/sbin下。
//封锁192.168.0.111对服务器所有接口的访问
iptables -I INPUT -s 192.168.0.111 -j DROP
//解除对192.168.0.111的封锁(注意,这里虽然达到了解除的效果,实际上并不是删掉了DROP的记录,而是又新增了一条ACCEPT记录,由于iptables是按照从上往下匹配的,新增的记录会放在第一条,所以ACCEPT过后就解除了,但是这会导致iptables上有多条记录
iptables -I INPUT -s 192.168.0.111 -j ACCEPT
//删除指定行数的iptables配置
iptables -D INPUT 11
//注意,这个11是行号,是iptables -L INPUT --line-numbers 所打印出来的行号
所有可能列出来的难点都解决了,现在只剩下使用代码来自动化整个过程了, 只要看过上面这些简单的阐述,下面的代码应该也是能够直接读懂的,直接上代码。
# -*- coding:utf-8 -*-
# 过滤假的蜘蛛
import os
from pymongo import MongoClient
conn = MongoClient('127.0.0.1', 27017)
db = conn.spider
tb_spider = db.Spilder
nginx_log_path = '/opt/verynginx/openresty/nginx/logs/access.log'
spider_list = [
['baiduspider', 'baidu.com.'],
['bingbot', 'msn.com.'],
['googlebot', 'googlebot.com.'],
]
ips = []
def find(data, ip):
data = data.lower()
if ip in ips or tb_spider.find_one({'ip': ip}):
return 'EXISTS'
ret_str = 'PASS'
dic = {
'ip': ip,
'status': 0,
}
for f in spider_list:
if data.find(f[0]) > 0:
host_str = os.popen('host %s' % ip).read()
if host_str.find(f[1]) == -1:
dic['status'] = 1
os.system('iptables -I INPUT -s %s -j DROP' % ip)
ret_str = 'DROP'
else:
ret_str = 'ACCEPT'
break
tb_spider.insert(dic)
ips.append(ip)
return ret_str
i = 0
with open(nginx_log_path, 'r') as _flie:
for line in _flie:
i += 1
ip = line.split(' ')[0]
result = find(line, ip)
print('(%s) %s > %s' % (i, ip, result))