跳至主要内容

爬取 DDos 保护的站点

准备抓取一个网站,但是发现启用DDOS保护,感觉有点麻烦了。
打开浏览器F12,发现要求你等待几秒钟检测浏览器,然后通过302重定向到正确的网页。

源码查看

打开html源码,可以看到主要是下面的这个代码:
还有在JS中使用到的Element:
源码分享
上面代码的作用,主要是原来判断访问站点的客户端是否是一个真实的浏览器。访问网站主页的时候,首先得到的是一个 503 错误,显示上面的页面内容。然后等待 5 秒,让用户看清楚提示信息,然后再根据得到的随机值东拼西凑计算出另一个值,写入 jschl-answer 中,最后通过 challenge-form 这个表单提交。
提交成功之后, /cdn-cgi/l/chk_jschl 会使用 302 重定向返回真实的网页内容。cgksoUW 是一个随机的名称,pass 那个 input 中包含的值也是一个变化的值。每次进入这个页面,这两个值都会变化。
那么,pass 的值在提交的时候到底是如何计算出来的?
从 f = document.getElementById('challenge-form'); 下面的那段代码可以看出,这个计算无非是对 cgksoUW.bRM 进行一些加乘运算。然后再转成一个 Int 进行提交。关键在于那些看起来莫名其妙的加号、括号和方括号的集合的含义为何?
这是一个简单的障眼法。
让我们看看 cgksoUW.bRM 的初始值,这个表达式的结果等于数字 12 

我们把上面的代码拆成几个子表达式,就好理解了。
首先是第一个嵌套括号中比如这样的: (+!![]+[]),打开浏览器的 console ,在其中输入 +!![] 回车,可以得到数字 1 。
先不管哪个加号,单独看 !![] 的值,它是布尔值 true 。这是因为 [] 返回的是一个有效的 Array,代表 true ,两次取反,依然是 true 。
而左边的那个加号由于没有提供左操作数,因此它的含义是正数,这里做了一次数字转换,自动把 true 转换成了 1 。
接着往后算,后面是个加号,然后是个 [] 。空的数组,默认是作为字符串处理的。[].toString() 的值为空字符串。前面的 1 加上后面的空字符串,得到一个字符串 "1" 。
知道了原理,可以使用同样的方式算出第二个嵌套括号中的值为数字 2 。
接下来是字符串 "1" 加上数字 2,得到字符串 "12" 。
最左边的加号又做了一次转换,将字符串 "12" 转换成了数字 12 。
所以,这一段看似乱码的代码,只是为了获取一些随机的数字,便于和服务器验证罢了。我判断这些数值都是有意义的,而且和服务器时间关联,用来判断访问网站的是不是标准的浏览器。

方案选择

分析完之后,就要进行技术的选择了。写爬虫自然是首选 Python 。开始,我准备使用 requests 来模拟请求,使用 BeautifulSoup4 来解析得到的 HTML,然后进行资源的下载。但 requests 并不擅于模拟浏览器的行为,也不能执行 Javascript,还需要用 PyV8 等库来执行上面的数据计算,得到最终的 pass 。PyExecJS 可以实现在 Python 环境中调用 Javascript 引擎,支持的引擎有 PyV8Node.js 等等。成功跳过 DDos 防护之后,还需要保持 cookies,构建 heads 。既然计算部分要依赖别的引擎解析 Javascript ,那还不如直接使用 Javascript 来写好了。这就是 Headless Browser 大显身手的时候了。

Headless Browser

Headless Browser ,通俗地来说就是个无界面且完整功能的浏览器。目前最流行的 Headless Browser 框架非 PhantomJS 莫属。它是基于 WebKit 开发的,想要整个网页截图神马的(再长也不怕!)用它就最好了。不过,用 PhantomJS 保存网页中的图片 还真是有点麻烦。由于上面的 DDOS 防护的存在,每个单独的图像文件依然存在防护,所以不能拿到地址后直接下载,必须使用浏览器来浏览这个图片。于是我选择了另一个框架 SlimerJS ,它和 PhantomJS 功能基本一致,API 也基本一致,所不同的就是它使用 Mozilla Firefox 的 Gecko 引擎。或者选择Qt编写一个简单的浏览器,一样可以解决。

评论

此博客中的热门博文

QtWebkit开发爬虫

由来 本文主要介绍以下动态网页爬取方法中的一种,在 web2.0 时代,很多都使用异步加载技术。导致有用信息获取不到,那怎么办了?不要慌,肯定是有办法的。 别废话了。 QtWebKit 是安装了Qt后内置封装好的浏览器内核。需要详细了解的可以到Qt官网 查看 ,有了浏览器内核后,就可以通过它实现分析ajax的连接,或者js事件。可能会有人会说,不是可以使用现在google出的 HeadlessChrome 。或者 selenium 操作浏览器或 phantom.js 。我觉得你还是太年轻,比如下面这种情况,你怎么解决? (逃~,如果你有什么好的方法一定记得告诉我) $( document ).ready( function ( ) { // 真假浏览器检查 var detect = false ; // 默认大家都是好学生,没有被老师发现上课时间在完爬虫…… if (navigator.webdriver || window .webdriver) { detect = true ; // 你竟然是用webdriver驱动的!Headless Chrome 同学,你被老师发现啦…… } if ( window .outerWidth === 0 || window .outerHeight === 0 ){ // 窗口的外部宽度和高度为零的同学,你很值得怀疑哦…… try { var canvas = document .createElement( 'canvas' ); var ctx = canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ); var exts = ctx.getSupportedExtensions(); } catch (e) { detect = true ; // 你竟然不支持WebGL!PhantomJS 同学,你被老师发现啦…… } } // 好啦,Headless Chrome 和 PhantomJS 两位同学已经被老师罚站去了。其他同学要...

PyCurl使用方法

主要介绍怎么使用pycurl的基本使用方法,参考官网的实例代码例举!!! 基本使用方法 c = pycurl.Curl() #创建一个curl对象 c.setopt(pycurl.CONNECTTIMEOUT, 5 ) #连接的等待时间,设置为0则不等待 c.setopt(pycurl.TIMEOUT, 5 ) #请求超时时间 c.setopt(pycurl.NOPROGRESS, 0 ) #是否屏蔽下载进度条,非0则屏蔽 c.setopt(pycurl.MAXREDIRS, 5 ) #指定HTTP重定向的最大数 c.setopt(pycurl.FORBID_REUSE, 1 ) #完成交互后强制断开连接,不重用 c.setopt(pycurl.FRESH_CONNECT, 1 ) #强制获取新的连接,即替代缓存中的连接 c.setopt(pycurl.DNS_CACHE_TIMEOUT, 60 ) #设置保存DNS信息的时间,默认为120秒 c.setopt(pycurl.URL, "http://www.baidu.com" ) #指定请求的URL c.setopt(pycurl.USERAGENT, "Mozilla/5.2 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50324)" ) #配置请求HTTP头的User-Agent c.setopt(pycurl.HEADERFUNCTION, getheader) #将返回的HTTP HEADER定向到回调函数getheader c.setopt(pycurl.WRITEFUNCTION, getbody) #将返回的内容定向到回调函数getbody c.setopt(pycurl.WRITEHEADER, fileobj) #将返回的HTTP HEADER定向到fileobj文件对象 c.setopt(pycurl.WRITEDATA, fileobj) #将返回的HTML内容定向到f...