前言
最近利用apache的httpclient模拟抓了一些东西,可惜验证码这一块让我很头疼,不知是Google的tesseract能力有限,还是我验证码处理的不到位,稍微一模糊的就效果很差;这不上网看了下,除了人工打码识别的,还有一个在线OCR的API——有道API,url:http://ai.youdao.com。
正文
接口调用参数
调用API需要向接口发送以下字段来访问服务。
字段名 | 类型 | 含义 | 必填 | 备注 |
---|---|---|---|---|
img | text | 要识别的图片,需要Base64编码 | True | 必须是Base64编码 |
langType | text | 要识别的语言类型 | True | 目前支持英文:en,和中英混合:zh-en |
detectType | text | 识别类型,目前只支持片段识别 | true | 片段识别:10011 |
imageType | text | 图片类型,目前只支持Base64 | True | 目前只支持Base64:1,imageType的值为1 |
appKey | text | 应用申请的key | True | 可在管理控制台查看 |
salt | text | 随机数 | True | |
sign | text | 签名,通过md5(appkey+img+salt+密钥)生成 | True | appKey+img+salt+密钥的MD5值 |
docType | text | 服务器响应类型,目前只支持json | True | json |
签名生成方法如下:
1、将请求参数中的appKey(应用ID),img(注意为图片的Base64编码), 随机数salt和密钥按照appKey+img+salt+密钥的顺序拼接得到字符串str。
2、对字符串str做 md5,得到32位大写的sign(参考Java生成MD5示例)。
注意:
请先将需要识别的图片转换为 Base64 编码。
在发送HTTP请求之前需要对各字段做 URL encode。
在生成签名拼接appid+img+salt+密钥字符串时,img不需要做 URL encode,在生成签名之后,发送 HTTP 请求之前才需要对要发送的待翻译文本字段img做URL encode。
输出结果
返回的结果是json格式,包含字段与FROM和TO的值有关,具体说明如下:
字段名 | 类型 | 含义 | 备注 |
---|---|---|---|
errorCode | text | 错误返回码 | 一定存在 |
Result | text | 识别结果 | 查询正确时一定存在 |
json格式
{ "errorCode": "0", "Result": { "orientation": "Up", "textAngle": 0, "language": "en", "lines": [{ "boundingBox": "30,33,25,10", "words": "hello" }]} }
其中,orientation代表方向,textAngle代表与垂直向上的偏差角度,language代表识别的语言,lines代表每行的返回结果;boundingBox的四个值代表识别的文字左上角的坐标(x,y),宽度和高度;words代表识别的字符;
errorCode列表
错误码 | 含义 |
---|---|
101 | 缺少必填的参数,出现这个情况还可能是et的值和实际加密方式不对应 |
102 | 不支持的语言类型 |
103 | 翻译文本过长 |
104 | 不支持的API类型 |
105 | 不支持的签名类型 |
106 | 不支持的响应类型 |
107 | 不支持的传输加密类型 |
108 | appKey无效,注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和密钥等信息,其中应用ID就是appKey( 注意不是应用密钥) |
109 | batchLog格式不正确 |
110 | 无相关服务的有效实例 |
111 | 开发者账号异常 |
201 | 解密失败,可能为DES,BASE64,URLDecode的错误 |
202 | 签名检验失败 |
203 | 访问IP地址不在可访问IP列表 |
301 | 辞典查询失败 |
302 | 小语种查询失败 |
303 | 服务端的其它异常 |
401 | 账户已经欠费停 |
1001 | 无效的OCR类型 |
1002 | 不支持的OCR image类型 |
1003 | 不支持的OCR Language类型 |
1004 | 识别图片过大 |
1201 | 图片base64解密失败 |
1301 | OCR段落识别失败 |
1411 | 访问频率受限 |
1412 | 超过最大识别字节数 |
示例demo
api官网上有多种语言的demo,限于环境,下面只介绍java的主要代码,本机实测通过;
public class OCRDemoForHttp { public static void main(String[] args) throws Exception{ Map <String, String>map = new HashMap<String, String>(); String url = "http://openapi.youdao.com/ocrapi"; String appKey = "你的appid"; String detectType = "10011"; String imageType = "1"; String langType = "en"; String docType = "json"; String path = "D:\\1 (36).jpg"; String salt = String.valueOf(System.currentTimeMillis()); saveImage(path); String img = getImageStr(path); map.put("appKey", appKey); map.put("img", img); map.put("detectType", detectType); map.put("imageType", imageType); map.put("langType", langType); map.put("salt", salt); map.put("docType", docType); String sign = md5(appKey + img + salt + "你的app secret"); map.put("sign", sign); String result= requestOCRForHttp(url,map); JSONObject jsonObject = new JSONObject(result); JSONObject obj1 = (JSONObject) jsonObject.get("Result"); org.json.JSONArray arr1 = obj1.getJSONArray("regions"); StringBuffer stringBuffer2 = new StringBuffer(); /***遍历jsonarry取出返回的多行text***/ for (int k = 0; k < arr1.length(); k++) { JSONObject obj2 = (JSONObject) arr1.get(k); org.json.JSONArray arr2 = obj2.getJSONArray("lines"); StringBuffer stringBuffer = new StringBuffer(); if (arr2.length() > 1) { for (int i = 0; i < arr2.length(); i++) { JSONObject obj3 = (JSONObject) arr2.get(i); org.json.JSONArray arr3 = obj3.getJSONArray("words"); for (int j = 0; j < arr3.length(); j++) { JSONObject obj4 = (JSONObject) arr3.get(j); String str = obj4.get("text")+" "; stringBuffer.append(str); //System.out.println("您识别的图片为"+obj4.get("text")); } stringBuffer.append("\r\n"); } }else { JSONObject obj3 = (JSONObject) arr2.get(0); org.json.JSONArray arr3 = obj3.getJSONArray("words"); JSONObject obj4 = (JSONObject) arr3.get(0); String str = obj4.get("text")+" "; stringBuffer.append(str); } stringBuffer2.append(stringBuffer+"\n\r"); } System.out.println("stringbuffer为-----"+"\n\r"+stringBuffer2); } protected transient final Log log = LogFactory.getLog(getClass()); @SuppressWarnings("finally") /***构造参数请求接口*****/ public static String requestOCRForHttp(String url,Map <String, String> requestParams) throws Exception{ String result = null; CloseableHttpClient httpClient = HttpClients.createDefault(); /**HttpPost*/ HttpPost httpPost = new HttpPost(url); List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(); params.add(new BasicNameValuePair("appKey", requestParams.get("appKey"))); params.add(new BasicNameValuePair("img", requestParams.get("img"))); params.add(new BasicNameValuePair("detectType", requestParams.get("detectType"))); params.add(new BasicNameValuePair("imageType", requestParams.get("imageType"))); params.add(new BasicNameValuePair("langType", requestParams.get("langType"))); params.add(new BasicNameValuePair("salt", requestParams.get("salt"))); params.add(new BasicNameValuePair("sign", requestParams.get("sign"))); params.add(new BasicNameValuePair("docType", requestParams.get("docType"))); httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8")); /**HttpResponse*/ CloseableHttpResponse httpResponse = httpClient.execute(httpPost); try{ HttpEntity httpEntity = httpResponse.getEntity(); result = EntityUtils.toString(httpEntity, "utf-8"); EntityUtils.consume(httpEntity); }finally{ try{ if(httpResponse!=null){ httpResponse.close(); } }catch(IOException e){ } return result; } } /** * 获得图片的Base64编码 * @param imgFile * @return */ public static String getImageStr(String imgFile) throws Exception {//将图片文件转化为字节数组字符串,并对其进行Base64编码处理 InputStream in = null; byte[] data = null; String imgstr = ""; //读取图片字节数组 try { in = new FileInputStream(imgFile); data = new byte[in.available()]; in.read(data); in.close(); } catch (IOException e) { e.printStackTrace(); } //对字节数组Base64编码 byte [] by= Base64.encode(data); imgstr = new String(by,"UTF-8"); return imgstr;//返回Base64编码过的字节数组字符串 } /** * 生成32位MD5摘要 * @param string * @return */ public static String md5(String string) { if(string == null){ return null; } char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; byte[] btInput = string.getBytes(); try{ /** 获得MD5摘要算法的 MessageDigest 对象 */ MessageDigest mdInst = MessageDigest.getInstance("MD5"); /** 使用指定的字节更新摘要 */ mdInst.update(btInput); /** 获得密文 */ byte[] md = mdInst.digest(); /** 把密文转换成十六进制的字符串形式 */ int j = md.length; char str[] = new char[j * 2]; int k = 0; for (byte byte0 : md) { str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); }catch(NoSuchAlgorithmException e){ return null; } } /*****利用IO流保存url图像为文件*****/ public static void saveImage(String path) throws Exception { URL url; url = new URL("http://img.jishux.com/jishux/2017/10/26/4859b33bed842978df19f9906ba013fb649aeb9d_.jpg"); URLConnection urlConnection = url.openConnection(); urlConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36"); urlConnection.addRequestProperty("Referer", "http://www.jishux.com"); InputStream inputStream = urlConnection.getInputStream(); byte[] by = new byte[1024]; int len ; OutputStream outputStream = new FileOutputStream(path); while ((len = inputStream.read(by)) != -1) { outputStream.write(by, 0, len); } inputStream.close(); outputStream.close(); } }
大体流程
main方法主要是解析接口返回来的json数据,剩下的就是利用http构造请求体,将参数传给api,其中加入了md5加密的方法和base64转码的方法,以及最后利用http保存io流的操作;
识别效果
后记
经过多番测试,这个api总体效果不错,英文识别率能达到95%以上,中文也能达到90%左右,确实还算理想,虽然就是识别验证码能力很差(笑哭),最后欢迎大家来技术栈官方网站:www.jishux.com学习参观。
