⾼频访问IP限制--Openresty(nginx+lua)[反爬⾍之旅]
前⾔
嗯….本⼈是从写爬⾍开始编程的,不过后⾯做web写⽹站去了,好了,最近web要搞反爬⾍了,哈哈哈,总算有机会把之以前做爬⾍时候见识过的反爬⼀点点给现在的⽹站⽤上了~ 做爬⾍的同志,有怪莫怪喽~还有求别打死 > <
⾸先要提⼀下AJAX,现在普天下⽹页⼏乎都是往特定的数据接⼝请求数据了,除了什么⾸屏渲染这种服务端渲染好html以外,⼏乎没有什么静态⽹页了。我看了有⼀些帖⼦说AJAX让爬⾍难做,可是我觉得结合⼀些⼯具(⽐如chrome的开发者⼯具),到AJAX所请求的后端数据接⼝⼀点也不难,⽽且现在⾃⼰也写过⼀段时间的web后端数据接⼝,发现接⼝的设计往往都是往简单易懂的⽅向做,外加从2000年出现REST风格,更是让接⼝设计越来越简明了。所以其实如果⼀个web站点没有察觉到有爬⾍的存在,或者察觉到了,但是没有想要做⼀点数据保护措施,它是不会再AJAX上做⽂章的,那么如果单纯的AJAX,其实并没有任何反爬的作⽤,所以别再说AJAX反爬什么的了,何况AJAX⽣出来就不是为了反爬的
然⽽在现在的前后端分离的时代,前端反爬还是有的搞的,基于我不太懂JavaScript,就不展开来说,我只是听说过什么参数加密啊,数据混淆什么的,但其实概括起来都是⼀种对数据接⼝的隐藏,这让⼀些不太懂js的⼈,也跟着懵逼了(⽐如说我 : <),但是你要知道,前端代码最终还是要请求⼀个url的,⽆论它把
这个过程拆开成多散,弄得多复杂都好,只要是需要数据,就必然需要请求⼀个后端接⼝(这个接⼝可以是SOAP,不过21世纪恐怕更多的是RESTful的),所以对于数据保护⽽⾔,更加需要重点关注的是后端数据接⼝的保护。
本反爬⾍之旅系列将会⼀点点从各个⽅⾯垒⾼数据保护墙,但是请记住,因为⽹站数据的公开性,所以,只是延缓被盗库的时间⽽已,想⾃⼰在⽹站上公开的数据完全不被爬⾛是不可能的。那么我们的⽬标就是:让盗库耗时被延缓到⼀个⽐较长的时间⾥⾯,那么对于爬取数据⽅⽽⾔,这些数据的价值将会随着时间的增加⽽降低,数据的价值=利⽤价值 - (爬取成本+数据贬值速度) * 爬取时间(不⽤纠结来源了,我说的)
这⼀篇就讲最基础的“给过频IP弹验证码”这种⼊门级防护实现,虽然花钱买点代理IP就可以搞定这种实现,但是⾄少也让他们增加了成本,但是我们相对地并没有花费多少成本,⽽且过频IP弹验证码除了能反爬,也能抵御⼀部分的CC攻击(短时间⼤量的爬⾍请求堪⽐CC攻击啊),虽然没有多⼤的作⽤,但是起码⽐裸奔强!这也算是功能上的复⽤吧
反爬⾍之旅预告:
1. 过频IP弹验证码[应⽤外]
2. 数据接⼝的url设计(uuid)和内容横向范围限制(参考)[应⽤内]
3. ⽤户可见(参考微博)以及内容纵向切割(盈利点思考)[应⽤内]
统览
⾼频访问IP弹验证码架构图
P.s. csdn默认⽔印real丑,直接去掉图⽚地址的watermark就可以了
OpenResty
我不准备在web应⽤中做ip的统计和查封,应⽤就应该只做业务功能,这些基础东西应该由我们应⽤的前部——专业的Nginx实现
Nginx本⾝就有根据ip访问频率的设置,⽐如就有提到。不过Nginx只能强硬地返回个403状态码什么的,但是我们这次ip封禁时间⽐较久,那么如果误伤到⽤户,我们仅仅强硬地返回个403,⽤户将会毫⽆办法证明⾃⼰是⼈,然后要等很久,那就伤⽤户就伤得很深了,因此我们需要⼀种可以让被误伤的⽤户能及时⾃⾏解封的策略,验证码就是⼀个不错的选择,可是Nginx该怎么接⼊验证码呢?
在说明怎么Nginx接⼊验证码之前,我想先说说验证码本⾝,其实就基础防护来说,(封IP+验证码)是性价⽐⽐较⾼的⼀般性基础组合了,⽐较低廉的成本就能给爬⾍制造⿇烦,基于这种组合就能筛选掉⼀部分廉价爬⾍。⽽虽然说⾄今为⽌,很多验证码都被破解了,甚⾄连新型的基于⾏为的验证码(⽐如极验的拖条验证和⾕⼤哥的reCaptcha),都有⼈提出了破解⽅案(我今天⾕歌⼀下,居然不⽌是⽅案,已经有两三页的教程了- -||| 我得个时间学习⼀下了),但是,这种破解⽅案却不是谁都可以完美丝滑地应⽤到⾃
⼰的爬⾍上,这是需要⼀定功⼒的,那么换个⾓度思考,我们在某种程度上已经赢了,毕竟我们只是调⽤别⼈⼀个接⼝⽽已,甚⾄就算我们⾃⼰DIY⼀个汉字的图⽚验证码也不费
多⼤功夫(汉字字符粘连+带随机噪点+⼲扰线并不特别难,实在不懂可以参考这篇就有现成代码~⼤概长这样),⽽爬⾍要搞定验证码要么⾃⼰花钱第三⽅识别,要么就⾃⼰的团队开发识别验证码的⼯具,总之⼜提⾼了他们爬取成本,杀敌⼀千,⾃伤只有五百
虽然有现成的免费的图⽚验证码⽣成程序,但是我们在这篇博⽂⾥⾯还是来点新潮的”基于⾏为”的验证码吧,⽐如说极验,⽽关于极验的部署后⾯还是会提到,个⼈觉得他们的官⽅⽂档后端部署的python那部分讲的不清不楚,后⾯得⾃⼰测试跑⼀次才知道怎么改….
那么回归Nginx接⼊验证码的问题,我们需要Lua,Lua是⼀个⾼性能的脚本语⾔,我感觉和Python很像,但是灵活性⽐不上Python,⽽执⾏速度却⽐Python快。Lua和C/C++是很亲和的,是补充C/C++灵活性的存在,因为有Lua,只要我们在C/C++中向外引⼊Lua脚本,那么如若Lua脚本发⽣了修改,我们也并不需要因此重新编译⼀次C/C++程序。Nginx本⾝便是由C/C++编写,所以⾃然和Lua亲和,⽽后⼜有OpenResty项⽬的存在(捆绑了nginx和lua并⾃带常⽤lua模块),让Lua在扩展Nginx上成为头号选择。
P.s.补充⼀点,其实Lua在Nginx的应⽤只是Lua应⽤中很⼩的⼀个点⽽已,它在游戏中才是被⼴泛地应⽤,因为:第⼀,游戏在乎性能体验,所以很多Engine都是⽤C/C++写的,⾃然需要Lua做⼀点粘合性
补充; 第⼆,Lua的性能仅仅次于C/C++,⽽且还有为了榨⼲lua性能的LuaJIT的存在,让lua的性能得到进⼀步地提升,故Lua是C/C++后的第⼆选择
OpenResty本⾝没有什么好讲的,它最⼤的功劳就是把Lua⽐较舒服地捆绑到了Nginx上,其他特性都是Lua本⾝的东西,所以想把Nginx 玩的更加溜,除了彻底玩转Nginx本⾝以外(Nginx本⾝的配置就有点像⼀门⼩语⾔了),Lua会是你不⼆的选择。
下载安装OpenResty
下载安装可以直接参考官⽹的教程(看安装和新⼿上路就可以了,以后有空想稍微深⼊⼀点的,可以直接看)
P.s因为我⽬前⼯作的本本是MBP,所以是⽤homebrew安装的,感觉会和linux⾥⾯的openresty有点不太⼀样,osx⾥⾯是⽤openresty这条命令启动才算是openresty,⽽linux貌似是openresty下的nginx启动的才算是openresty,才能⽤⽐如access_by_lua_file或者content_by_lua这种openresty语法
我⾃定义的⽬录结构如下:
-anti_spider
-conf/
-f
-lua/
-access.lua
-log/
-error.log
-geetest_web/
-demo/
-sdk/
-geetest.py
-setup.py
-
Nginx配置
在openresty下接⼊Lua脚本就⼀句话,下⾯给出f⽰范:
worker_processes 1;
error_log logs/error.log;
events{
worker_connections 1024;
}
http {
server {
listen 80;
location / {
access_by_lua_file 'lua/access.lua';
content_by_lua 'ngx.say("Welcome PENIS!")';
}
}
}
access.lua
-- package.path = '/usr/local/openresty/nginx/lua/?.lua;/usr/local/openresty/nginx/lua/lib/?.lua;'
-- package.cpath = '/usr/local/openresty/nginx/lua/?.so;/usr/local//openresty/nginx/lua/lib/?.so;'
-- 连接redis
local redis = require 'dis'
local cache = w()
local ok ,err = t(cache,'127.0.0.1','6379')
cache:set_timeout(60000)
-- 如果连接失败,跳转到label处
if not ok then
goto label
-- ⽩名单
is_white ,err = cache:sismember('white_list', _addr)
if is_white == 1then
goto label
end
-- ⿊名单
is_black ,err = cache:sismember('black_list', _addr)
if is_black == 1then
goto label
end
-- ip访问频率时间段
ip_time_out = 60
-- ip访问频率计数最⼤值
connect_count = 45
-- 60s内达到45次就ban
-- 封禁ip时间(加⼊突曲线增长算法)
ip_ban_time, err = cache:get('ip_ban_time:' .. _addr)
if ip_ban_time == ngx.null then
ip_ban_time = 300
res , err = cache:set('ip_ban_time:' .. _addr, ip_ban_time)
res , err = cache:expire('ip_ban_time:' .. _addr, 43200) -- 12h重置
end
-- 查询ip是否在封禁时间段内,若在则跳转到验证码页⾯
is_ban , err = cache:get('ban:' .. _addr)
if tonumber(is_ban) == 1then
-- source携带了之前⽤户请求的地址信息,⽅便验证成功后返回原⽤户请求地址
local source = de_base64(ngx.var.scheme .. '://' ..
怎么查自己的ip
ngx.var.host .. ':' .. ngx.var.server_port .. quest_uri)
local dest = '127.0.0.1:5000/' .. '?continue=' .. source
goto label
end
-- ip记录时间key
start_time , err = cache:get('time:' .. _addr)
-- ip计数key
ip_count , err = cache:get('count:' .. _addr)
-- 如果ip记录时间的key不存在或者当前时间减去ip记录时间⼤于指定时间间隔,则重置时间key和计数key -- 如果当前时间减去ip记录时间⼩于指定时间间隔,则ip计数+1,
-- 并且ip计数⼤于指定ip访问频率,则设置ip的封禁key为1,同时设置封禁key的过期时间为封禁ip时间
if start_time == ngx.null or os.time() - tonumber(start_time) > ip_time_out then
res , err = cache:set('time:' .. _addr , os.time())
res , err = cache:set('count:' .. _addr , 1)
else
ip_count = ip_count + 1
res , err = cache:incr('count:' .. _addr)
-- 统计当⽇访问ip集合
res , err = cache:sadd('statistic_total_ip:' .. os.date('%x'), _addr)
if ip_count >= connect_count then
res , err = cache:set('ban:' .. _addr , 1)
res , err = cache:expire('ban:' .. _addr , ip_ban_time)
res , err = cache:incrby('ip_ban_time:' .. _addr, ip_ban_time)
-- 统计当⽇屏蔽ip总数
res , err = cache:sadd('statistic_ban_ip:' .. os.date('%x'), _addr)
end
::label::
local ok , err = cache:close()
Reference:
1.
2.
3.
启动/重启nginx
启动:
nginx -p`pwd`-c f
重载:(修改了lua脚本或者f配置每次都要重载⽣效)
nginx -p`pwd`-c f -s reload
Redis统计数据持久化
Lua脚本⾥⾯有statistic_ban_ip和statistic_total_ip两个统计数据,分别记录了每天的被屏蔽过的ip数量和总共访问的ip数量,那么根据这些数据,我们就可以做分析,⽐如statistic_ban_ip/statistic_total_ip每⽇被封禁ip占总ip量的百分⽐,还有可以结合百度地图的ip地理定位做被封ip的定位,看看哪个地区被封杀最严重~ 甚⾄还可以以后积累了⼏个个⽉甚⾄⼏年的redis记录,然后可以做⼀份 [⽉被封ip量 - ⽉份|年份] 的笛卡尔坐标系(Cartesian coordinate system),然后可以深⼊分析⼀下时间分布,根据这种分布,适当地调整⼀下策略,或者甚⾄可以做成智能型的
当然现在已经有很多⽹站前置统计数据的服务了,⽐如友盟+什么的,但是我们所记录的这些数据是实实在在我们⾃⼰⼀天天”熬”出来的数据,留在本地做数据分析⽤,或者给其他的什么需求提供数据⽀持,这个…谁说的准呢?不过数据就是数据,留下来是对的,我们的这些留下来的数据也不是什么垃圾
数据,况且,实际⼯作量也不⼤(就redis增加两个字段⽽已),占⽤的空间也不⼤(就⼀些短字符串⽽已)
不过问题是,如果你内存不够,⽽redis是内存型的数据库,加之也没有必要长年累⽉都把统计数据堆在redis⾥⾯,所以我们得有把这些统计数据,或者可以直接说冷数据持久化到硬盘的定时操作,⽽⾄于redis的持久化,这⾥留个坑,回头再来填
极验
现在来讲讲统览图⾥⾯的Captcha WebApi的构建,在上⾯Lua的脚本⾥⾯有⼀句跳转到验证码接⼝的:
local dest = '127.0.0.1:5000/' .. '?continue=' .. source
⾥⾯的这个就是统览图⾥⾯的Captcha WebApi开放的验证码验证地址,我们在这个地址上部署的是极验的验证码服务(并⽆⼴告意思,易盾貌似也不错~),你可以上他们的官⽹下载他们的demo,我这⾥的以Flask demo为例:
1.git拉下来
git clone github/GeeTeam/gt3-python-sdk.git
2.构建geetest
python3 setup.py install
3.到启动demo⾥⾯的基于flask写的web api
#直接python3 start.py是不⾏滴!你还需要flask,⽽且因为还要访问redis,再来个redis
pip3 install Flask
pip3 install redis
python3 start.py
#注意要和start.py以及templates/同⼀层启动start.py,不然等下不到templates/下⾯的login.html和gt.js
#吐槽⼀下极验的后端部署⽂档的不完整,我也是⾃⼰调试着才知道怎么回事...
Refer:
好的,既然能跑了,那么我们得怎么改?要知道他们给的demo是没有redis访问的!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论