C++,python,热爱算法和机器学习
全部博文(1214)
分类: 网络与安全
2016-10-12 19:10:53
本篇我将给大家介绍一下这个极验验证码是什么鬼,要想破解它我们应该分成几个步骤。
首先打开网址:。应该感谢极验给我们提供了一个测试平台,如下图所示:
我们需要知道的事情:
1、极验验证码是以api的方式集成到不同的网站上的,这个api是一个js文件,上图中的“1”处就是这个js文件,我们下载下来看看:
我们需要注意的是:
1、极验验证码是采用api的方式与别的网站集成的,api就是上图中“1”所指的js文件,我们可以看看这个文件:
(function () { var e = function () { var t, n = document.body.getElementsByTagName("script"), r = ''; for (var o = 0; o < n.length; o++) { if (n[o].src == r) { t = n[o]; if (window.Geetest) { new Geetest({ "gt": "a40fd3b0d712165c5d13e6f747e948d4", "feedback": "", "hide_delay": 800, "product": "float", "height": 116, "logo": true, "theme_version": "3.0.21", "id": "a7adbcccc6366d5a96a94aac6b6ad7518", "slice": "pictures/gt/7ed8940e0/slice/89ead30a.png",//小滑块的图片 "theme": "golden", "version": "5.5.16", "https": false, "type": "slide", "show_delay": 250, "xpos": 0, "bg": "pictures/gt/7ed8940e0/bg/89ead30a.jpg",//带洞的背景图 "fullbg": "pictures/gt/7ed8940e0/7ed8940e0.jpg",//完整的背景图 "fullpage": false, "benchmark": false, "ypos": 16, "link": "", "staticservers": ["static.geetest.com/", "dn-staticdown.qbox.me/"], "mobile": false, "challenge": "7adbcccc6366d5a96a94aac6b6ad751837", "apiserver": "", "clean": false }, true).appendTo(t, true) } else { setTimeout(e, 100) } break } } }; …… })();
可以看到这个文件包含了极验验证码的所有参数,页面上的验证码控件只不过是根据这个api的结果加上html渲染出来的而已。
2、验证码控件有两个背景图(一张完整的和一张带洞的)和一个滑块图片,滑块的位置就由图片来指示。值得注意的是,极验验证码的两个背景图并不是一张完整的图片,极验的api给的图片都是混乱的,界面上显示的完整的背景图都是在原图上抠出52个小图片拼接而成的,如下所示:
3、如果想知道滑块的位置,我们只有通过对比两张背景图的像素差异来找的,值得庆幸的是,极验验证码只能水平拖动,所以我们只要找出滑块目标位置的left坐标即可。
4、拿到滑块的目标位置后,我们就要进行验证码破解。目前存在两种方式:一种是研究极验的api,然后收集参数构造post请求到极验的后台进行验证;另一种就是模拟人的操作来拖动鼠标到目标位置来解锁验证码。这两种方式各有利弊,第一种方式需要自己构造参数发送请求,严重依赖于极验的api,如果api稍有变更,那么此方法就会失效;第二种方式需要一个一个模拟浏览器来渲染页面,然后触发鼠标事件来达到目的,它的优点是比较稳定,不用考虑更多的技术细节,再牛逼的验证码总是要让人能通过的,我们模拟人的行为,它估计也没办法不让我们过,它的缺点是需要模拟浏览器来渲染,因此破解时间比较长。为了稳定性,我选择的是第二种方法,以phantomJs作为浏览器引擎,编写js脚本进行交互。
综上所述,我们要想破解这个验证码,我们可以分成几个任务来完成:
1、图片还原:找出两张背景图的原图,根据页面上给的参数生成两张完整的图片。
2、求解滑块位置:对比上一步生成的两张背景图,求解滑块目标位置的left坐标,此坐标便是滑块的目标位移(暂时这么理解,实际有点偏差,后面会讲到)
3、编写js脚本:编写控制整个流程并移动滑块进行验证的js脚本文件
4、模拟器加载页面并执行js脚本进行破解
好了,这一章讲到这里,下一章我将介绍如何实现图片还原和求解滑块位置。
上一章我们讨论了破解极验验证码的思路和步骤,这一章我将介绍如何还原两张背景图和求解滑块的目标位置。
我们首先看看页面上给了我们什么参数:
这个是完整的背景图(fullbg)的页面元素,可以看到他们都是来自于同一张原图,只是截取的位置不同。上图红框就是该小图片在原图中的位置,每一张小图片都是10个像素宽,58个像素高,我们再来看看原图:
确实很乱,根本看不出什么东西。如果我们把这个原图下载下来,然后按照页面上的参数截取一个个10像素宽,58像素高的小图片拼接在一起便可以得到完整的背景图了,上代码:
/** * 合成指定的多张图片到一张图片 * * @param imgSrcList 图片的地址列表 * @param topLeftPointList 每张小图片的偏移量 * @param countOfLine 每行的小图片个数 * @param cutWidth 每张小图片截取的宽度(像素) * @param cutHeight 每张小图片截取的高度(像素) * @param savePath 合并后图片的保存路径 * @param subfix 合并后图片的后缀 * @return 是否合并成功 */ public static boolean combineImages(ListimgSrcList, List topLeftPointList, int countOfLine, int cutWidth, int cutHeight, String savePath, String subfix) { if (imgSrcList == null || savePath == null || savePath.trim().length() == 0) return false; BufferedImage lastImage = new BufferedImage(cutWidth * countOfLine, cutHeight * ((int) (Math.floor(imgSrcList.size() / countOfLine))), BufferedImage.TYPE_INT_RGB); String prevSrc = ""; BufferedImage prevImage = null; try { for (int i = 0; i < imgSrcList.size(); i++) { String src = imgSrcList.get(i); BufferedImage image; if (src.equals(prevSrc)) image = prevImage; else { if (src.trim().toLowerCase().startsWith("http")) image = ImageIO.read(new URL(src)); else image = ImageIO.read(new File(src)); prevSrc = src; prevImage = image; } if (image == null) continue; String[] topLeftPoint = topLeftPointList.get(i); int[] pixArray = image.getRGB(0 - Integer.parseInt(topLeftPoint[0].trim()), 0 - Integer.parseInt(topLeftPoint[1].trim()), cutWidth, cutHeight, null, 0, cutWidth); int startX = ((i) % countOfLine) * cutWidth; int startY = ((i) / countOfLine) * cutHeight; lastImage.setRGB(startX, startY, cutWidth, cutHeight, pixArray, 0, cutWidth); } File file = new File(savePath); return ImageIO.write(lastImage, subfix, file); } catch (Exception ex) { ex.printStackTrace(); return false; } }
带洞的背景图也是一样的处理,现在看看我们还原后的两张背景图:
有了第一步的结果,我们只需要对比两张背景图的像素,从左往右扫描即可找到滑块的目标位置了,还是看代码:
public static int findXDiffRectangeOfTwoImage(String imgSrc1, String imgSrc2) { try { BufferedImage image1 = ImageIO.read(new File(imgSrc1)); BufferedImage image2 = ImageIO.read(new File(imgSrc2)); int width1 = image1.getWidth(); int height1 = image1.getHeight(); int width2 = image2.getWidth(); int height2 = image2.getHeight(); if (width1 != width2) return -1; if (height1 != height2) return -1; int left = 0; /** * 从左至右扫描 */ boolean flag = false; for (int i = 0; i < width1; i++) { for (int j = 0; j < height1; j++) if (isPixelNotEqual(image1, image2, i, j)) { left = i; flag = true; break; } if (flag) break; } return left; } catch (Exception ex) { ex.printStackTrace(); return -1; } } private static boolean isPixelNotEqual(BufferedImage image1, BufferedImage image2, int i, int j) { int pixel1 = image1.getRGB(i, j); int pixel2 = image2.getRGB(i, j); int[] rgb1 = new int[3]; rgb1[0] = (pixel1 & 0xff0000) >> 16; rgb1[1] = (pixel1 & 0xff00) >> 8; rgb1[2] = (pixel1 & 0xff); int[] rgb2 = new int[3]; rgb2[0] = (pixel2 & 0xff0000) >> 16; rgb2[1] = (pixel2 & 0xff00) >> 8; rgb2[2] = (pixel2 & 0xff); for (int k = 0; k < 3; k++) if (Math.abs(rgb1[k] - rgb2[k]) > 50)//因为背景图会有一些像素差异 return true; return false; }
值得注意的是,比较像素的时候要设置一个容差值,可能是两张背景图经过多次处理存在了一定的像素差异,也可能是有个水印。
求解出滑块的目标位置后,我们是不是直接按照这个位移来拖动滑块就行了呢?答案是否定的,看下图:
可以看到在滑动之前滑块与背景图就已经存在一个距离了,需要做一个位移的调整,经过观察,这个值大概是7个像素,因此:最终滑动位移=求解出的滑块left像素个数-7。
下一章我将介绍如何使用模拟浏览器来加载和渲染页面。
前面我们介绍了如何求解极验验证码的滑块目标位移,下面我就就要开始实施拖动滑块破解了。因为我们采取的是模拟人的行为操作,而极验验证码都是js渲染的,因此我们需要一个工具来帮我们完成这个渲染过程得到一个完整的页面,否则一切都是空谈。这里我将使用casperJs+phantomJs来实现目标。
phantomJs号称一个headless的浏览器,也就是包含浏览器内核但是没有界面的浏览器,它是跨平台的,安装很简单,解压到一个目录即可。
casperJs是基于phantomJs的封装,提供了更友好的api和方法,让我们编写脚本更容易。
请各位自行下载这两个东西然后解压在一个目录,我的情况是这样的(exec目录下的casperjs和phantomjs两个文件夹就是下载包解压后的内容):
然后我们编写一个工具类来管理这个无头浏览器的执行:
package com.yay.geetestIdentification.utils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; import java.util.concurrent.Semaphore; /** * 管理Casperjs的启动和执行 * */ public class CasperjsProgramManager { private static Logger logger = LoggerFactory.getLogger(CasperjsProgramManager.class); private static final Semaphore semaphore = new Semaphore(10, true); public static String launch(String jsFileName, Object... params) { if (StringUtils.isBlank(jsFileName)) { logger.error("待执行的js文件名不能为空!"); return null; } try { semaphore.acquire(); String path = CasperjsProgramManager.class.getResource("/").getPath(); path = path.substring(1, path.lastIndexOf("/") + 1); String os = System.getProperties().getProperty("os.name"); String casperJsPath = ""; String phantomJsPath = ""; if (StringUtils.startsWithIgnoreCase(os, "win")) { casperJsPath = path + "casperjs/bin/casperjs.exe"; phantomJsPath = path + "phantomjs/window/phantomjs.exe"; } else { casperJsPath = path + "casperjs/bin/casperjs"; phantomJsPath = path + "phantomjs/linux/phantomjs"; } logger.info("CasperJs程序地址:{}", casperJsPath); ProcessBuilder processBuilder = new ProcessBuilder(casperJsPath, jsFileName); if (params != null) { for (Object param : params) { processBuilder.command().add(String.valueOf(param)); } } processBuilder.directory(new File(path + "casperjs/js")); processBuilder.environment().put("PHANTOMJS_EXECUTABLE", phantomJsPath); Process p = processBuilder.start(); InputStream is = p.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuffer sbf = new StringBuffer(); String tmp = ""; while ((tmp = br.readLine()) != null) { sbf.append(tmp).append("\r\n"); } p.destroy(); semaphore.release(); return sbf.toString(); } catch (Exception ex) { logger.error(ex.getMessage()); return null; } } }
由以上代码我们可以知道我们的前面两个东西为什么要放在特殊的目录下面了,调用很简单:
private static boolean startIdentification(String pageUrl,String domain,String cookies,String jsFileName, String deltaResolveAddress) { String result = CasperjsProgramManager.launch(jsFileName, pageUrl,deltaResolveAddress,domain,cookies, " web-security=no", "ignore-ssl-errors=true"); logger.info("验证码识别结果:\r\n" + result); return result != null && (result.contains("验证通过") || result.contains("不存在极验验证码")); }
经过以上的铺垫,我们就差最后一步了-破解!首选我们来分析一下要做的事情:
1、加载包含验证码的页面,当然是用我们前面讲的phantomaJS来加载啦,因为极验验证码是依赖于js渲染的,我们必须等页面完全渲染完成后再执行拖动
2、收集一些页面的参数发送到java后台服务计算滑块的目标位移并接受结果
3、通过js模拟鼠标事件来实现滑块的移动
4、输出验证结果
好,让我们一步步来讲解如果实现上面的目标。
我们首先新建一个js文件,就叫做geetest_refresh.js好了,我们首先写一些样板代码,比如创建对象,日志处理和接收传进来的参数:
var utils = require('utils'); var casper = require('casper').create({ //clientScripts: ["jquery-2.1.3.min.js"], pageSettings: { javascriptEnabled: true, XSSAuditingEnabled: true, loadImages: true, // The WebPage instance used by Casper will loadPlugins: false, // use these settings userAgent: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36" }, waitTimeout: 10000, exitOnError: false, httpStatusHandlers: { 404: function () { console.log(404); } }, onAlert: function (msg) { console.log(msg); }, onError: function (self, m) { console.log("FATAL:" + m); self.exit(); }, onDie: function () { console.log('dieing'); }, onLoadError: function (casper, url) { console.log(url + ' can\'t be loaded'); }, onPageInitialized: function () { }, onResourceReceived: function () { //console.log(arguments[1]['url'] + ' Received'); }, onResourceRequested: function () { //console.log(arguments[1]['url'] + ' requested'); }, onStepComplete: function () { //console.log('onStepComplete'); }, onStepTimeout: function () { console.log('timeout'); }, logLevel: "debug", // Only "info" level messages will be logged verbose: false // log messages will be printed out to the console }); casper.on('remote.message', function (msg) { this.log(msg, 'info'); }); var pageUrl = casper.cli.get(0);//传进来的页面url var deltaResolveServer = casper.cli.get(1);//传进来的滑块位置求解服务地址 //定义一些内部变量 var id =( new Date()).getTime(); var pageParam = null;
然后是实现第一个目标:加载并渲染页面(这里还对页面做了一个判断,是否包含极验验证码):
casper.start(pageUrl).then(function () { this.wait(5000, function () { //this.echo("等待5秒以便页面充分渲染"); }); }); casper.then(function () { if (!this.exists(".gt_slider_knob")) { this.echo("页面中不存在极验验证码模块"); //this.echo(this.getPageContent()); this.exit(); } });
第二个目标:收集参数请求滑块位置:
casper.waitFor(function check() { return this.evaluate(function () { return (document.querySelectorAll('.gt_cut_bg_slice').length == 52) && (document.querySelectorAll('.gt_cut_fullbg_slice').length == 52);//确保页面已经渲染完成,出现了背景图 }); }, function then() { this.echo("页面渲染成功!"); var styleReg = new RegExp("background-image: url\\((.*?)\\); background-position: (.*?);"); var fullbgSrcArray = []; var fullbgCoordinateArray = []; var fullbgSliceArray = this.getElementsAttribute('.gt_cut_fullbg_slice', 'style'); for (var i = 0; i < fullbgSliceArray.length; i++) { var result = styleReg.exec(fullbgSliceArray[i]); if (result != null) { fullbgSrcArray.push(result[1]); fullbgCoordinateArray.push(result[2]); } else this.echo(fullbgSliceArray[i]); } var bgSrcArray = []; var bgCoordinateArray = []; var bgSliceArray = this.getElementsAttribute('.gt_cut_bg_slice', 'style'); for (var i = 0; i < bgSliceArray.length; i++) { var result = styleReg.exec(bgSliceArray[i]); if (result != null) { bgSrcArray.push(result[1]); bgCoordinateArray.push(result[2]); } } var data = {}; data.fullbgSrcArray = fullbgSrcArray; data.fullbgPositionArray = fullbgCoordinateArray; data.bgSrcArray = bgSrcArray; data.bgPositionArray = bgCoordinateArray; data.itemWidth = 10;//每个小块的宽度(像素) data.itemHeight = 58;//每个小块的高度(像素) data.lineItemCount = 26;//拼图中每行包含的小图片个数 pageParam = data; }, function () { this.echo("等待渲染超时!"); this.exist(); }, 10000); var deltaX = 0; casper.then(function () { if (pageParam == null) { this.echo("收集图片参数失败!"); //this.echo(this.getPageContent()); this.exit(); } this.echo("开始请求滑块位置"); var result = casper.evaluate(function (url, param) { return JSON.parse(__utils__.sendAJAX(url, 'POST', param, false));//ajax请求求解滑块位置 }, deltaResolveServer, {"params": JSON.stringify(pageParam)}); if (result != null && result.status == 1) { deltaX = result.data.deltaX; this.echo("滑块位置求解成功:" + JSON.stringify(result.data)); } else { this.echo("请求滑块位置失败:" + JSON.stringify(result)); this.exit(); } });
其中滑块位置求解后台服务也就一个Spring的一个controller而已:
package com.yay.geetestIdentification.controller; import com.alibaba.fastjson.JSON; import com.yay.geetestIdentification.model.RestFulResult; import com.yay.geetestIdentification.utils.ImageUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.map.HashedMap; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; @RestController public class CaptchaController { @RequestMapping(value = "resolveGeetestSlicePosition", method = RequestMethod.POST) public RestFulResult resolveGeetestSlicePosition(HttpServletResponse response, String params) { response.addHeader("Access-Control-Allow-Origin", "*"); MapparamMap = (Map ) JSON.parseObject(params, Map.class); if (paramMap == null) return RestFulResult.failure("参数不能为空!"); List fullbgSrcList = (List ) paramMap.get("fullbgSrcArray"); List fullbgPositionList = (List ) paramMap.get("fullbgPositionArray"); List bgSrcList = (List ) paramMap.get("bgSrcArray"); List bgPositionList = (List ) paramMap.get("bgPositionArray"); int itemWidth = MapUtils.getIntValue(paramMap, "itemWidth"); int itemHeight = MapUtils.getIntValue(paramMap, "itemHeight"); int lineItemCount = MapUtils.getIntValue(paramMap, "lineItemCount"); try { Assert.notEmpty(fullbgSrcList); Assert.notEmpty(fullbgPositionList); Assert.notEmpty(bgSrcList); Assert.notEmpty(bgPositionList); Assert.isTrue(fullbgSrcList.size() == 52); Assert.isTrue(bgSrcList.size() == 52); Assert.isTrue(itemWidth > 0); Assert.isTrue(lineItemCount > 0); Assert.isTrue(itemHeight > 0); String tmpFolder = System.getProperty("user.dir") + "/tmp/"; File file = new File(tmpFolder); if (!file.exists() && !file.isDirectory()) file.mkdir(); String identification = String.valueOf(System.currentTimeMillis()); String imageSubfix = "jpg"; List fullbgPointList = new ArrayList<>(); for (String positionStr : fullbgPositionList) { fullbgPointList.add(positionStr.replace("px", "").split(" ")); } List bgPointList = new ArrayList<>(); for (String positionStr : bgPositionList) { bgPointList.add(positionStr.replace("px", "").split(" ")); } String fullbgImagePath = tmpFolder + identification + "_fullbg." + imageSubfix; String bgImagePath = tmpFolder + identification + "_bg." + imageSubfix; if (ImageUtils.combineImages(fullbgSrcList, fullbgPointList, lineItemCount, itemWidth, itemHeight, fullbgImagePath, imageSubfix) && ImageUtils.combineImages(bgSrcList, bgPointList, lineItemCount, itemWidth, itemHeight, bgImagePath, imageSubfix)) { int deltaX = ImageUtils.findXDiffRectangeOfTwoImage(fullbgImagePath, bgImagePath); //删除缓存的图片 deleteImage(fullbgImagePath); deleteImage(bgImagePath); Map resultMap = new HashedMap(); resultMap.put("deltaX", deltaX); resultMap.put("deltaY", 0); return RestFulResult.success(resultMap); } else { return RestFulResult.failure("合成图片失败!"); } } catch (Exception ex) { return RestFulResult.failure(ex.getMessage()); } } private void deleteImage(String fullbgImagePath) { File file = new File(fullbgImagePath); // 路径为文件且不为空则进行删除 if (file.isFile() && file.exists()) { file.delete(); } } }
第三个目标,实现滑块移动到目标位置:
var currentTrailIndex = 0; casper.then(function () { if (deltaX <= 0) { this.echo("滑块目标位移为0:处理失败"); this.exit(); } this.echo("开始移动滑块,目标位移为 " + deltaX); currentTrailIndex = this.evaluate(function (selector, deltaX) { var createEvent = function (eventName, ofsx, ofsy) { var evt = document.createEvent('MouseEvents'); evt.initMouseEvent(eventName, true, false, null, 0, 0, 0, ofsx, ofsy, false, false, false, false, 0, null); return evt; }; var trailArray = [ // 算法生成的鼠标轨迹数据,为了不至于给极验团队带来太多的麻烦,我这里就省略了,请大家谅解 ]; var trailIndex = Math.round(Math.random() * (trailArray.length - 1)); var deltaArray = trailArray[trailIndex]; console.log('当前使用轨迹路径:' + (trailIndex + 1)); var delta = deltaX - 7;//要移动的距离,减掉7是为了防止过拟合导致验证失败 delta = delta > 200 ? 200 : delta; //查找要移动的对象 var obj = document.querySelector(selector); var startX = obj.getBoundingClientRect().left + 20; var startY = obj.getBoundingClientRect().top + 18; var nowX = startX; var nowY = startY; console.log("startX:" + startX); console.log("startY:" + startY); var moveToTarget = function (loopRec) { setTimeout(function () { nowX = nowX + deltaArray[loopRec][0]; nowY = nowY + deltaArray[loopRec][1]; //console.log(loopRec + "次移动滑块"); obj.dispatchEvent(createEvent('mousemove', nowX, nowY)); console.log("当前滑块位置:" + obj.getBoundingClientRect().left); if (nowX > (startX + delta - 2)) { obj.dispatchEvent(createEvent('mousemove', startX + delta, nowY)); obj.dispatchEvent(createEvent('mouseup', startX + delta, nowY)); console.log("最终滑块位置:" + obj.getBoundingClientRect().left); } else { moveToTarget(loopRec + 1); } }, deltaArray[loopRec][2]); }; obj.dispatchEvent(createEvent("mousedown", startX, startY)); moveToTarget(2); return trailIndex; }, ".gt_slider_knob", deltaX); }).then(function () { casper.waitForSelectorTextChange('.gt_info_type', function () { var status = this.fetchText('.gt_info_type'); this.echo("验证结果:" + status); this.capture(status.replace(":","_")+ id + "_" + currentTrailIndex + '.png');//对当前页面进行截图以便复查 if (status.indexOf("通过") > -1) { if (this.exists('#verify')) { this.click("#verify"); this.echo("点击成功"); } } }, function () { this.echo("等待滑块移动超时!"); }, 10000); });
代码中的trailArray 保存着到目标位移的移动轨迹数据,也就是说先到哪个位置,再到哪个位置……。大家都知道极验验证码最难的就是对这个轨迹做了行为检测来区分人和机器人,因此这个数据相当重要,为了不给极验团队带来太多麻烦,我这里就省略了,毕竟人家也要吃饭啦。
最好一个目标,执行以上的操作并返回结果:
casper.run();
没错,就一行代码,上面脚本中的所有输出文字都可以在java代码中接收,然后判断是否验证成功,而且可以把验证结果的网页截图保存下来:
private static boolean startIdentification(String pageUrl,String domain,String cookies,String jsFileName, String deltaResolveAddress) { String result = CasperjsProgramManager.launch(jsFileName, pageUrl,deltaResolveAddress,domain,cookies, " web-security=no", "ignore-ssl-errors=true"); logger.info("验证码识别结果:\r\n" + result); return result != null && (result.contains("验证通过") || result.contains("不存在极验验证码")); }
运行结果:
[info] [phantom] Step then 7/10 http://user.geetest.com/login?url= (HTTP 200) 页面渲染成功! [info] [phantom] Step then 7/10: done in 69935ms. [info] [phantom] Step anonymous 8/10 http://user.geetest.com/login?url= (HTTP 200) 开始请求滑块位?? [debug] [remote] sendAJAX(): Using HTTP method: 'POST' 滑块位置求解成功:{"deltaX":119,"deltaY":0} [info] [phantom] Step anonymous 8/10: done in 80502ms. [info] [phantom] Step anonymous 9/10 http://user.geetest.com/login?url= (HTTP 200) 开始移动滑??目标位移?? 119 [info] [phantom] 当前使用轨迹路径:2 [info] [phantom] startX:51.03125 [info] [phantom] startY:292 [info] [phantom] Step anonymous 9/10: done in 80514ms. [info] [phantom] Step anonymous 10/10 http://user.geetest.com/login?url= (HTTP 200) [info] [phantom] Step anonymous 10/10: done in 80528ms. [info] [phantom] Step _step 11/11 http://user.geetest.com/login?url= (HTTP 200) [info] [phantom] Step _step 11/11: done in 80547ms. [info] [phantom] 当前滑块位置:33.03125 [info] [phantom] 当前滑块位置:33.03125 [info] [phantom] 当前滑块位置:34.03125 [info] [phantom] 当前滑块位置:35.03125 [info] [phantom] 当前滑块位置:36.03125 [info] [phantom] 当前滑块位置:37.03125 [info] [phantom] 当前滑块位置:38.03125 [info] [phantom] 当前滑块位置:39.03125 [info] [phantom] 当前滑块位置:40.03125 [info] [phantom] 当前滑块位置:41.03125 [info] [phantom] 当前滑块位置:44.03125 [info] [phantom] 当前滑块位置:46.03125 [info] [phantom] 当前滑块位置:47.03125 [info] [phantom] 当前滑块位置:48.03125 [info] [phantom] 当前滑块位置:49.03125 [info] [phantom] 当前滑块位置:50.03125 [info] [phantom] 当前滑块位置:51.03125 [info] [phantom] 当前滑块位置:53.03125 [info] [phantom] 当前滑块位置:55.03125 [info] [phantom] 当前滑块位置:56.03125 [info] [phantom] 当前滑块位置:58.03125 [info] [phantom] 当前滑块位置:60.03125 [info] [phantom] 当前滑块位置:61.03125 [info] [phantom] 当前滑块位置:64.03125 [info] [phantom] 当前滑块位置:66.03125 [info] [phantom] 当前滑块位置:67.03125 [info] [phantom] 当前滑块位置:68.03125 [info] [phantom] 当前滑块位置:69.03125 [info] [phantom] 当前滑块位置:71.03125 [info] [phantom] 当前滑块位置:73.03125 [info] [phantom] 当前滑块位置:75.03125 [info] [phantom] 当前滑块位置:76.03125 [info] [phantom] 当前滑块位置:77.03125 [info] [phantom] 当前滑块位置:78.03125 [info] [phantom] 当前滑块位置:79.03125 [info] [phantom] 当前滑块位置:81.03125 [info] [phantom] 当前滑块位置:83.03125 [info] [phantom] 当前滑块位置:85.03125 [info] [phantom] 当前滑块位置:86.03125 [info] [phantom] 当前滑块位置:87.03125 [info] [phantom] 当前滑块位置:88.03125 [info] [phantom] 当前滑块位置:89.03125 [info] [phantom] 当前滑块位置:90.03125 [info] [phantom] 当前滑块位置:91.03125 [info] [phantom] 当前滑块位置:92.03125 [info] [phantom] 当前滑块位置:94.03125 [info] [phantom] 当前滑块位置:95.03125 [info] [phantom] 当前滑块位置:96.03125 [info] [phantom] 当前滑块位置:97.03125 [info] [phantom] 当前滑块位置:98.03125 [info] [phantom] 当前滑块位置:100.03125 [info] [phantom] 当前滑块位置:103.03125 [info] [phantom] 当前滑块位置:104.03125 [info] [phantom] 当前滑块位置:105.03125 [info] [phantom] 当前滑块位置:106.03125 [info] [phantom] 当前滑块位置:108.03125 [info] [phantom] 当前滑块位置:110.03125 [info] [phantom] 当前滑块位置:114.03125 [info] [phantom] 当前滑块位置:116.03125 [info] [phantom] 当前滑块位置:118.03125 [info] [phantom] 当前滑块位置:119.03125 [info] [phantom] 当前滑块位置:121.03125 [info] [phantom] 当前滑块位置:122.03125 [info] [phantom] 当前滑块位置:124.03125 [info] [phantom] 当前滑块位置:125.03125 [info] [phantom] 当前滑块位置:127.03125 [info] [phantom] 当前滑块位置:129.03125 [info] [phantom] 当前滑块位置:130.03125 [info] [phantom] 当前滑块位置:134.03125 [info] [phantom] 当前滑块位置:135.03125 [info] [phantom] 当前滑块位置:136.03125 [info] [phantom] 当前滑块位置:137.03125 [info] [phantom] 当前滑块位置:138.03125 [info] [phantom] 当前滑块位置:139.03125 [info] [phantom] 当前滑块位置:140.03125 [info] [phantom] 当前滑块位置:142.03125 [info] [phantom] 最终滑块位??143.03125 [info] [phantom] waitFor() finished in 1913ms. [info] [phantom] Step anonymous 12/12 http://user.geetest.com/login?url= (HTTP 200) 验证结果:验证通过: [debug] [phantom] Capturing page to D:/yayCrawler/demo/GeetestIdentification/target/classes/casperjs/js/验证通过_1467992089127_1.png
验证通过的截图为: