反爬

/ 技术 / 无站内评论 / 65浏览

反爬

因为反爬虫暂时是个较新的领域,因此有些定义要自己下。我们内部定义(携程)是这样的:

这里要切记,人力成本也是资源,而且比机器更重要。因为,根据摩尔定律,机器越来越便宜。而根据IT行业的发展趋势,程序员工资越来越贵。因此,让对方加班才是王道,机器成本并不是特别值钱。

为什么要反爬虫?

1.占用服务器资源

短时间内发出大量请求,服务器带宽都被占用了,影响正常用户访问。

2.公司可免费查询的资源被批量抓走,丧失竞争力,这样少赚钱。

竞争对手可以抓到我们的价格,时间长了用户就会知道,只需要去竞争对手那里就可以了,没必要来携程。这对我们是不利的。

3、爬虫是否涉嫌违法? 如果是的话,是否可以起诉要求赔偿?这样可以赚钱。

这个问题我特意咨询了法务,最后发现这在国内还是个擦边球,就是有可能可以起诉成功,也可能完全无效。所以还是需要用技术手段来做最后的保障。

反什么样的爬虫

1、十分低级的初学者

初学者爬虫通常简单粗暴,根本不管服务器压力,加上人数不可预测,很容易把站点弄挂。

2、十分低级的创业小公司

现在的创业公司越来越多,也不知道是被谁忽悠的然后大家创业了发现不知道干什么好,觉得大数据比较热,就开始做大数据。

分析程序全写差不多了,发现自己手头没有数据。

怎么办?写爬虫爬啊。于是就有了不计其数的小爬虫,出于公司生死存亡的考虑,不断爬取数据。

3、不小心写错了没人去停止的失控小爬虫

携程上的点评有的时候可能高达60%的访问量是爬虫。我们已经选择直接封锁了,它们依然孜孜不倦地爬取。

什么意思呢?就是说,他们根本爬不到任何数据,除了http code是200以外,一切都是不对的,可是爬虫依然不停止这个很可能就是一些托管在某些服务器上的小爬虫,已经无人认领了,依然在辛勤地工作着。

4、成型的商业对手

这个是最大的对手,他们有技术,有钱,要什么有什么,如果和你死磕,你就只能硬着头皮和他死磕。

5、抽风的搜索引擎

大家不要以为搜索引擎都是好人,他们也有抽风的时候,而且一抽风就会导致服务器性能下降,请求量跟网络攻击没什么区别。

知己知彼:如何编写简单爬虫

如上。实现:模拟登陆博客后台,并且获取文章列表,就是一个简单爬虫。当然,还有更简单的,访问URL即可得到页面数据。

知己知彼:如何编写高级爬虫

那么爬虫进阶应该如何做呢?通常所谓的进阶有以下几种:

分布式

通常会有一些教材告诉你,为了爬取效率,需要把爬虫分布式部署到多台机器上。这完全是骗人的。分布式唯一的作用是:防止对方封IP。封IP是终极手段,效果非常好,当然,误伤起用户也是非常爽的。

模拟JavaScript

有些教程会说,模拟javascript,抓取动态网页,是进阶技巧。但是其实这只是个很简单的功能。因为,如果对方没有反爬虫,你完全可以直接抓ajax本身,而无需关心js怎么处理的。如果对方有反爬虫,那么javascript必然十分复杂,重点在于分析,而不仅仅是简单的模拟。

换句话说:无反爬直接请求ajax,如果反爬虫,将会通过js加密,分析js难度较大。

PhantomJs

这个是一个极端的例子。这个东西本意是用来做自动测试的,结果因为效果很好,很多人拿来做爬虫。但是这个东西有个硬伤,就是:效率。此外PhantomJs也是可以被抓到的,出于多方面原因,这里暂时不讲。 

不同级别爬虫的优缺点

越是低级的爬虫,越容易被封锁,但是性能好,成本低。越是高级的爬虫,越难被封锁,但是性能低,成本也越高。

当成本高到一定程度,我们就可以无需再对爬虫进行封锁。经济学上有个词叫边际效应。付出成本高到一定程度,收益就不是很多了。

那么如果对双方资源进行对比,我们就会发现,无条件跟对方死磕,是不划算的。应该有个黄金点,超过这个点,那就让它爬好了。毕竟我们反爬虫不是为了面子,而是为了商业因素。

如何设计一个反爬虫系统(常规架构)

传统反爬虫手段

