www.fachun.net是我做的一个音乐资源站,因为资源丰富,容易成为他人抓取的目标。
被抓取资源事小,另一方面,爬虫的频繁访问还会严重占用服务器资源。
前期写代码的时候,我已经考虑到防爬问题,做了一些简单的事,比如避免使用数字ID,而是ID + 名字。
例如一个歌手的名字是ABC,ID是36,最终的URL就是:
http://www.fachun.net/musician/36-ABC/
这是爬虫难以直接猜解的。
今天我再写一些额外的代码来限制单个IP请求的频率,实现防爬。有几个要点:
1. 使用MySQL Memory数据库引擎来存储用户IP地址,
因为多了个过滤层,响应时间必然增加。应该尽量减小这个影响,把IP放在内存中可以节省查找时间。
原来的应用是InnoDB引擎,所以,需要新建数据库,并在settings.DATABASES中添加它。
2. 目前我的过滤条件是10分钟内请求超过100次(已改为60次),封IP十分钟。
3. 搜索引擎百度、Google等的爬虫应该不受过滤,因此,需要特别处理搜索引擎的爬虫。
设IP白名单会再次影响效率,我不希望设用白名单来筛选。
只是检查REMOTE_HOST是否包含googlebot.com、crawl.baidu.com等字符串
1. 修改配置文件,增加数据库
首先新建一个app,我这里命名为robotkiller,执行:
manage.py startapp robotkiller
然后在工程settings.py文件中添加一个数据库,我仍然用同名的robotkiller,并在INSTALLED_APPS中添加robotkiller。
DATABASES = {
'default': {...},
'robotkiller': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'robotkiller',
'USER': 'your_user',
'PASSWORD': 'your_pass',
'HOST': '127.0.0.1',
'PORT': '3306',
'OPTIONS': {
"init_command": "SET storage_engine=MEMORY",
}
}
}
models.py中的内容是这样的:
from django.db import models
class RobotKiller(models.Model):
id = models.IntegerField(primary_key=True)
ip = models.CharField(max_length=16) #IP地址
visits = models.IntegerField() #请求次数
time = models.DateTimeField() #第一次发起请求的时间
class Meta:
db_table = 'robotkiller'
记得为ip字段建一个索引(让MySQL为内存数据库再维护个索引,是对是错,暂不深究):
我在settings.py中设置了时区为’Asia/chongqing’,所以,取得时间差,不应该直接用datetime,
而是用django.utils.timezone。下面是我的视图函数:
from models import RobotKiller
from django.utils import timezone
max_visits = 100
min_seconds = 600
def filterIP(request):
domain = request.META.get('REMOTE_HOST')
white_list = ['googlebot.com', 'crawl.baidu.com', 'sogou.com', 'bing.com', 'yahoo.com']
for bot_domain in white_list:
if domain.find(bot_domain) > 0:
return bot_domain
user_ip = request.META['REMOTE_ADDR']
try:
record = RobotKiller.objects.using('robotkiller').get(ip=user_ip)
except RobotKiller.DoesNotExist:
RobotKiller.objects.using('robotkiller').create(ip=user_ip, visits=1, time=timezone.now())
return
passed_seconds = (timezone.now() - record.time).seconds
if record.visits > max_visits and passed_seconds < min_seconds:
raise Exception('user ip banned.')
else:
if passed_seconds < min_seconds:
record.visits = record.visits + 1
record.save()
else:
record.visits = 1
record.time = timezone.now()
record.save()
上述代码就是基本的过滤逻辑。在urls.py中添加:
from robotkiller import filterIP
然后在需要防爬取的对应app的views.py中添加该函数:
def checkIP(request):
try:
filterIP(request)
except Exception, e:
if unicode(e) == 'user ip banned.':
raise PermissionDenied()
接着,在所有视图函数的第一行添加checkIP(request) 就可以过滤所有页面的请求了。
当用户在10分钟内请求了超过100个页面,会得到一个403错误。
可以在模板文件夹下新建一个403.html,自定义出错提示了。