轻松把玩HttpClient之封装HttpClient工具类(七),新增验证码识别功能

       这个HttpClientUtil工具类分享在GitHub上已经半年多的时间了,并且得到了不小的关注,有25颗star,被fork了38次。有了大家的鼓励,工具类一直也在完善中。最近比较忙,两个多月前的修改在今天刚修改测试完成,今天再次分享给大家。


       验证码识别这项技术并不是本工具类的功能,而是通过一个开源的api来识别验证码的。这里做了一个简单的封装,主要是用来解决登陆时的验证码的问题。在线验证码识别官网:http://lab.ocrking.com/,github地址:https://github.com/AvensLab/OcrKing/,是一个功能非常强大的工具。


       好了,言归正传,本次封装的工具重要代码如下:

[java]  view plain  copy
 print ?
  1. /**  
  2.  * 识别验证码 
  3.  *  
  4.  * @author arron 
  5.  * @date 2016年3月24日 上午9:44:35  
  6.  * @version 1.0  
  7.  */  
  8. public class OCR {  
  9.       
  10.     /** 
  11.      * 接口说明: 
  12.      * https://github.com/AvensLab/OcrKing/blob/master/线上识别http接口说明.txt 
  13.      */  
  14.     private static final String apiUrl = "http://lab.ocrking.com/ok.html";  
  15.     private static final String apiKey = PropertiesUtil.getProperty("OCR.key");  
  16.     private static final String boundary = "----------------------------OcrKing_Client_Aven_s_Lab";  
  17.     private static final String end="\r\n--" + boundary + "--\r\n";  
  18.     private static final Header[] headers = HttpHeader.custom()                                         .referer("http://lab.ocrking.com/?javaclient0.1)")  
  19.                                                                                     .build();  
  20.     private static final Map<String, Object> map = getParaMap();  
  21.     private static HttpClient client  =null//=HCB.custom().proxy("127.0.0.1", 8888).build();  
  22.   
  23.     public static void debug(){  
  24.         client =HCB.custom().proxy("127.0.0.1"8888).build();  
  25.     }  
  26.     public static void exitDebug(){  
  27.         client =null;  
  28.     }  
  29.       
  30.     //获取固定参数  
  31.     private static Map<String, Object> getParaMap(){  
  32.         //加载所有参数  
  33.         Map<String , Object> map = new HashMap<String, Object>();  
  34.         map.put("service""OcrKingForCaptcha");  
  35.         map.put("language""eng");  
  36.         map.put("charset""7");//7-数字大写小写,5-数字大写字母  
  37.         map.put("type""http://www.unknown.com");  
  38.         map.put("apiKey", apiKey);  
  39.         return map;  
  40.     }  
  41.       
  42.     /** 
  43.      * 识别本地校验码(英文:字母+大小写) 
  44.      *  
  45.      * @param imgFilePath   验证码地址 
  46.      * @return 
  47.      */  
  48.     public static String ocrCode(String filePath){  
  49.         return ocrCode(filePath, 0);  
  50.     }  
  51.     /** 
  52.      * 识别本地校验码(英文:字母+大小写) 
  53.      *  
  54.      * @param imgFilePath   验证码地址 
  55.      * @param limitCodeLen  验证码长度(如果结果与设定长度不一致,则返回获取失败的提示) 
  56.      * @return 
  57.      */  
  58.     @SuppressWarnings("resource")  
  59.     public static String ocrCode(String imgFilePath, int limitCodeLen){  
  60.         byte[] data = null;  
  61.         String fileName = imgFilePath.replaceAll("[^/]*/|[^\\\\]*\\\\", "");  
  62.           
  63.         StringBuffer strBuf = new StringBuffer();  
  64.         for (Entry<String, Object> entry : map.entrySet()) {  
  65.             strBuf.append("\r\n").append("--").append(boundary).append("\r\n");  
  66.             strBuf.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");  
  67.             strBuf.append(entry.getValue());  
  68.         }  
  69.         strBuf.append("\r\n").append("--").append(boundary).append("\r\n");  
  70.         strBuf.append("Content-Disposition: form-data; name=\"ocrfile\"; filename=\"" + fileName + "\"\r\n");  
  71.         strBuf.append("Content-Type:application/octet-stream\r\n\r\n");  
  72.           
  73.         //读取文件  
  74.         File f = new File(imgFilePath);  
  75.         if(!f.exists()){  
  76.             return "Error:文件不存在!";  
  77.         }  
  78.           
  79.         //内容长度=参数长度+文件长度+结尾字符串长度  
  80.         ByteArrayOutputStream bos = new ByteArrayOutputStream(strBuf.length()+(int)f.length()+end.length());   
  81.         try {  
  82.             bos.write(strBuf.toString().getBytes());//转化参数内容  
  83.             BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));    
  84.             int buf_size = 1024;  
  85.             int len = 0;    
  86.             byte[] buf = new byte[buf_size];    
  87.             while (-1 != (len = in.read(buf, 0, buf_size))) {    
  88.                 bos.write(buf, 0, len);    
  89.             }    
  90.             bos.write(end.getBytes());  
  91.             data= bos.toByteArray();    
  92.         } catch (IOException e) {    
  93.             e.printStackTrace();  
  94.         }  
  95.           
  96.         Map<String , Object> m = new HashMap<String, Object>();  
  97.         m.put(Utils.ENTITY_BYTES, data);  
  98.           
  99.         String html;  
  100.         try {  
  101.             html = HttpClientUtil.post(HttpConfig.custom().client(client).url(apiUrl).headers(headers).map(m));  
  102.             //System.out.println(html);  
  103.             String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html);  
  104.             if(results.length>0){  
  105.                 //System.out.println(results[0]);  
  106.                 if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判断长度或者长度一致时,直接返回  
  107.                     return results[0];  
  108.                 }  
  109.             }  
  110.         } catch (HttpProcessException e) {  
  111.             e.printStackTrace();  
  112.         }  
  113.           
  114.         return "Error:获取失败!";  
  115.     }  
  116.       
  117.     /** 
  118.      * 直接获取网络验证码(验证码不刷新) 
  119.      *  
  120.      * @param imgUrl            验证码地址 
  121.      * @return 
  122.      */  
  123.     public static String ocrCode4Net(String imgUrl){  
  124.         return ocrCode4Net(imgUrl, 0);  
  125.     }  
  126.     /** 
  127.      * 直接获取网络验证码(验证码不刷新) 
  128.      *  
  129.      * @param imgUrl            验证码地址 
  130.      * @param limitCodeLen  验证码长度 
  131.      * @return 
  132.      */  
  133.     public static String ocrCode4Net(String imgUrl, int limitCodeLen){  
  134.         Map<String, Object> map = getParaMap();  
  135.         map.put("url", imgUrl);  
  136.           
  137.         Header[] headers = HttpHeader.custom().userAgent("Mozilla/5.0 (Windows NT 5.1; zh-CN; rv:1.9.1.3) Gecko/20100101 Firefox/8.0").build();  
  138.   
  139.         try {  
  140.             String html = HttpClientUtil.post(HttpConfig.custom().client(client).url(apiUrl).headers(headers).map(map));  
  141.             //System.out.println(html);  
  142.             String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html);  
  143.             if(results.length>0){  
  144.                 //System.out.println(results[0]);  
  145.                 if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判断长度或者长度一致时,直接返回  
  146.                     return results[0];  
  147.                 }  
  148.             }  
  149.         } catch (HttpProcessException e) {  
  150.             e.printStackTrace();  
  151.         }  
  152.           
  153.         return "Error:获取失败!";  
  154.     }  
  155.       
  156.     /** 
  157.      * 直接获取网络验证码(通过获取图片流,然后识别验证码) 
  158.      *  
  159.      * @param config                HttpConfig对象(设置cookie) 
  160.      * @param savePath      图片保存的完整路径(值为null时,不保存),如:c:/1.png 
  161.      * @return 
  162.      */  
  163.     public static String ocrCode4Net(HttpConfig config, String savePath){  
  164.         return ocrCode4Net(config, savePath, 0);  
  165.     }  
  166.     /** 
  167.      * 直接获取网络验证码(通过获取图片流,然后识别验证码) 
  168.      *  
  169.      * @param config                HttpConfig对象(设置cookie) 
  170.      * @param savePath      图片保存的完整路径(值为null时,不保存),如:c:/1.png 
  171.      * @param limitCodeLen  验证码长度 
  172.      * @return 
  173.      */  
  174.     @SuppressWarnings("resource")  
  175.     public static String ocrCode4Net(HttpConfig config, String savePath, int limitCodeLen){  
  176.         byte[] data = null;  
  177.           
  178.         StringBuffer strBuf = new StringBuffer();  
  179.         for (Entry<String, Object> entry : map.entrySet()) {  
  180.             strBuf.append("\r\n").append("--").append(boundary).append("\r\n");  
  181.             strBuf.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");  
  182.             strBuf.append(entry.getValue());  
  183.         }  
  184.         strBuf.append("\r\n").append("--").append(boundary).append("\r\n");  
  185.         strBuf.append("Content-Disposition: form-data; name=\"ocrfile\"; filename=\"" + "aaa" + "\"\r\n");  
  186.         strBuf.append("Content-Type:application/octet-stream\r\n\r\n");  
  187.           
  188.         //下载图片  
  189.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  190.         try {  
  191.             out = (ByteArrayOutputStream) HttpClientUtil.down(config.client(client).out(out));  
  192.             if(savePath==null || savePath.equals("")){  
  193.             }else{  
  194.                 //本地测试,可以保存一下图片,方便核验  
  195.                 FileOutputStream fos = new FileOutputStream(savePath);  
  196.                 fos.write(out.toByteArray());  
  197.             }  
  198.               
  199.             ByteArrayOutputStream bos = new ByteArrayOutputStream(out.size()+strBuf.length()+end.length());  
  200.             bos.write(strBuf.toString().getBytes());  
  201.             bos.write(out.toByteArray());  
  202.             bos.write(end.getBytes());  
  203.             data= bos.toByteArray();  
  204.         } catch (HttpProcessException e) {  
  205.             e.printStackTrace();  
  206.         } catch (IOException e) {  
  207.             e.printStackTrace();  
  208.         }  
  209.           
  210.         Map<String , Object> m = new HashMap<String, Object>();  
  211.         m.put(Utils.ENTITY_BYTES, data);  
  212.           
  213.         String html;  
  214.         try {  
  215.             html = HttpClientUtil.post(config.client(client).url(apiUrl).headers(headers).map(m));  
  216.             //System.out.println(html);  
  217.             String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html);  
  218.             if(results.length>0){  
  219.                 //System.out.println(results[0]);  
  220.                 if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判断长度或者长度一致时,直接返回  
  221.                     return results[0];  
  222.                 }  
  223.             }  
  224.         } catch (HttpProcessException e) {  
  225.             e.printStackTrace();  
  226.         }  
  227.           
  228.         return "Error:获取失败!";  
  229.     }  
  230. }  
       其实这个类中,主要用3个方法,第一个是识别本地图片,第二个是识别网络上的固定图片,第三个是识别网络上可刷新的验证码图片。当然不管哪个方法,核心代码都是读取图片字节流,上传到api接口,通过接口进行识别。


       上面代码中用到了StringUtil.regex方法,具体如下:

