最近公司网站的一个查询接口一直遭受爬虫的访问,由于对方通过数量巨大的分布式IP访问(大约是正常访问IP数量的几百倍),同时单个IP的访问频率又比较低,所以以往部署的一些根据访问频率特征识别阻挡的策略基本无法起到有效的防范作用。
频率特征识别行不通,于是我们又通过识别爬虫工具的HTTP请求头中的一些特征:是否包含特定的HTTP头或没有包含某些正常浏览器请求应该携带的HTTP头(如cookie和referrer),这样的封锁比较有针对性,但是HTTP头的修改成本很低,对方后来也确实多次调整HTTP头信息来规避我们的检查。同时这个方法也有可能误伤一些正常的访问。
为了更有效地识别爬虫程序,我们继续寻找其它的方法,也在网上查了一些资料。其中一个比较普遍应用的原理,是基于爬虫一般都是构造查询请求直接Post到查询接口这一特征,通过在页面嵌入JS代码判断访问来源是一个真实浏览器还是一个直接请求接口的程序。
在更完整的方案里,还有同时结合memcache或redis对请求的IP进行计数统计:在查询接口时,对来源IP请求计数+1,同时在响应页面的JS里实现对来源IP请求-1,这样一来,正常的用户浏览器查询访问,两次操作计数抵消,计数应该始终保持在一个比较低的值(理想情况应该始终在0或1,但考虑到响应页面可能出现加载不完全等情况,不一定每次都能减值),而爬虫程序由于并不触发JS,累计查询始终增长。这样就能有效识别出哪些来源是爬虫请求,接下来要怎么处理就简单了。
说起来总是没有看起来那么清晰明了,于是和团队的小伙伴们商量做个demo来演示一下,说干就干,用简单强大又好用的Python(我不会告诉你其实是因为我只会Python的)搭个网站,起个memcache,半个下午基本齐活了。
代码如下:
<pre><code>
!/bin/python
import memcache
import web
render = web.template.render('templates/')
mc = memcache.Client(['x.x.x.x:11211'], debug=0)
index页面有个最基本的搜索框,post到search页面,在search直接显示post的内容。
在search页面嵌入的JS代码会向memcache页面发起请求
urls = (
'/','index',
'/search','search',
'/memcache','mem'
)
class index:
def GET(self):
return render.index()
在search页面,操作memcache,对访问源IP的计数+1
同时做个判断,如果发现memcache里这个IP的访问计数>1,就返回一个报错页面
class search:
def POST(self):
data = web.input().get('searchstr')
#data = web.ctx['ip']
srcip = str(web.ctx['ip'])
if mc.get(srcip):
mc.incr(srcip)
else:
mc.set(srcip,1)
if mc.get(srcip) > 1:
return render.bug()
else:
return render.search(data)
memcache页面的处理方法,操作对访问源IP计数-1
class mem:
def GET(self):
srcip = str(web.ctx['ip'])
if mc.get(srcip):
mc.decr(srcip)
if name == "main":
app = web.application(urls,globals())
app.run()
</code></pre>
第一次贴完代码发现果然还是没有养成写注释的好习惯,好在比较简单,赶紧补充一下。
代码运行起来,首页就长这样:
随便输入一个查询,比如"123",查询出的结果页,长这样:
这时候,去看一下memcache里的记录,我们这个IP的记录有了,计数是0
接下来我们通过工具构造POST请求模拟爬虫的行为。构造工具很多很多,我们为了方(tou)便(lan),就用linux自带的wget,道理是一样一样的
$ wget --post-data="searchstr=123&submit=Submit" http://x.x.x.x:8080/search
嗯,第一次查询,我们看到获取的结果和用浏览器访问得到的是一样的:
但是看看我们的memcache,由于工具并不会触发结果页面上的JS,所以计数累计到了1
于是再用工具来查询一次看看,由于memcache中累计到了2,程序处理直接跳到报错页面了。真实应用中,这里也可以是验证码之类的东西。
这个demo并没有用到特别深的原理和技术,不过能通过自己的双手把别人提供的解决方案演示出来,感觉还是棒棒哒,不断理论结合实践并做好总结才是提高技术水平的好方法。
纸上得来终觉浅,绝知此事要躬行,以此作为简书第一篇。