无需验证码,一样可以把爬虫扼杀在摇篮-防采集策略一

前言:
对于采集者,一直很敬畏,是又尊敬又害怕,本身我也是一个爬虫爱好者,对于不考虑别人死活,只想着自己的采集者,那么就屏蔽掉它吧!下面分享一下自己的一些方法!

正文:

一:获取访问者的真实ip

我的一个二级网站,最近外网流出流量相当大,导致其他网站不能正常运行,分析nginx日志,发现大量的来自阿里云的ip
感觉很好奇.
47.110.176.169 - - [25/Jan/2019:15:23:57 +0800] "POST /what/ HTTP/1.1" 200 22 "http://whatweb.bugscaner.com/look/" "python-requests/2.19.1"
140.205.127.152 - - [25/Jan/2019:15:24:04 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.131 - - [25/Jan/2019:15:24:17 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:24:21 +0800] "POST /what/ HTTP/1.1" 200 229 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:24:22 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:24:31 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:24:31 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:24:31 +0800] "POST /what/ HTTP/1.1" 200 22 "http://whatweb.bugscaner.com/look/" "python-requests/2.19.1"
47.110.176.190 - - [25/Jan/2019:15:24:38 +0800] "GET /favicon.ico HTTP/1.1" 404 577 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"
101.200.101.213 - - [25/Jan/2019:15:24:40 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:24:47 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:24:48 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:24:52 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:24:56 +0800] "POST /what/ HTTP/1.1" 200 22 "http://whatweb.bugscaner.com/look/" "python-requests/2.19.1"
47.110.176.156 - - [25/Jan/2019:15:25:04 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:25:04 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:25:09 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:25:17 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:25:34 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:25:53 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:25:59 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:26:00 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:26:02 +0800] "POST /what/ HTTP/1.1" 200 24 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
101.200.101.213 - - [25/Jan/2019:15:26:12 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:26:12 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:26:12 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:26:16 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
47.110.176.169 - - [25/Jan/2019:15:26:22 +0800] "POST /what/ HTTP/1.1" 200 24 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"
140.205.127.152 - - [25/Jan/2019:15:26:30 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0"


这个人貌似很有钱,搞这么多阿里云服务器?然后自己爬自己,发现nginx日志里找不到自己的ip,这究竟是怎么回事?
蹊跷的事情正在发生...... 一夜无话,第二天醒来,整装待发,细细品味了一番,幡然醒悟,原来这些神秘的ip竟然来自......
阿里云cdn回源,没错,就是阿里云cdn的ip回源源站时留下的ip!!!!!!!!! 
怎么回事呢?原因很简单,因为我网站采用了阿里云cdn加速!
一旦采用cdn加速,如果nginx日志采用默认设置,是记录不到用户的真实ip的 ,一般情况下,最最可信的ip就是REMOTE_ADDR
而现在获取到的确是最最不真实的ip

--------------非常重要的地方----------------------------
在nginx里 如果网站采用了cdn加速,那么获取用户真实ip的办法就是
$http_x_forwarded_for  这个变量,在nginx.conf,改写如下:
 

	log_format  access  '$http_x_forwarded_for - $remote_user [$time_local] "$request" '
	'$status $body_bytes_sent "$http_referer" '
	'"$http_user_agent" ';

ok,在此模拟访问网站 出现了自己的真实ip

但是你不要忽略另外一个非常重要的信息,比如我在爬虫程序里加入这么一段意味深长的代码,这段代码是我的任何一个爬虫里必存在的一段经典代码。
 

#! /usr/bin/env python
#coding=utf-8
#bugscaner的可爱爬虫
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
import random

randomip = "%d.%d.%d.%d" % (random.randint(2,255),random.randint(2,255),random.randint(2,255),random.randint(2,255))
headers["X-Forwarded-For"] = randomip  
r = session.head(picurl,headers=headers,timeout=4)
在请求头部插入一个randomip,再跑一下,看看nginx日志!!
random ip?Dammit!    nginx日志里,出现了以一个逗点分开的两个ip组合,
类似如下:
219.142.102.149, 131.161.26.90 - - [25/Jan/2019:15:26:51 +0800] "POST /what/ HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0" 

经分析,最后那个才是真实ip,前面那个是前端的可以伪造的X-Forwarded-For
如果你忽略了以一个逗点分开的两个ip组合,这样对拦截程序,是一个致命的伤害,如同虚设的一个防采集的waf,一个不会思考的waf,一个只会被人蹂躏的waf。
获取真实ip,分析到此,也就是$http_x_forwarded_for 后面的以逗号分隔的最后一个ip


二:把频繁访问的ip,拍死在沙滩上
nginx的性能不是盖的,应付高并发,是他的天性,如果把访问频繁的ip扼杀在nginx初始化的时候,对网站的消耗,非常小,这里要借助openresty
因为openresty可以方便的嵌入lua脚本,利用lua脚本来拦截恶意ip:
	location  /what/ {
		default_type 'text/html';
		lua_code_cache on;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		access_by_lua_file "conf/whatweb/filter.lua";
		proxy_pass http://whatweb.bugscaner.com/what/;
	}

我们来看下filter.lua脚本的具体实现
local limit_count = require "resty.limit.count"
-- rate: 5000 requests per 3600s
local lim, err = limit_count.new("my_limit_count_store", 50, 86400)
if not lim then
	--ngx.log(ngx.ERR, "failed to instantiate a resty.limit.count object: ", err)
	--return ngx.exit(500)
	ngx.say("{\"error\":4}")
	return ngx.exit(200)
end
-- use the Authorization header as the limiting key
local headers=ngx.req.get_headers()
local ips=headers["X-Forwarded-For"]
local key = string.gsub(ips,"(.*),%s","") or "0.0.0.0"
--local key = ngx.var.remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
	if err == "rejected" then
		ngx.header["X-RateLimit-Limit"] = "100/day"
		ngx.header["X-RateLimit-Remaining"] = 0
		ngx.header["powered by"] = "BugScaner"
		ngx.say("{\"error\":2}")
		ngx.sleep(10)
		return ngx.exit(200)
		--return ngx.exit(503)
	end
	--ngx.log(ngx.ERR, "failed to limit count: ", err)
	--return ngx.exit(500)
	ngx.say("{\"error\":4}")
	return ngx.exit(200)
end
-- the 2nd return value holds the current remaining number
-- of requests for the specified key.
local remaining = err
ngx.header["X-RateLimit-Limit"] = "100/day"
ngx.header["X-RateLimit-Remaining"] = remaining
ngx.header["powered by"] = "BugScaner"

这样,就对每个用户每天访问多少次进行了限制,而限制的依据就是真实ip

如果感觉这样就万事大吉了?no,难道你不知道还有一个代理ip这种东西吗?代理ip已经廉价到白菜价格,对于当前的防护,完全是不堪一击,瞬间大脸了!

发现这样设置之后,还是有不断的真实ip随机变换的采集,而就在此刻,我漏出了邪恶的微笑?难道你不知道我还有终极必杀吗?那就是验证码@!
但是为了保证用户体验,验证码是最下侧,我还是不愿意使用验证码!!!!!!难道就没有一个更好的方法?

三:更好的选择

我忽然眼前一亮,在某年某月的某一天,我看到了一个在线工具,可以对js进行BT加密,BT到什么程度呢?连作者自己都觉得biantai.
具体就不多说了,让你逆向出现,我的防护就被你破了,自己去找吧!这种加密js的程序有很多,前端的东西,都不可信,再怎么加密,你至少得让浏览器识别吧,能人背后有人能.不过目前对我来说,应付一个爬虫还是勉强可以撑一阵子的,至少他破解js后, 我再从新加密一个,或者多生成一些js加密文件,根据用户分类,前端自动使用不同的加密js,等等,又可以增加他们的破解难度了。 想看效果 请前往http://whatweb.bugscaner.com/

js文件采用一种可逆加密方式,而后端接受参数后,进行尝试解密,如果解密失败, 直接把这个ip引入到404,这样可以大大减少非常占用资源的程序运行,经测试,采用这个方法后, 网站性能足足提升了15倍


加密解密lua脚本大致如下:
local request_method = ngx.var.request_method
ngx.req.read_body()
if request_method == "POST" then
	local hexurl = ngx.req.get_post_args()["hexurl"] or nil
	local url = ngx.req.get_post_args()["url"] or nil
	local ok, info = pcall(decrypt, hexurl)
	if ok then
		if info ~= url then
			ngx.sleep(10)
			ngx.say("{\"url\": "..url..", \"error\": \"no\", \"CMS\": [\"Ecshop\"], \"Web Servers\": [\"Apache\"], \"Programming Languages\": [\"PHP 5.5.38\", \"PHP\"]}")
			return ngx.exit(200)
		end
	else
		ngx.sleep(10)
		ngx.say("{\"url\": "..url..", \"error\": \"no\", \"CMS\": [\"Ecshop\"], \"Web Servers\": [\"Apache\"], \"Programming Languages\": [\"PHP 5.5.38\", \"PHP\"]}")
		return ngx.exit(200)
	end
else
	ngx.say("{}")
	return ngx.exit(404)
end


另外的防范措施:
由于cdn的计费方式包括流量,访问量等,从cdn屏蔽ip黑名单,也是一个不错的方法,这样完全把访问者可以和你的服务器隔离@!


还有就是经分析大部分代理ip都是国外的,比如加拿大,香港,印度,等等,现在的域名解析都可以分别解析了,国内解析到特定线路,国外解析到特定线路 如下图所示:


把这些国外的ip全部都转义到一个不存在的解析,嘎嘎,也是一种方法,这种方法可能会损失一些访问者,但是没有办法的时候也可以试一下!

下次再介绍一个方法! 再见!







您可能还会对下面的文章感兴趣: