tornado结合nginx实现后端防爬虫功能(一)

2 篇文章 0 订阅
1 篇文章 0 订阅

  大部分程序员可能都有过“爬虫”的经历,使用python+scrapy可以很快的实现一个爬虫程序,爬取各种心仪的数据。但做为网站或数据提供方,则是极度讨厌这种“不劳而获”的行为的,公司辛辛苦苦积累的数据,几行代码就拿走了,因此会配置各种各样的防爬虫策略。因此现实中,上演着一场又一场的攻防大战。
  防爬虫功能的本质就是区分是否为正常请求,常见的几种防爬虫手段有:

  1. User-Agent请求代理限制;
  2. IP访问频率限制;
  3. 添加Cookie校验;
  4. 基于JS校验;
  5. 基于验证码/滑块等校验.

其中前第4、5种方法需要前端配合才能实现,我们主要讨论如何对后端进行改造,实现前3种防爬虫功能。

开发环境及软件包依赖

mac os 10.14.2
redis 
python 3.7
python tornado 5.1.1
python redis 3.2.1

1. User-Agent代理限制

   User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。对用户代理的限制也是一种常见的手段,主要是屏蔽一些低级别爬虫,比如用python的requests库直接发请求,或者调用curl发请求等等。有两种方式可以实现该策略:

1. 在server端控制

   对于tornado而言,我们可以通过直接获取User_Agent参数,因此用正则表达式进行匹配即可,示例代码如下:

user_agent = self.request.headers["User-Agent"]
if re.match("(curl|python|scrapy|http|requests)+", user_agent):
	self.send_error(status_code=403)
2. 通过nginx控制

   一般服务会利用nginx来做一层反向代理或负载均衡,因此也可以在nginx入口进行User_Agent的控制,我们只需修改nginx.conf文件即可,在server配置添加User_Agent的限制信息即可:

    server {
        listen       8005;
        server_name  localhost;

        #charset koi8-r;
		# 添加的user_agent限制
		if ($http_user_agent ~* (Scrapy|Curl|HttpClient)) {
  			return 403;
		}
        #access_log  logs/host.access.log  main;

        location / {
        	proxy_pass http://127.0.0.1:8002/;
		}
	}

2. IP访问频率控制

   IP访问频率控制的思路为:设定一个记录IP在一段时间内的访问次数,如果该数值超过特定阈值,则返回403页面,禁止该IP继续访问网站。实践中,利用redis可以很方便的实现这一功能。逻辑如下:

 定义访问控制时间 EXPIRE_TIME
 定义访问次数阈值 LIMIT
 # 判断逻辑
 1. 用户首次发起正常请求
 2. server端记录用户IP,检查redis中是否存在键IP:access_count;
 3. 如果存在该键IP:access_count,则值加1,即incr(IP:access_count)
 4. 判断IP:access_count<=LIMIT,如果为True,正常访问,否则返回错误界面
 5. 如果该键不存在,在redis中创建特定键: IP:access_count,设定访问次数为1,并设定该键的超时时间为 EXPIRE_TIME

   该流程中存在一个特殊配置,即键的超时时间,作用为:键IP:access_count创立后,经过一段时间(EXPIRE_TIME)后,该键自动被redis删除。即IP的访问次数会被重置,因此如果某一IP超过访问阈值后,等到键被删除后,可以继续重新访问,实现了访问频率控制以及临时封禁的功能。

思考: 如何实现IP黑名单功能?

   我们直接看下这段逻辑对应的代码,这里我预设了超时时间为2分钟,访问次数限制为10次,即2分钟内单IP最多访问10次。如果某一用户已经超多10次了,不好意思,稍等一会就可以再访问了。

    def frequency_count(remote_ip):
        redis_key = remote_ip + ":test:access_count"
        check = redis_client.exists(redis_key)
        check_result = True
        if check:
            redis_client.incr(redis_key)
            count = int(redis_client.get(redis_key))
            if count > 10:
                check_result = False
                print "Ban Ip", remote_ip
        else:
            redis_client.set(redis_key, 1)
            redis_client.expire(redis_key, 120)
        return check_result
3. cookie添加

   cookie认证也是一种防爬虫手段,思路为:对于特定页面需要传入cookie,server端进行cookie校验,通过可正常访问,否则返回错误页面。cookie认证是IP访问频率限制的一种补充。tornado中封装了可以直接调用的API,具体如下:

class CookieHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_cookie("mycookie"):
            self.set_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")

进一步,我们可以对cookie进行加密,并设定过期时间,此时cookie需要隔一段时间进行更新,tornado的实现代码如下:

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_secure_cookie("mycookie"):
            self.set_secure_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")
 # 可以在初始化构造Application时设定秘钥
 application = tornado.web.Application([
    (r"/", MainHandler),
], cookie_secret="test")

思考:本小节我们只是给出了基于tornado进行cookie添加的方法,并未给出如何用cookie实现防爬虫,大家可以思考一下?

总结

   在本文中,我们主要给出了User_Agent和IP访问频率限制两种防爬虫策略,并留了两处思考,我们将在下一篇文章中继续讨论,最后给出一个完整的tornado后端server,包含了以上三种方法。

# -*- coding:utf-8 -*-
import redis
import re
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.httpclient
import time


from tornado.options import define, options


define("port", default=8002, help="run on the given port", type=int)
redis_client = redis.Redis("127.0.0.1", 6379, 3, "f66sU9iP")


class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        remote_ip = self.request.remote_ip
        user_agent = self.request.headers["User-Agent"]
        if re.match("([curl]|[python]|[scrapy]|[http]|[requests])+", user_agent):
            self.send_error(status_code=403)
        else:
            flag = self.frequency_count(remote_ip)
            if flag:
                cookie_value = self.get_secure_cookie("cookie_value")
                if not cookie_value:
                    self.set_secure_cookie("cookie_value", value=str(time.time()), expires_days=1)
                    self.write("Error access!")
                    self.request.finish()
                else:
                    self.write("Your are valid user")
                    self.request.finish()
            else:
                self.send_error(status_code=403)

    @staticmethod
    def frequency_count(remote_ip):
        redis_key = remote_ip + ":test:access_count"
        check = redis_client.exists(redis_key)
        check_result = True
        if check:
            redis_client.incr(redis_key)
            count = int(redis_client.get(redis_key))
            if count > 10:
                check_result = False
                print "Ban Ip", remote_ip
        else:
            redis_client.set(redis_key, 1)
            redis_client.expire(redis_key, 120)
        return check_result

def make_app():
    tornado.options.parse_command_line()
    app = tornado.web.Application(handlers=[(r"/get_cookie", IndexHandler)],
                                  cookie_secret="test123")	
    http_server = tornado.httpserver.HTTPServer(app, xheaders=True)
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.current().start()


if __name__ == "__main__":
    make_app()

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值