1、后台对访问进行统计,如果单个IP访问超过阈值,予以封锁。

这个虽然效果还不错,但是其实有两个缺陷,一个是非常容易误伤普通用户,另一个就是,IP其实不值钱,几十块钱甚至有可能买到几十万个IP。所以总体来说是比较亏的。不过针对三月份呢爬虫,这点还是非常有用的。

2、后台对访问进行统计,如果单个session访问超过阈值,予以封锁。

这个看起来更高级了一些,但是其实效果更差,因为session完全不值钱,重新申请一个就可以了。

3、后台对访问进行统计,如果单个userAgent访问超过阈值,予以封锁。

这个是大招,类似于抗生素之类的,效果出奇的好,但是杀伤力过大,误伤非常严重,使用的时候要非常小心。至今为止我们也就只短暂封杀过mac下的火狐。

4、以上的组合

组合起来能力变大,误伤率下降,在遇到低级爬虫的时候,还是比较好用的。

由以上我们可以看出,其实爬虫反爬虫是个游戏,RMB玩家才最牛逼。因为上面提到的方法,效果均一般,所以还是用JavaScript比较靠谱。

https://www.sojson.com/jscodeconfusion.html 在线加密混淆

eval(加密)

函数可计算某个字符串,并执行其中的的 JavaScript 代码。

eval已经臭名昭著了,它效率低下,可读性糟糕。正是我们所需要的。

想了解更多?戳我http://www.w3school.com.cn/js/jsref_eval.asp

效果:

  
alert(1);#弹出1

