我们在性能测试中总会时不时地遭遇到来自于应用系统的各种阻碍,图片验证码就是一类最常见的束缚,登录或交易时需要按照图片中的内容输入正确的验证信息后,数据才可以提交成功,这使得许多性能测试工具只能望而却步。网上也出现了一些LoadRunner的解决方案,但结合LoadRunner对于C脚本内存控制和识别成功率低下等诸多问题,这些方案没有什么实际用途。然而,为JMeter开发插件却给我们提供了一条可行的道路来冲破图片验证码的束缚!

选择一个理想的第三方图形图像识别工具

在此我们首先需要一个比较理想的图形图像识别工具来完成将验证码中的图形图像文字识别转换为文本文字主体识别工作,在此我们选择Tesseract, Tesseract是一个开源的OCR(Optical Character Recognition,光学字符识别)引擎,可以识别多种格式的图像文件并将其转换成文本,发布在Googel Project上,地址为http://code.google.com/p/tesseract-ocr/(但Googel Project停止维护后不知道现在在哪里维护)。

一组用于验证码识别的JMeter插件

我们常见的验证码图片样本如下:

1. 降噪

当你遇到这样的验证码时,首先你要做的就是降噪,将背景的一些干扰我们识别文本内容的线条过滤掉,人眼需要降噪,识别软件在进行识别前也需要帮助其进行降噪来加大识别成功率,通常降噪的方案是对图片像素点进行逐个扫描,通过创建降噪规则对背景噪音进行过滤,如上面的样本,我们可以建立如下降噪规则和方法:

public static int isFilter(intcolorInt) {

Color color= newColor(colorInt);if ((color.getRed() > 85 && color.getRed() < 255)&& (color.getGreen() > 85 && color.getGreen() < 255)&& (color.getBlue() > 85 && color.getBlue() < 255)) {return 1;

}return 0;

}public staticBufferedImage removeBackgroud(BufferedImage img)throwsException {int width =img.getWidth();int height =img.getHeight();for (int x = 0; x < width; ++x) {for (int y = 0; y < height; ++y) {if (isFilter(img.getRGB(x, y)) == 1) {

img.setRGB(x, y, Color.WHITE.getRGB());

}

}

}returnimg;

}

可以看到效果非常明显,但降噪也有它的局限性,比如会把一些需要正常显示的图形文字过滤掉一部分,诸如此类问题我们会在后面的介绍中通过其他方式解决,但对于图形图像识别软件的输入来说,必须对其加以降噪才能保证读取正确率。

2. 识别插件(第一个Extractor插件)

我们在最初的章节介绍了Extractor的基本实现方法,在此我们还是简单回顾一下后置处理器的一些功能,下图显示了JMeter为我们默认提供的后置处理器:

所谓后置处理器是相对Sampler的后置,主要用于处理Sampler所抽样得到的SamplerResult对象,对SamplerResult做修饰或通过SamplerResult抽取信息,最常使用的是“正则表达式提取器”、“CSS/JQuery Extractor”、“XPath Extractor”,使用它们可以实现性能测试脚本中最重要的“关联”操作。

好了,我们的需求是对验证码进行读取,即通过验证码URL获取到图片资源(这部分由“HTTP请求Sampler”完成),然后提取资源中的图形图像信息作为Tesseract的输入,最后在将Tesseract的输出作为一个JMeter参数数据进行保存。惯例使用分离法,分为逻辑控制部分VcodeExtractor和GUI部分VcodeExtractorGUI,另外,还包括对图片进行处理的ImageIOHelper类以及实现调用Tesseract对验证码信息识别并读取的OCR类。

ImageIOHelper主要包含两大部分,一部分就是前面所介绍的降噪逻辑,另一部分是将图片格式转换为tiff格式以更好地进行识别,这部分的代码参考如下:

public staticFile createImage(File imageFile, String imageFormat) {

File tempFile= null;

ImageInputStream iis= null;

ImageOutputStream ios= null;

ImageReader reader= null;

ImageWriter writer= null;try{

Iterator readers =ImageIO.getImageReadersByFormatName(imageFormat);

reader=readers.next();

iis=ImageIO.createImageInputStream(imageFile);

reader.setInput(iis);

IIOMetadata streamMetadata=reader.getStreamMetadata();

TIFFImageWriteParam tiffWriteParam= newTIFFImageWriteParam(Locale.CHINESE);

tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);

Iterator writers = ImageIO.getImageWritersByFormatName("tiff");

writer=writers.next();

BufferedImage bi= removeBackgroud(reader.read(0));

IIOImage image= new IIOImage(bi,null,reader.getImageMetadata(0));

tempFile=tempImageFile(imageFile);

ios=ImageIO.createImageOutputStream(tempFile);

writer.setOutput(ios);

writer.write(streamMetadata, image, tiffWriteParam);

}catch(IOException e) {

e.printStackTrace();

}catch(Exception e) {//TODO Auto-generated catch block

e.printStackTrace();

}finally{if(iis != null){try{

iis.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}if(ios != null){try{

ios.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}if(writer != null){

writer.dispose();

}if(reader != null){

reader.dispose();

}

}returntempFile;

}private staticFile tempImageFile(File imageFile) {

String path=imageFile.getPath();

StringBuffer strB= newStringBuffer(path);return new File(strB.toString().replaceFirst("jpg", "tif"));

}

OCR类主要是通过Process调用已经安装的Tesseract程序,调用命令基本形式为tesseract xxx.tif 1 -l eng,参考如下代码:

importjava.io.BufferedReader;importjava.io.File;importjava.io.FileInputStream;importjava.io.IOException;importjava.io.InputStreamReader;importjava.util.ArrayList;importjava.util.List;public classOCR {private final String LANG_OPTION = "-l";private final String EOL = System.getProperty("line.separator");private String tessPath = "D://Program Files (x86)//Tesseract-OCR";publicString recognizeText(File imageFile,String imageFormat) {

File tempImage=ImageIOHelper.createImage(imageFile,imageFormat);

File outputFile= new File(imageFile.getParentFile(),"output" +imageFile.getName());

StringBuffer sb= newStringBuffer();

List cmd = new ArrayList();

cmd.add(tessPath+"//tesseract");

cmd.add("");

cmd.add(outputFile.getName());

cmd.add(LANG_OPTION);

cmd.add("eng");

ProcessBuilder pb= newProcessBuilder();

pb.directory(imageFile.getParentFile());

cmd.set(1, tempImage.getName());

pb.command(cmd);

pb.redirectErrorStream(true);

Process process= null;

BufferedReader in= null;intwait;try{

process=pb.start();//tesseract.exe xxx.tif 1 -l eng

wait =process.waitFor();if(wait == 0){

in= new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()+".txt"),"UTF-8"));

String str;while((str = in.readLine())!=null){

sb.append(str).append(EOL);

}

in.close();

}else{

tempImage.delete();

}new File(outputFile.getAbsolutePath()+".txt").delete();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}catch(InterruptedException e) {//TODO Auto-generated catch block

e.printStackTrace();

}finally{if(in != null){try{

in.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

tempImage.delete();returnsb.toString();

}

}

VcodeExtractor类继承AbstractScopedTestElement抽象类,实现PostProcessor接口的process方法,来处理利用OCR读取验证码信息的逻辑控制,参考代码如下:

importjava.io.File;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.Serializable;importorg.apache.jmeter.processor.PostProcessor;importorg.apache.jmeter.samplers.SampleResult;importorg.apache.jmeter.testelement.AbstractScopedTestElement;importorg.apache.jmeter.threads.JMeterContext;importorg.apache.jmeter.threads.JMeterVariables;importorg.apache.jorphan.logging.LoggingManager;importorg.apache.log.Logger;public class VcodeExtractor extends AbstractScopedTestElement implementsPostProcessor, Serializable{private static final Logger log =LoggingManager.getLoggerForClass();

@Overridepublic voidprocess() {//TODO Auto-generated method stub

JMeterContext context =getThreadContext();

SampleResult previousResult=context.getPreviousResult();if (previousResult == null) {return;

}

log.debug("VcodeExtractor processing result");

String status=previousResult.getResponseCode();int id =context.getThreadNum();

String imageName= id + ".jpg";if(status.equals("200")){byte[] buffer =previousResult.getResponseData();

FileOutputStream out= null;

File file= null;try{

file= newFile(imageName);

out= newFileOutputStream(file);

out.write(buffer);

out.flush();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}finally{if(out != null){try{

out.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}

}try{

String vcode= new OCR().recognizeText(file, "jpg");

vcode= vcode.replace(" ", "").trim();

JMeterVariables var=context.getVariables();

var.put("vcode", vcode);

var.put("vuser", String.valueOf(id));

}catch(Exception e) {

e.printStackTrace();

}

}

}

}

代码逻辑非常简洁,即通过getThreadContext()方法获取当前线程(vuser)的上下文,从而从上下文中获取到前一个Sampler所抽样的结果,为保证结果不为空我们做了一个简单的处理,也可以添加一些更为精细的控制,如下代码:

if(context.getPreviousSampler() instanceofHTTPSampler){return;

}

判断前一个Sampler是否为HTTPSampler,以限定有效使用范围。

将previousResult.getResponseData()保存为文件后,通过前面我们创建的OCR完成识别任务后,将识别结果通过JMeterVariables对象保存下来,在此我们分别建立了两个参数”vcode”和”vuser”,后面我们可以用它们进行测试。

该版本的VcodeExtractorGUI类只是单纯实现一个可视化的界面用于在测试计划Tree中进行操作:

importorg.apache.jmeter.processor.gui.AbstractPostProcessorGui;importorg.apache.jmeter.testelement.TestElement;public class VcodeExtractorGUI extendsAbstractPostProcessorGui{

@OverridepublicTestElement createTestElement() {//TODO Auto-generated method stub

VcodeExtractor extractor = newVcodeExtractor();

modifyTestElement(extractor);returnextractor;

}

@OverridepublicString getLabelResource() {//TODO Auto-generated method stub

return this.getClass().getName();

}

@Overridepublic String getStaticLabel() {//设置显示名称//TODO Auto-generated method stub

return "VcodeExtractor";

}

@Overridepublic voidmodifyTestElement(TestElement extractor) {//TODO Auto-generated method stub

super.configureTestElement(extractor);

}

}

这意味着识别存在错误!

3. 提高识别成功率

识别成功率是成败的关键,提升成功率可以采取以下方案:

训练Tesseract

提供大量样本来训练Tesseract对特定图形的识别成功率。

修正错误的识别结果

有些识别错误是这样的,如:

将J识别为[,将M识别为|\/|,将N识别为||,这种识别错误是机器识别离散一些的像素点产生的,人眼是可以修正的,因此,我们可以建立映射表方式将错误字符进行修正。

避免混淆形状接近的图形字符

有些识别错误是这样的,如:

将5识别为S,将1识别为I,将0识别为O,这种识别错误是纯的图形混淆产生的,人眼也可能犯此类错误,我们管它叫“看不清”。

4. 看不清,换一张(第一个Controller插件)

“看不清,换一张”无论对人眼或机器识别都是一种弥补方案,我们对于“看不清”的字符需要模拟换一张重新识别的操作,这里我们引入一个新的插件Controller(逻辑控制器),照例我们先来回顾一下该插件的一些功能,下图显示了JMeter为我们默认提供的逻辑控制器:

前面的章节曾经介绍过所谓逻辑控制器主要就是用来控制线程行为的,当然也包括一些用于划分Sampler或功能边界的控制器如事务控制器和录制控制器,主要是依靠一些限定的条件或阈值的判断,按想要的方式控制总体线程或单独线程行为。

好了,我们的需求很明确“看不清,换一张”,在此可以完全照搬循环控制器的源代码,参考LoopController类和LoopControlPanel类,只需要对LoopController在每次循环结束后判断是否退出的函数中增加我们对于图片是否看清的逻辑,代码如下:

private final static String PATTERN = "34789ABCEFHKLPRTUVWXY"

private booleanisVerify(String vcode){int length =vcode.length();//对长度进行判断

if(length != 4){return false;

}//对内容进行判断

for(int i = 0; i < length; i++){if(PATTERN.indexOf(vcode.toCharArray()[i]) < 0){return false;

}

}return true;

}

@OverridepublicSampler next() {

JMeterContext context=getThreadContext();

JMeterVariables var=context.getVariables();

String vcode= var.get("vcode");if(vcode != null){if(isVerify(vcode)){

setDone(true);return null;

}

}if(endOfLoop()) {if (!getContinueForever()) {

setDone(true);

}return null;

}return super.next();

}

如果通过isVerify函数校验(看得清楚)就直接退出循环,否则(看不清楚)就接着重新请求图片验证码进行校验(换一张),创建此逻辑控制器VcodeVerifyController。

将插件打包插入JMeter框架,可以在逻辑控制器列表中查看到VcodeVerifyController组件:

全部通过了登录验证,但根据测试发现识别率成功基本在75%左右,因此,还需要进一步完善,第一是通过改进识别逻辑,第二是增加一个验证码如果识别错误重新进行识别提交登录事务的过程控制。

java自动识别图片验证码插件_JMeter开发插件——图片验证码识别相关推荐

  1. 微信直接回复图片+php,php微信开发之图片回复功能

    本文实例为大家分享了php微信图片回复功能的具体代码,供大家参考,具体内容如下 /** *wechatphptest */ //defineyourtoken define("TOKEN&q ...

  2. Android组件化和插件化开发

    项目发展到一定程度,就必须进行模块的拆分.模块化是一种指导理念,其核心思想就是分而治之.降低耦合.而在 Android 工程实践,目前有两种途径,一个是组件化,一个是插件化. 组件化开发 说起组件化少 ...

  3. Android组件化和插件化开发,android开发工程师月薪

    开发调试时不需要对整个项目进行编译,每个模块可独立编译,提高了编译速度. 多人合作时可以只关注自己的业务模块,把某一业务当成单一项目来开发,可以提升开发,测试效率. 可以灵活的对业务模块进行组装和拆分 ...

  4. 如何为Apache JMeter开发插件(三)——冲破图片验证码的束缚

    我们在性能测试中总会时不时地遭遇到来自于应用系统的各种阻碍,图片验证码就是一类最常见的束缚,登录或交易时需要按照图片中的内容输入正确的验证信息后,数据才可以提交成功,这使得许多性能测试工具只能望而却步 ...

  5. linux下tomcat6无法显示图片验证码 少了图形插件

    linux下tomcat6无法显示图片验证码(windows下显示正常) 原创 2015年10月20日 10:31:47 3526 linux下tomcat6无法显示图片验证码(windows下显示正 ...

  6. 阿里巴巴JAVA开发手册及开发插件

    JAVA开发手册 https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%8 ...

  7. jQuery 图片查看插件 Magnify 开发简介(仿 Windows 照片查看器)

    前言 因为一些特殊的业务需求,经过一个多月的蛰伏及思考,我开发了这款 jQuery 图片查看器插件 Magnify,它实现了 Windows 照片查看器的所有功能,比如模态窗的拖拽.调整大小.最大化, ...

  8. java做图插件_java报表开发插件制作双轴图

    工具/原料 java报表开发插件:FineReport 大小:148.2M 适用平台:windows/linux 问题描述 多种图表类型,可在一张图表混合显示.如图表中既存在柱形图,又有折线图或面积图 ...

  9. 开发AndroidStudio图片压缩插件TinyPngPlus

    相信很多人都遇到切图过大需要压缩后再使用的问题.少的话,还好说,一旦多起来,那种感觉又上来了- 于是谷歌.百度大法找了一通轮子.有!但不太符合自己的使用习惯.还不如就此造一个,顺便练习一下插件的开发与 ...

最新文章

  1. ASP.NET mvc 自定义验证和Filter过滤器传参
  2. laravel批量插入报错:1292: Incorrect datetime value: '0000-00-00 00:00:00' for column 'TERM_DATE'
  3. 课时5:闲聊之Python的数据类型
  4. 怎么查看地址值_西门子S7-200 SMART PID回路控制,配置PID向导,查看项目组件
  5. IE11下用forms身份验证的问题
  6. Nginx学习笔记(五) 源码分析内存模块内存对齐
  7. 模拟ctrl+alt+delete三键
  8. 网页空间 - 概念篇
  9. 句柄与指针的区别(一)
  10. php怎么去除内容,php怎么把html标签去除?
  11. java为什么要分为service层,dao层,controller层
  12. js脚本页面自动刷新
  13. mysql 源码阅读_mysql 源码阅读入口
  14. 掉头发厉害,是为什么呢?
  15. oracle在运行存储的时候出现:同义词转换不再有效
  16. 微云同步盘 linux,微云同步盘pc版下载
  17. thread ,socket
  18. 计算机语言点餐笑话,笑话:逗逼的程序员去点餐,结果·····
  19. CSS精灵图和字体图标
  20. ubuntu安装blocklocks与简单的设置方法

热门文章

  1. 【虚幻引擎UE】打包异常问题合集
  2. 企业办公模式转型,OA系统进入常态化
  3. 有限体积法(1)——一维扩散方程的推导
  4. 用Python做逐步回归
  5. foxmail邮件数据和通讯录的备份恢复
  6. Linux清理GPU显存
  7. ccache高速编译工具
  8. 生物实验室搬迁需要注意什么
  9. 机柜搬迁到IDC机房需要注意哪些事项
  10. html 原生弹出框,html、css和js原生写一个模态弹出框,顺便解决父元素半透明子元素不透明效果...