[java]  view plain  copy
 print ?
  1. /** 
  2.  * 通过正则表达式获取内容 
  3.  *  
  4.  * @param regex     正则表达式 
  5.  * @param from      原字符串 
  6.  * @return 
  7.  */  
  8. public static String[] regex(String regex, String from){  
  9.     Pattern pattern = Pattern.compile(regex);   
  10.     Matcher matcher = pattern.matcher(from);  
  11.     List<String> results = new ArrayList<String>();  
  12.     while(matcher.find()){  
  13.         for (int i = 0; i < matcher.groupCount(); i++) {  
  14.             results.add(matcher.group(i+1));  
  15.         }  
  16.     }  
  17.     return results.toArray(new String[]{});  
  18. }  
       由于识别验证码的api需要用到ke'y,所以就写了一个配置文件config.properties,用于设置ke'y的值。同时提供了一个简单的配置文件工具类:
[java]  view plain  copy
 print ?
  1. /** 
  2.  * 最简单的属性文件读取工具类 
  3.  *  
  4.  * @author arron 
  5.  * @date 2016年1月14日 下午5:37:18  
  6.  * @version 1.0 
  7.  */  
  8. public class PropertiesUtil {  
  9.   
  10.     /** 
  11.      * 默认属性集合(文件在Constants中配置) 
  12.      */  
  13.     protected static Properties defaultProp = null;  
  14.     /** 
  15.      * 所有读取过的属性集合 
  16.      * 文件名 <-> 属性集合 
  17.      */  
  18.     protected static Map<String, Properties> allProps = new HashMap<String, Properties>();  
  19.       
  20.     // 初始化默认的属性集合  
  21.     static {  
  22.         if (defaultProp == null) {  
  23.             defaultProp = loadProperties("config.properties");  
  24.             allProps.put("config.properties", defaultProp);  
  25.         }  
  26.     }  
  27.       
  28.     /** 
  29.      * 读取属性文件,并将读出来的属性集合添加到【allProps】当中 
  30.      * 如果该属性文件之前已读取过,则直接从【allProps】获得 
  31.      */  
  32.     public static Properties getProperties(String fileName) {  
  33.         if (fileName==null || "".equals(fileName)) {  
  34.             return defaultProp;  
  35.         } else {  
  36.             Properties prop = allProps.get(fileName);  
  37.             if(prop == null) {  
  38.                 prop = loadProperties(fileName);  
  39.                 allProps.put(fileName, prop);  
  40.             }  
  41.               
  42.             return prop;  
  43.         }  
  44.     }         
  45.       
  46.     /** 
  47.      * 解析属性文件,将文件中的所有属性都读取到【Properties】当中 
  48.      */  
  49.     protected static Properties loadProperties (String fileName) {  
  50.         Properties prop = new Properties();  
  51.         InputStream ins = null;  
  52.         ins = PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName);  
  53.         if (ins == null) {  
  54.             System.err.println("Can not find the resource!");  
  55.         } else {  
  56.             try {  
  57.                 prop.load(ins);  
  58.             } catch (IOException e) {  
  59.                 System.err.println("An error occurred when reading from the input stream, "+e.getMessage());  
  60.             } catch (IllegalArgumentException e) {  
  61.                 System.err.println("The input stream contains a malformed Unicode escape sequence, "+e.getMessage());  
  62.             }  
  63.         }  
  64.         return prop;  
  65.     }  
  66.       
  67.     /** 
  68.      * 从指定的属性文件中获取某一属性值 
  69.      * 如果属性文件不存在该属性则返回 null 
  70.      */  
  71.     public static String getProperty(String fileName, String name){  
  72.         return getProperties(fileName).getProperty(name);  
  73.     }  
  74.       
  75.     /** 
  76.      * 从默认的属性文件中获取某一属性值 
  77.      * 如果属性文件不存在该属性则返回 null 
  78.      */  
  79.     public static String getProperty(String name){  
  80.         return getProperties(null).getProperty(name);  
  81.     }  
  82. }  
       代码也就这么多。在最后,提供一段测试代码来测试该功能:核心逻辑就是通过HttpClientUtil的download方法获取图片,然后通过api进行识别,然后通过请求特定网址进行验证识别的结果是否正确。
