前言
最近利用apache的httpclient模拟抓了一些东西,可惜验证码这一块让我很头疼,不知是Google的tesseract能力有限,还是我验证码处理的不到位,稍微一模糊的就效果很差;这不上网看了下,除了人工打码识别的,还有一个在线OCR的API——有道API,url:http://ai.youdao.com。
正文
- 接口调用参数
调用API需要向接口发送以下字段来访问服务。
字段名类型含义必填备注imgtext要识别的图片,需要Base64编码True必须是Base64编码langTypetext要识别的语言类型True目前支持英文:en,和中英混合:zh-endetectTypetext识别类型,目前只支持片段识别true片段识别:10011imageTypetext图片类型,目前只支持Base64True目前只支持Base64:1,imageType的值为1appKeytext应用申请的keyTrue可在管理控制台查看salttext随机数True
signtext签名,通过md5(appkey+img+salt+密钥)生成TrueappKey+img+salt+密钥的MD5值docTypetext服务器响应类型,目前只支持jsonTruejson
签名生成方法如下:
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的值有关,具体说明如下:
字段名类型含义备注errorCodetext错误返回码一定存在Resulttext识别结果查询正确时一定存在
- 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不支持的传输加密类型108appKey无效,注册账号, 登录后台创建应用和实例并完成绑定, 可获得应用ID和密钥等信息,其中应用ID就是appKey( 注意不是应用密钥)109batchLog格式不正确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解密失败1301OCR段落识别失败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()); | |
/***构造参数请求接口*****/ | |
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学习参观。