輕鬆把玩HttpClient之封裝HttpClient工具類(七),新增驗證碼識別功能
阿新 • • 發佈:2018-12-30
這個HttpClientUtil工具類分享在GitHub上已經半年多的時間了,並且得到了不小的關注,有25顆star,被fork了38次。有了大家的鼓勵,工具類一直也在完善中。最近比較忙,兩個多月前的修改在今天剛修改測試完成,今天再次分享給大家。
驗證碼識別這項技術並不是本工具類的功能,而是通過一個開源的api來識別驗證碼的。這裡做了一個簡單的封裝,主要是用來解決登陸時的驗證碼的問題。線上驗證碼識別官網:http://lab.ocrking.com/,github地址:https://github.com/AvensLab/OcrKing/,是一個功能非常強大的工具。
好了,言歸正傳,本次封裝的工具重要程式碼如下:
其實這個類中,主要用3個方法,第一個是識別本地圖片,第二個是識別網路上的固定圖片,第三個是識別網路上可重新整理的驗證碼圖片。當然不管哪個方法,核心程式碼都是讀取圖片位元組流,上傳到api介面,通過介面進行識別。/** * 識別驗證碼 * * @author arron * @date 2016年3月24日 上午9:44:35 * @version 1.0 */ public class OCR { /** * 介面說明: * https://github.com/AvensLab/OcrKing/blob/master/線上識別http介面說明.txt */ private static final String apiUrl = "http://lab.ocrking.com/ok.html"; private static final String apiKey = PropertiesUtil.getProperty("OCR.key"); private static final String boundary = "----------------------------OcrKing_Client_Aven_s_Lab"; private static final String end="\r\n--" + boundary + "--\r\n"; private static final Header[] headers = HttpHeader.custom() .referer("http://lab.ocrking.com/?javaclient0.1)") .build(); private static final Map<String, Object> map = getParaMap(); private static HttpClient client =null; //=HCB.custom().proxy("127.0.0.1", 8888).build(); public static void debug(){ client =HCB.custom().proxy("127.0.0.1", 8888).build(); } public static void exitDebug(){ client =null; } //獲取固定引數 private static Map<String, Object> getParaMap(){ //載入所有引數 Map<String , Object> map = new HashMap<String, Object>(); map.put("service", "OcrKingForCaptcha"); map.put("language", "eng"); map.put("charset", "7");//7-數字大寫小寫,5-數字大寫字母 map.put("type", "http://www.unknown.com"); map.put("apiKey", apiKey); return map; } /** * 識別本地校驗碼(英文:字母+大小寫) * * @param imgFilePath 驗證碼地址 * @return */ public static String ocrCode(String filePath){ return ocrCode(filePath, 0); } /** * 識別本地校驗碼(英文:字母+大小寫) * * @param imgFilePath 驗證碼地址 * @param limitCodeLen 驗證碼長度(如果結果與設定長度不一致,則返回獲取失敗的提示) * @return */ @SuppressWarnings("resource") public static String ocrCode(String imgFilePath, int limitCodeLen){ byte[] data = null; String fileName = imgFilePath.replaceAll("[^/]*/|[^\\\\]*\\\\", ""); StringBuffer strBuf = new StringBuffer(); for (Entry<String, Object> entry : map.entrySet()) { strBuf.append("\r\n").append("--").append(boundary).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n"); strBuf.append(entry.getValue()); } strBuf.append("\r\n").append("--").append(boundary).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"ocrfile\"; filename=\"" + fileName + "\"\r\n"); strBuf.append("Content-Type:application/octet-stream\r\n\r\n"); //讀取檔案 File f = new File(imgFilePath); if(!f.exists()){ return "Error:檔案不存在!"; } //內容長度=引數長度+檔案長度+結尾字串長度 ByteArrayOutputStream bos = new ByteArrayOutputStream(strBuf.length()+(int)f.length()+end.length()); try { bos.write(strBuf.toString().getBytes());//轉化引數內容 BufferedInputStream in = new BufferedInputStream(new FileInputStream(f)); int buf_size = 1024; int len = 0; byte[] buf = new byte[buf_size]; while (-1 != (len = in.read(buf, 0, buf_size))) { bos.write(buf, 0, len); } bos.write(end.getBytes()); data= bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); } Map<String , Object> m = new HashMap<String, Object>(); m.put(Utils.ENTITY_BYTES, data); String html; try { html = HttpClientUtil.post(HttpConfig.custom().client(client).url(apiUrl).headers(headers).map(m)); //System.out.println(html); String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html); if(results.length>0){ //System.out.println(results[0]); if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判斷長度或者長度一致時,直接返回 return results[0]; } } } catch (HttpProcessException e) { e.printStackTrace(); } return "Error:獲取失敗!"; } /** * 直接獲取網路驗證碼(驗證碼不重新整理) * * @param imgUrl 驗證碼地址 * @return */ public static String ocrCode4Net(String imgUrl){ return ocrCode4Net(imgUrl, 0); } /** * 直接獲取網路驗證碼(驗證碼不重新整理) * * @param imgUrl 驗證碼地址 * @param limitCodeLen 驗證碼長度 * @return */ public static String ocrCode4Net(String imgUrl, int limitCodeLen){ Map<String, Object> map = getParaMap(); map.put("url", imgUrl); 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(); try { String html = HttpClientUtil.post(HttpConfig.custom().client(client).url(apiUrl).headers(headers).map(map)); //System.out.println(html); String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html); if(results.length>0){ //System.out.println(results[0]); if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判斷長度或者長度一致時,直接返回 return results[0]; } } } catch (HttpProcessException e) { e.printStackTrace(); } return "Error:獲取失敗!"; } /** * 直接獲取網路驗證碼(通過獲取圖片流,然後識別驗證碼) * * @param config HttpConfig物件(設定cookie) * @param savePath 圖片儲存的完整路徑(值為null時,不儲存),如:c:/1.png * @return */ public static String ocrCode4Net(HttpConfig config, String savePath){ return ocrCode4Net(config, savePath, 0); } /** * 直接獲取網路驗證碼(通過獲取圖片流,然後識別驗證碼) * * @param config HttpConfig物件(設定cookie) * @param savePath 圖片儲存的完整路徑(值為null時,不儲存),如:c:/1.png * @param limitCodeLen 驗證碼長度 * @return */ @SuppressWarnings("resource") public static String ocrCode4Net(HttpConfig config, String savePath, int limitCodeLen){ byte[] data = null; StringBuffer strBuf = new StringBuffer(); for (Entry<String, Object> entry : map.entrySet()) { strBuf.append("\r\n").append("--").append(boundary).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n"); strBuf.append(entry.getValue()); } strBuf.append("\r\n").append("--").append(boundary).append("\r\n"); strBuf.append("Content-Disposition: form-data; name=\"ocrfile\"; filename=\"" + "aaa" + "\"\r\n"); strBuf.append("Content-Type:application/octet-stream\r\n\r\n"); //下載圖片 ByteArrayOutputStream out = new ByteArrayOutputStream(); try { out = (ByteArrayOutputStream) HttpClientUtil.down(config.client(client).out(out)); if(savePath==null || savePath.equals("")){ }else{ //本地測試,可以儲存一下圖片,方便核驗 FileOutputStream fos = new FileOutputStream(savePath); fos.write(out.toByteArray()); } ByteArrayOutputStream bos = new ByteArrayOutputStream(out.size()+strBuf.length()+end.length()); bos.write(strBuf.toString().getBytes()); bos.write(out.toByteArray()); bos.write(end.getBytes()); data= bos.toByteArray(); } catch (HttpProcessException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } Map<String , Object> m = new HashMap<String, Object>(); m.put(Utils.ENTITY_BYTES, data); String html; try { html = HttpClientUtil.post(config.client(client).url(apiUrl).headers(headers).map(m)); //System.out.println(html); String[] results = StringUtil.regex("<Result>([^<]*)</Result>\\s*<Status>([^<]*)</Status>", html); if(results.length>0){ //System.out.println(results[0]); if(limitCodeLen<=0 || limitCodeLen==results[0].length()){//不判斷長度或者長度一致時,直接返回 return results[0]; } } } catch (HttpProcessException e) { e.printStackTrace(); } return "Error:獲取失敗!"; } }
上面程式碼中用到了StringUtil.regex方法,具體如下:
由於識別驗證碼的api需要用到ke'y,所以就寫了一個配置檔案config.properties,用於設定ke'y的值。同時提供了一個簡單的配置檔案工具類:/** * 通過正則表示式獲取內容 * * @param regex 正則表示式 * @param from 原字串 * @return */ public static String[] regex(String regex, String from){ Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(from); List<String> results = new ArrayList<String>(); while(matcher.find()){ for (int i = 0; i < matcher.groupCount(); i++) { results.add(matcher.group(i+1)); } } return results.toArray(new String[]{}); }
/**
* 最簡單的屬性檔案讀取工具類
*
* @author arron
* @date 2016年1月14日 下午5:37:18
* @version 1.0
*/
public class PropertiesUtil {
/**
* 預設屬性集合(檔案在Constants中配置)
*/
protected static Properties defaultProp = null;
/**
* 所有讀取過的屬性集合
* 檔名 <-> 屬性集合
*/
protected static Map<String, Properties> allProps = new HashMap<String, Properties>();
// 初始化預設的屬性集合
static {
if (defaultProp == null) {
defaultProp = loadProperties("config.properties");
allProps.put("config.properties", defaultProp);
}
}
/**
* 讀取屬性檔案,並將讀出來的屬性集合新增到【allProps】當中
* 如果該屬性檔案之前已讀取過,則直接從【allProps】獲得
*/
public static Properties getProperties(String fileName) {
if (fileName==null || "".equals(fileName)) {
return defaultProp;
} else {
Properties prop = allProps.get(fileName);
if(prop == null) {
prop = loadProperties(fileName);
allProps.put(fileName, prop);
}
return prop;
}
}
/**
* 解析屬性檔案,將檔案中的所有屬性都讀取到【Properties】當中
*/
protected static Properties loadProperties (String fileName) {
Properties prop = new Properties();
InputStream ins = null;
ins = PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName);
if (ins == null) {
System.err.println("Can not find the resource!");
} else {
try {
prop.load(ins);
} catch (IOException e) {
System.err.println("An error occurred when reading from the input stream, "+e.getMessage());
} catch (IllegalArgumentException e) {
System.err.println("The input stream contains a malformed Unicode escape sequence, "+e.getMessage());
}
}
return prop;
}
/**
* 從指定的屬性檔案中獲取某一屬性值
* 如果屬性檔案不存在該屬性則返回 null
*/
public static String getProperty(String fileName, String name){
return getProperties(fileName).getProperty(name);
}
/**
* 從預設的屬性檔案中獲取某一屬性值
* 如果屬性檔案不存在該屬性則返回 null
*/
public static String getProperty(String name){
return getProperties(null).getProperty(name);
}
}
程式碼也就這麼多。在最後,提供一段測試程式碼來測試該功能:核心邏輯就是通過HttpClientUtil的download方法獲取圖片,然後通過api進行識別,然後通過請求特定網址進行驗證識別的結果是否正確。 public static void main(String[] args) throws InterruptedException, HttpProcessException {
String qq = "123456789";//qq號
String imgUrl = "http://qqxoo.com/include/vdimgvt.php?t="+Math.random(); //獲取驗證碼圖片地址
String verifyUrl = "http://qqxoo.com/include/vdcheck.php";
String saveCodePath = "C:/1.png";//儲存驗證碼圖片路徑
Header[] headers = HttpHeader.custom().referer("http://qqxoo.com/main.html?qqid="+qq).build();//設定referer,是為了獲取對應qq號的驗證碼,否則報錯
HttpConfig config = HttpConfig.custom().headers(headers).context(HttpCookies.custom().getContext());//必須設定context,是為了攜帶cookie進行操作
String result =null;//識別結果
do {
if(result!=null){
System.err.println("本次識別失敗!");
}
//獲取驗證碼
//OCR.debug(); //開始Fiddler4抓包(127.0.0.1:8888)
String code = OCR.ocrCode4Net(config.url(imgUrl), saveCodePath);
while(code.length()!=5){//如果識別的驗證碼位數不等於5,則重新識別
if(code.equals("親,apiKey已經過期或錯誤,請重新獲取")){
System.err.println(code);
return;
}
code = OCR.ocrCode4Net(config.url(imgUrl), saveCodePath);
}
System.out.println("本地識別的驗證碼為:"+code);
System.out.println("驗證碼已儲存到:"+saveCodePath);
//開始驗證識別的驗證碼是否正確
result = HttpClientUtil.get(config.url(verifyUrl+"?vc="+code+"&qqid="+qq));
} while (result.contains("succeed"));
System.out.println("識別驗證碼成功!反饋資訊如下:\n" + result);
}
執行結果如下: