最近需要取一些数据,这个数据只有在注册状态下才能得到。 调查了很多爬行动物的反爬技术的攻防,采用比较下层的爬行动物框架虽然速度快、扩展性好,但是成本高,目标网站任何变更都可以瓦解整个爬行动物,所以在维护上很辛苦。 但是,我的需求不是大量的数据,而是“爬”,所以最后选择了硒。
关于认证码,登录时发现需要验证认证码。 验证码的本质是识别机器和人,这本身就是一场攻坚战,理论上足够像人就足够了。 剩下的就是爬虫如何能让人看起来更像人这一话题,玄学成了话题,所以验证码的设计和爬虫这种猫鼠游戏其实很有趣。
目前常见的认证码主要分为照片字幕、鼠标轨迹捕捉、字符组合、以及反人类知识认知解答(如12306 )等,连人类都很难识别。 由于12306的垄断性质,虽然很极端,但即便如此,也产生了人工打码、雇佣人类进行识别的特殊职业。 据我所知,在网上所谓的家里点一点鼠标就能赚钱
大多数互联网企业倾向于在用户体验和验证码识别机器与人之间取得平衡。 毕竟,在互联网公平竞争的时代,用户体验是王道。 这次需要破解的是幻灯片验证码。
就是这样的验证码。
整理这个验证码如何识别,就是按住下面的滑块,移动图像中的滑块使之与背景图的压痕一致,使之与完整的图像一致。 不仅如此,整个幻灯片的操作本身也必须是适合健康者的一个操作。 例如,普通人一般不会瞬间使幻灯片一致,而是在靠近缺失的块附近,然后慢慢准确地一致。
要使技术分析模拟幻灯片,selenium本身首先支持按下鼠标并滑动,然后松开鼠标。 这个请参照selenium的文档。
要拉伸滑块的长度,需要分析整个图像中缺陷的位置。 此外,该像素的长度不是滑块的长度,而是缩放的过程。 因此,第一步需要计算凹陷在整个图像中所占的百分比,然后将滑块的像素长度乘以比率以获得最终的幻灯片。 公式:滑块比例*滑块长度=滑块像素长度。
如何分析图像凹陷的位置? 要分析图像,首先需要获得图像。 selenium path从页面获取验证码图像。 因为图像本身由像素点构成,所以打算从分析各像素点的特征开始。 在我看来,图像是二维数组,每个点都是RGB值。 应该有某种方法将图像解析为此RGB值的二维数组。 因此,当我去寻找开源库时,我发现JDK自带就可以分析图像。
将图像解析成二维阵列,分析图像的特征,发现分析这两条边的位置,其实可以求平均,找到压痕中央的点。 这两条垂直的白线有什么特征呢? 说到“白”,RGB一定有一定的特征。 根据该特征识别这几点,通过计算得到这两条竖线的横坐标。
另外,通过计算位置相对于整个图像长度的比例,并在selenium中获得滑动条的长度,可以计算滑动条的长度。
模拟幻灯片。 这个过程模拟人的幻灯片。 创建随机数散列,而不是简单的等速。 动态剩余长度的随机数幻灯片具有速度变慢的效果,因为剩余长度越小,随机数的范围也越窄,从而欺骗了检测。
代码实战/** *幻灯片验证码破解的处理*/public boolean skipRobotTest (() try ) /图片imagedownload=new image download ); webelementimg=getrobottestimage (; string imgurl=img.getattribute (src ); image download.dowload image (imgurl,CURRENT_ROBOT_TEST_IMAGE ); //获取比率doublesliderate=getsliderate (current _ robot _ test _ image ); //滑杆movebutton(sliderate ); }catch(throwablee ) { return true; } return false; }为了保证破解验证码的稳定性,避免破解程序一次崩溃的尴尬,我们可以方便地调用外层编写重试逻辑:
while (! skipRobotTest () (logutils.print ) skiprobotwillretry ); 连续; }logutils.print(loginsuccess ); 下一个很棒的环节:
每个像素点的RGB值实际上有4个字节的混合值,因此需要进行位运算才能分别获得相应的3个点值。
privatedoublegetsliderate (string imagename ) { try {
/* 获取图片的长宽,构建二维数组遍历 */ BufferedImage image = ImageIO.read(new File(ChromeSupport.INS_PATH + imageName + “.jpeg”)); int width = image.getWidth(); int height = image.getHeight(); // 标记较白的点为凹陷轮廓边缘点 List<Integer> widthEdgeList = Lists.newArrayList(); for (int i = 0;i < width;i ++) { for (int j = 0; j < height; j++) { // 这里分别获取RGB值的,红,绿,蓝 的值 int rgb = image.getRGB(i, j); int redV = (rgb & 0xff0000) >> 16; int greenV = (rgb & 0xff00) >> 8; int blueV = (rgb & 0xff); // 分析发现,数值越大,越接近白色,因此这里分别判断三个值,达到230即可标记为一个较白的点 if (redV > 230 && greenV > 230 && blueV > 230) { widthEdgeList.add(i); } } } /* 统计凹陷最多的横坐标 */ Map<Integer, List<Integer>> map = widthEdgeList.stream().collect(Collectors.groupingBy(Integer::intValue)); ArrayList<List<Integer>> lists = Lists.newArrayList(map.values()); Collections.sort(lists, (o1, o2) -> { if (o1.size() > o2.size()) { return -1; } else if (o1.size() < o2.size()) { return 1; } return 0; }); // 左竖线横坐标 int leftEdge = lists.get(0).get(0); // 右竖线横坐标 int rightEdge = lists.get(1).get(0); // 得到凹陷正中央 int slidePixel = (rightEdge + leftEdge) / 2; // 滑动比例 double rate = Double.valueOf(slidePixel) / Double.valueOf(width); // 柔性调整 return 美好的豌豆Rate(rate); } catch (IOException e) { LogUtils.errorPrint(e, “get kill robot rate error”); } return 0; }
关于柔性调整,最终实验发现光滑动条*比例还不够,验证码会在越偏离整个背景图片的正中央位置进行放大比例,这个柔性比例就是在比例在某个区间的时候去进行同步放大一定的比例,去抵消被放大的比例。这个方法很简单粗暴,就是if else去调整,根据识别的成功率逐渐完善:
/** * 发现会根据50%的偏移量需要增量 * @param rate * @return */ private double 美好的豌豆Rate(double rate) { double originRate = rate; if (rate < 0.45) { rate -= 0.02; } if (rate >= 0.45 && rate < 0.6) { return rate; } else if (rate >= 0.6 && rate < 0.67) { rate += 0.02; } else if (rate >= 0.67 && rate < 0.75){ rate += 0.02; } else if (rate >= 0.75 && rate < 0.8){ rate += 0.05; } else { rate += 0.07; } LogUtils.print(“kill robot slide rate %s, 美好的豌豆 rate %s”, originRate, rate); return rate; }
准备好了所有数据,然后开始滑动操作:
private void moveButton(double slideRate) { // 获取滑动条点击样式元素 WebElement moveButtonEl = webDriver.findElement(By.xpath(“xxxxxxx”)); Actions moveAction = new Actions(webDriver); moveAction.clickAndHold(moveButtonEl); // 540为滑动条的全部长度,随机滑动步数 int targetMoveCount = (int) (540 * slideRate); // getRandomStep获得随机长度移动数组 for (Integer count : getRandomStep(targetMoveCount)) { moveAction.moveByOffset(count, 0); moveAction.perform(); } moveAction.release(moveButtonEl).perform(); }
随机长度生成规则:
public List<Integer> getRandomStep(int targetMoveCount) { List<Integer> list = Lists.newArrayList(); while (targetMoveCount > 0) { // 剩余长度生成随机数 int count = RandomUtils.nextInt(0, targetMoveCount + 1); list.add(count); // 得到剩余长度 targetMoveCount -= count; } return list; }
完成
总结
由于涉及目标网站,所以这里就不展示效果,得到这样的优化之后,成功率基本达到100%,整个过程学习到不少图片识别OCR相关的知识,也熟悉了爬虫框架,甚至了解了人体行为学的东西,还是挺好玩的。