JMeter开发插件——图片验证码识别

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

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

一组用于验证码识别的JMeter插件
咱们常见的验证码图片样本以下:
正则表达式

1. 降噪

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

public static int isFilter(int colorInt) { Color color = new Color(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 static BufferedImage removeBackgroud(BufferedImage img) throws Exception { 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()); } } } return img; } 

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

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

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

 

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

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

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

public static File createImage(File imageFile, String imageFormat) { File tempFile = null; ImageInputStream iis = null; ImageOutputStream ios = null; ImageReader reader = null; ImageWriter writer = null; try { Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(imageFormat); reader = readers.next(); iis = ImageIO.createImageInputStream(imageFile); reader.setInput(iis); IIOMetadata streamMetadata = reader.getStreamMetadata(); TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.CHINESE); tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED); Iterator<ImageWriter> 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(); } } return tempFile; } private static File tempImageFile(File imageFile) { String path = imageFile.getPath(); StringBuffer strB = new StringBuffer(path); return new File(strB.toString().replaceFirst("jpg", "tif")); }

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

import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; public class OCR { private final String LANG_OPTION = "-l"; private final String EOL = System.getProperty("line.separator"); private String tessPath = "D://Program Files (x86)//Tesseract-OCR"; public String recognizeText(File imageFile,String imageFormat) { File tempImage = ImageIOHelper.createImage(imageFile,imageFormat); File outputFile = new File(imageFile.getParentFile(),"output" + imageFile.getName()); StringBuffer sb = new StringBuffer(); List<String> cmd = new ArrayList<String>(); cmd.add(tessPath+"//tesseract"); cmd.add(""); cmd.add(outputFile.getName()); cmd.add(LANG_OPTION); cmd.add("eng"); ProcessBuilder pb = new ProcessBuilder(); pb.directory(imageFile.getParentFile()); cmd.set(1, tempImage.getName()); pb.command(cmd); pb.redirectErrorStream(true); Process process = null; BufferedReader in = null; int wait; 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(); return sb.toString(); } }

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

import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.Serializable; import org.apache.jmeter.processor.PostProcessor; import org.apache.jmeter.samplers.SampleResult; import org.apache.jmeter.testelement.AbstractScopedTestElement; import org.apache.jmeter.threads.JMeterContext; import org.apache.jmeter.threads.JMeterVariables; import org.apache.jorphan.logging.LoggingManager; import org.apache.log.Logger; public class VcodeExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable{ private static final Logger log = LoggingManager.getLoggerForClass(); @Override public void process() { // 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 = new File(imageName); out = new FileOutputStream(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() instanceof HTTPSampler){ return; }

判断前一个Sampler是否为HTTPSampler,以限定有效使用范围。
将previousResult.getResponseData()保存为文件后,经过前面咱们建立的OCR完成识别任务后,将识别结果经过JMeterVariables对象保存下来,在此咱们分别创建了两个参数”vcode”和”vuser”,后面咱们能够用它们进行测试。

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

import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; import org.apache.jmeter.testelement.TestElement; public class VcodeExtractorGUI extends AbstractPostProcessorGui{ @Override public TestElement createTestElement() { // TODO Auto-generated method stub
        VcodeExtractor extractor = new VcodeExtractor(); modifyTestElement(extractor); return extractor; } @Override public String getLabelResource() { // TODO Auto-generated method stub
        return this.getClass().getName(); } @Override public String getStaticLabel() {//设置显示名称 // TODO Auto-generated method stub
        return "VcodeExtractor"; } @Override public void modifyTestElement(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 boolean isVerify(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; } @Override public Sampler 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%左右,所以,还须要进一步完善,第一是经过改进识别逻辑,第二是增长一个验证码若是识别错误从新进行识别提交登陆事务的过程控制。

 

 原文地址https://blog.csdn.net/xreztento/article/details/48682923

大量jmeter二次开发文章地址https://blog.csdn.net/xreztento/article/category/2551407

相关文章
相关标签/搜索