【Django 023】中间件Middleware(二):结合session和cache实现反爬虫中间件图文详解

上一节《【Django 022】中间件Middleware(一):Django中间件本质和处理流程详解》中,我们了解了中间件的本质和执行逻辑,这一节就来自己动手制作一个用来反爬虫的中间件。

我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。

爬虫

爬虫,就是一段伪装成浏览器,对网站发起请求获得网站资源的程序。例如爬取某求职网站的薪水统计,征婚交友网站的美女资料,或者某电子书网站的电子书等等。因为快速高效,爬虫可以帮助我们自动完成很多在线搜索统计的工作

但是做为服务器,因为爬虫程序的频繁请求,可能造成负载过高,甚至宕机的后果。所以服务端运维通常会对自己的网站加入很多反爬机制。

爬虫与反爬一直是大家津津乐道的话题。今天咱们就来看看如何实现自己的一个反爬小程序,知己知彼,百战不殆。

反爬和频率控制

如果一个爬虫程序频繁对服务器进行请求,会对服务器性能造成极大冲击。所以如果发现某个用户或者某个ip访问极为频繁,基本可以判定其为爬虫,从而对其行为进行一些限制。

这里模仿前面《【Django 013】Django2.2会话技术之Session》中去创建登录页面和个人主页,结合《【Django 021】Django2.2利用装饰器和cache底层API两种方式实现Redis缓存原理和操作详解》对每个sessionid记录访问个人主页时间戳来达到限制用户频繁访问的目的。

正常登录逻辑

  • 访问login/页面,如果session已经存在,跳转到homepage/,否则进入登入页面。提交登陆表单后创建session并进入homepage/,如果用户名为空则一直重定向回login/
  • 如果session已经存在,直接访问homepage/可以成功,否则重定向到login/
  • 主页的logout/会flush当前用户的session,重定向到login/重新登陆

三个路由规则和view函数如下

path('login/', views.login, name='login'),
path('homepage/', views.homepage, name='homepage'),
path('logout/', views.logout, name='logout'),
def login(request):
    if request.method == 'GET':
        name = request.session.get('username', 0)
        if name:
            response = HttpResponseRedirect(reverse('app:homepage'))
        else:
            response = render(request, 'login.html')
        return response
    elif request.method == 'POST':
        name = request.POST.get('name', 0)
        if name:
            response = HttpResponseRedirect(reverse('app:homepage'))
            request.session['username'] = name
        else:
            response = HttpResponseRedirect(reverse('app:login'))
        return response


def homepage(request):
    name = request.session.get('username', 0)
    if name:
        response = render(request, 'homepage.html', context={'name': name})
    else:
        response = HttpResponseRedirect(reverse('app:login'))
    return response


def logout(request):
    request.session.flush()
    return HttpResponseRedirect(reverse('app:login'))

login页面h5如下,这里的csrf_token是防跨站攻击的,也是Django自带的安全中间件,下一节再详细讲csrf的原理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<form action="{% url 'app:login' %}" method="post">
    {% csrf_token %}
    <label for="name">Name: </label><input type="text" id="name" name="name" placeholder="Your name">
    <input type="submit">
</form>
</body>
</html>

homepage页面h5如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Homepage</title>
</head>
<body>
<h2>Hi {{ name }}, These are all your photos and videos</h2>
<a href="{% url 'app:logout' %}">Logout</a>
</body>
</html>

基本逻辑完成以后,下面开始本节的重点,频率限制。

假设一个爬虫,已经模拟了用户登录,获取了session,开始疯狂爬取登录用户才能获取的资源。这时候就可以针对该用户做一些操作了。

10秒只能访问一次

频率限制的逻辑如下。

用户在访问本项目任何url前,中间件会去检查是否存在sessionid对应的username的值,如果存在说明session已经被创建,进入频率限制的逻辑。如果不存在(可能是cookie没带sessionid,或者是session过期),则中间件跳过不处理。

如果session存在,中间件去cache中查找该session对应的cache是否存在,没有则创建,过期时间10秒,如果有则返回ttl,直接提示用户等待。

中间件实现如下

class Test1(MiddlewareMixin):
    def process_request(self, request):
        name = request.session.get('username', 0)
        if name:
            cache = caches['my_redis_cache']
            if cache.get(name):
                ttl = cache.ttl(name)
                response = HttpResponse('Hi {}, please wait for another {} seconds to visit'.format(name,str(ttl)))
                return response
            else:
                cache.set(name, name, timeout=10)

这里利用之前配置的redis做cache,以用户的name做为key,name做为value,过期时间为10秒。如果key已经存在,返回ttl提示给用户。

如果出现 ‘WSGIRequest’ object has no attribute ‘session’ 的报错,试着将自定义的中间件放在settings中列表的最后

效果如下
case1.gif

30秒只能访问五次

如果更进一步,想要实现动态时间限制,其逻辑如下。

同样是通过session的值做为key来存储cache,不过不同的是,这里cache的value是一个list。每次用户携带sessionid成功访问,都在list里面插入当前的时间。同时每次访问的时候,清除距现在操作30秒的时间戳,然后计算剩余时间戳的个数。如果个数操作5,则本次操作失败

中间件实现如下

class Test1(MiddlewareMixin):
    def process_request(self, request):
        name = request.session.get('username', 0)
        if name:
            cache = caches['my_redis_cache']
            timestamps = cache.get_or_set(name, default=[], timeout=30)
            for item in timestamps:
                if time.time() - item > 30:
                    timestamps.remove(item)
            if len(timestamps) > 5:
                return HttpResponse('Sorry {}, you have reached 5 visits within 30s.'.format(name))
            else:
                timestamps.append(time.time())
                cache.set(name, timestamps, 30)

这里利用cache的get_or_set方法可以在cache不存在的时候set一个默认值以及过期时间。

效果如下
case2.gif

封ip操作

除了前面两种限制频率的操作,还可以更进一步,直接将某个频繁访问的用户的cache过期时间改为1天,而且cache存在情况下不能访问,这样就达到了将用户封ip一天的目的。

因为与前面两种方式代码大同小异,这里就不写了,大家可以自己练习一下。

总结

通过写了两个简单的反爬小程序,我们巩固了中间件的执行逻辑。下一节就一起去看看Django自带的csrf中间件是怎么样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值