[java]  view plain  copy
 print ?
  1. public static void main(String[] args) throws InterruptedException, HttpProcessException {  
  2.     String qq = "123456789";//qq号  
  3.     String imgUrl = "http://qqxoo.com/include/vdimgvt.php?t="+Math.random(); //获取验证码图片地址  
  4.     String verifyUrl = "http://qqxoo.com/include/vdcheck.php";  
  5.     String saveCodePath = "C:/1.png";//保存验证码图片路径  
  6.       
  7.     Header[] headers = HttpHeader.custom().referer("http://qqxoo.com/main.html?qqid="+qq).build();//设置referer,是为了获取对应qq号的验证码,否则报错  
  8.     HttpConfig config = HttpConfig.custom().headers(headers).context(HttpCookies.custom().getContext());//必须设置context,是为了携带cookie进行操作  
  9.       
  10.     String result =null;//识别结果  
  11.       
  12.     do {  
  13.         if(result!=null){  
  14.             System.err.println("本次识别失败!");  
  15.         }  
  16.           
  17.         //获取验证码  
  18.         //OCR.debug(); //开始Fiddler4抓包(127.0.0.1:8888)  
  19.         String code = OCR.ocrCode4Net(config.url(imgUrl), saveCodePath);  
  20.           
  21.         while(code.length()!=5){//如果识别的验证码位数不等于5,则重新识别  
  22.             if(code.equals("亲,apiKey已经过期或错误,请重新获取")){  
  23.                 System.err.println(code);  
  24.                 return;  
  25.             }  
  26.             code = OCR.ocrCode4Net(config.url(imgUrl), saveCodePath);  
  27.         }  
  28.           
  29.         System.out.println("本地识别的验证码为:"+code);  
  30.         System.out.println("验证码已保存到:"+saveCodePath);  
  31.           
  32.         //开始验证识别的验证码是否正确  
  33.         result = HttpClientUtil.get(config.url(verifyUrl+"?vc="+code+"&qqid="+qq));  
  34.           
  35.     } while (result.contains("succeed"));  
  36.       
  37.     System.out.println("识别验证码成功!反馈信息如下:\n" + result);  
  38. }  
       运行结果如下:



     