当我们使用eval处理之后的代码

  
eval(function(p,a,c,k,e,r){e=String;if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'^$'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('alert(1);',[],1,''.split('|'),0,{}))

???

看懂我吃

当我们复制过来执行发现!!!

混淆

混淆会用一些简单的字母代替你的变量名,这个是没法还原的,但是还是遵循js基本规则的,可以读懂,还有一种是加密混淆。

效果:

  
alert(1);

混淆后

  
window["\x61\x6c\x65\x72\x74"](1);

是不是也没看懂什么玩意?

执行

你觉得这就完了?

不,加密+混淆好像很刺激的样子。

  
window["\x65\x76\x61\x6c"](function(zDHIAYU1,qS2,csqrpM$J3,C_$iNMKIj4,Mp5,dcKMOAJtj6){Mp5=window["\x53\x74\x72\x69\x6e\x67"];if('\x30'['\x72\x65\x70\x6c\x61\x63\x65'](0,Mp5)==0){while(csqrpM$J3--)dcKMOAJtj6[Mp5(csqrpM$J3)]=C_$iNMKIj4[csqrpM$J3];C_$iNMKIj4=[function(Mp5){return dcKMOAJtj6[Mp5]||Mp5}];Mp5=function(){return'\x5e\x24'};csqrpM$J3=1};while(csqrpM$J3--)if(C_$iNMKIj4[csqrpM$J3])zDHIAYU1=zDHIAYU1['\x72\x65\x70\x6c\x61\x63\x65'](new window["\x52\x65\x67\x45\x78\x70"]('\\\x62'+Mp5(csqrpM$J3)+'\\\x62','\x67'),C_$iNMKIj4[csqrpM$J3]);return zDHIAYU1}('\x61\x6c\x65\x72\x74\x28\x31\x29\x3b',[],1,''['\x73\x70\x6c\x69\x74']('\x7c'),0,{}))

再次执行

不稳定代码

什么bug不容易修?不容易重现的bug不好修。因此,我们的代码要充满不确定性,每次都不一样。

说这么多到底怎么用呢?

我们看下西部航空的请求参数。

在进行敏感数据传输时,一般都会通过javascript进行加密,例如通过前端JS对提交数据进行加密。

  
q:%F7%9D%93%E6%DA%D7%EB%9B%5C%95%D6%9D%91%E1%D9%89%5C%5Cj%89%8CmNN%86%D7%E7%96%5C%5Ce%8E%92i%9F%DA%89N%95%E7%D5%D3%E6%B8%A5%D5%D9%87%5C%5CTbaie%5Eb%5E%5EeVNN%87%D3%D2%A8%A5%D5%D9%87%5C%5CTbaie%5Ec_%5EeVNN%8E%CD%CF%D5%BB%CD%E9%D5%87%5C%5C%9C%E2%C7%A2%91pNN%92%DC%CD%D5%DA%D5%E1%DF%C1%CD%E9%D5%87%5C%5C%92%D3%85%9F
  1. 发起提交

  2. 通过前端JS加密,将加密后的参数提交到后台

  3. 后台解密处理

  4. 后台加密返回前端响应

  5. 前端解密展示

但是有个问题,因为JS的控制权是完全交给用户的,一切代码都暴露出去。换句话说,他们可以拿着我们的JS代码,推导出加密解密的参数。

于是,我们需要对JS进行加密+混淆。(让他们看不懂JS代码)

JS使用的加密库是google开源的crypto-js,加密方式AES(前后端配置一致的key,VI)。

当我们输入

  
爬虫

加密后输出

  
J/RyL29AX7K7YbndvlQScQ==

提交加密的参数到服务器,服务器解密处理,返回时将结果加密,返回前台再将结果解密展示

  
  @RequestMapping(value = "/decrypt", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
  @ResponseBody
  public String decrypt(@RequestBody JSONObject json) {
      String decrypt = AESUtils.decrypt(json.get("q").toString());
      String s = AESUtils.encrypt("解密结果:" + decrypt);
      JSONObject object = new JSONObject();
      object.put("q", s);
      return object.toJSONString();
  }

  
解密结果:爬虫

抓包解析

JS代码

  
window.onload = function () {
  var key = CryptoJS.enc.Utf8.parse("qskS0r_3zNVA``f6");
  var iv = CryptoJS.enc.Utf8.parse("'Z,f[^~Fu8;o{J{w");

  function encrypt(context) {
      var encrypted = '';
      if (typeof(context) == 'string') {

      } else if (typeof(context) == 'object') {
          context = JSON.stringify(context);
      }
      var srcs = CryptoJS.enc.Utf8.parse(context);
      encrypted = CryptoJS.AES.encrypt(srcs, key, {
          iv: iv,
          mode: CryptoJS.mode.CBC,
          padding: CryptoJS.pad.Pkcs7
      });
      return encrypted.toString();
  }

  function decrypt(context) {
      var decrypt = CryptoJS.AES.decrypt(context, key, {
          iv: iv,
          mode: CryptoJS.mode.CBC,
          padding: CryptoJS.pad.Pkcs7
      });
      var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
      return decryptedStr.toString();
  };

  document.getElementById("jiami").onclick = function () {
      var jiamibefore = document.getElementById("jiamibefore").value;
      var jiamiafter = encrypt(jiamibefore);
      document.getElementById("jiamiafter").value = jiamiafter;
  };
  document.getElementById("jiemi").onclick = function () {
      var xhr = new XMLHttpRequest();
      xhr.open("POST", "/decrypt", true);
      xhr.setRequestHeader('content-type', 'application/json');
      xhr.onreadystatechange = function () {
          if (xhr.readyState == 4) {
              var result = JSON.parse(xhr.responseText);
              var jiemiafter = decrypt(result.q);
              document.getElementById("jiemiafter").value = jiemiafter;
          }
      }
      var jiemibefore = document.getElementById("jiemibefore").value;
      var sendData = {'q': jiemibefore};
      xhr.send(JSON.stringify(sendData));
  };
}

加密混淆

(完整代码我放在了笔记文件根目录下,可以学习使用)

前面我们提到了边际效应,就是说,可以到此为止了。后续再投入人力就得不偿失了。除非有专门的对手与你死磕。不过这个时候就是为了尊严而战,不是为了商业因素了。

我抓到你了——然后该怎么办

不会引发生产事件——直接拦截

可能引发生产事件——给假数据(也叫投毒)

此外还有一些发散性的思路。例如是不是可以在响应里做SQL注入?毕竟是对方先动的手。不过这个问题法务没有给具体回复,也不容易和她解释。因此暂时只是设想而已。

1、技术压制

我们都知道,DotA AI里有个de命令,当AI被击杀后,它获取经验的倍数会提升。因此,前期杀AI太多,AI会一身神装,无法击杀。

正确的做法是,压制对方等级,但是不击杀。反爬虫也是一样的,不要一开始就搞太过分,逼人家和你死磕。

2、心理战

挑衅、怜悯、嘲讽、猥琐。

以上略过不提,大家领会精神即可。

3、放水

这个可能是是最高境界了。

程序员都不容易,做爬虫的尤其不容易。可怜可怜他们给他们一小口饭吃吧。没准过几天你就因为反爬虫做得好,改行做爬虫了。

召唤蕾姆
琼ICP备18000156号

鄂公网安备 42011502000211号