使用过程注意事项: 1.调用DLL识别库识别,多线程下不需要加许可证,支持并发识别. 2.调用DLL识别,识别参数设置命令SetWmOption必须与你弄字库时候的设置参数一样,不然会导致识别率下降具体参数说明,请看调用例子里的[我的函数.txt]文档有对应参数说明. 3.如果遇见本工具无法识别的验证码,可以找群主(用神经网络识别)定制,价格便宜公道. [2017-07-27] 完美验证码识别系统V3.2 1.增加DLL识别返回方式2和3具体看我的函数.txt里说明,主要是增加一个可以返回识别后的总体信任度.这个值你可以给它个阀值,比如说如果总体信任度小于60,那么你就不提交服务器,直接重新获取图片识别,直到总体信任度大于60你才提交给服务器,这个阀值具体多少,自己可以先测试. 2.修复导出字库没有导出完,直接关闭窗口崩溃问题 3.添加批量下载后定位到批量下载文件夹 4.增加可以使用验证码长度进行过滤,增加可以使用总体信任度进行过滤.(这两个功能可以快速的制作字库,比如说你验证码是4位的,你可以设置验证码小于4,大于4那这张图片肯定是识别错误的,那么软件就会把这张图片保存下来,同理可以使用总体信任度进行此保存) [2017-07-20] 完美验证码识别系统V3.1 1.修复数组下标越界问题 2.字库列表添加可以多选然后右键批量删除选中项 [2017-07-13] 完美验证码识别系统V3.0 1.修复导出字库,重复导出不会覆盖原来的BUG,导出的图片会累加上去(注意,以前导出的字模导入新版中会出错,新版字模导出格式为a_md5.bmp 请自行写个软件修改文件名,然后再导入) 2.修复添加字库空格都可以添加进去 3.修复崩溃问题.(崩溃应该绝大多数都是此原因造成的.) 4.其它一些调整. [2017-06-25] 完美验证码识别系统V2.6 1.解决输入焦点问题. 2.批量下载增加可以过滤掉宽度范围,面积范围,高度范围,黑色数范围,可以去掉没有用的干扰图片. 3.其它的一些细节调整 [2017-06-07] 完美验证码识别系统V2.5 1.修复去除干扰滤镜在没有先二值化图片都可以使用. 2.尝试解决添加字库崩溃问题(代码较多,找到1处问题.不知道还有没有其他问题) 3.编辑字库页面增加个选中框[添加字库后自动下载图片],使用批量下载图片,可以快速添加字库 4.使用截图工具,截取图片后,会自动切换到划线工具. [2017-05-17] 完美验证码识别系统V2.3 1.修复设置滤镜默认都有选择项. 2.修复当使用本地图像时,没有新建项目都可以编辑的问题. 3.修复黑白处理滤镜在没有先二值化图片都可以使用. 4.修复处理很多逻辑错误会导致程序崩溃. 5.增加字库列表添加右键删除当前选中的字库 6.增加禁止重复运行,由于重复运行会导致字库添加失败,和读取不到已经做过的字库. 7.增加快捷键"自动分割(Alt+R)" ,"手动分割(Alt+T)" 8.编辑页面增加个批量下载按钮,可以实现批量下载并自动分割图片功能(批量下载的图片保存在当前项目文件夹下的"批量下载"文件夹中) 9.主窗口增加一个选择框_图像已处理不使用滤镜,由于批量下载后图片都是已经使用过滤镜了,处理批量下载的图片这里必须得勾选上,不然会重复运用滤镜 [2017-05-07] 完美验证码识别系统V2.2 1.新增分辨率1024*768布局 2.修改注册热键方式为本进程方式(原为全局热键,感谢群友XGSoft提供源代码) [2017-05-06] 完美验证码识别系统V2.1(要求屏幕分辨率最低1440*900) 1.去掉窗口最大化,去掉自动调整窗口大小代码,修复部分控件显示不全 2.增加检测图像是否二值化,没二值化的图片不允许编辑 3.尝试解决添加字库崩溃的问题(不确定问题出在哪!!) 4.解决滤波数组越界问题.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值