Java帶有驗證碼的模擬登陸
阿新 • • 發佈:2019-02-15
- 需求:
最近得到一個需求,需要模擬登陸網頁,然後通過網頁介面獲取相應資料。一共兩個網頁,其中沒有驗證碼的網頁比較容易的模擬登陸成功。但是另一個帶有驗證碼(圖片)卻總是登陸失敗。
- 程式碼:
public class AliYun { private static Logger logger = Logger.getLogger(AliYun.class); // 通過圖片請求地址 獲取圖片Base64編碼 public static String getImageStrFromUrl(String imgURL) { byte[] data = null; InputStream inStream = null; try { // 建立URL URL url = new URL(imgURL); // 建立連結 (注意:稍後更改程式碼在這一部分) HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5 * 1000); inStream = conn.getInputStream(); BufferedImage src = ImageIO.read(inStream); File file = new File("D:\\temp.jpg"); ImageIO.write(src, "jpg", file); InputStream inputStream = new FileInputStream(file); data = new byte[inputStream.available()]; inputStream.read(data); } catch (IOException e) { e.printStackTrace(); } finally { try { inStream.close(); } catch (IOException e) { e.printStackTrace(); } } // 對位元組陣列Base64編碼 BASE64Encoder encoder = new BASE64Encoder(); // 返回Base64編碼過的位元組陣列字串 return encoder.encode(data); } // 獲取識別後的驗證碼 public static String getLoginCode(String imgPath) { String baseImg = getImageStrFromUrl(imgPath);//base64轉換 baseImg = baseImg.replaceAll("\\r\\n", ""); String host = "http://jisuyzmsb.market.alicloudapi.com"; String path = "/captcha/recognize"; String appcode = "你購買介面之後的Code"; Map<String, String> bodys = new HashMap<String, String>(); bodys.put("pic", baseImg); HttpRequest request = HttpRequest.post(host + path + "?type=en4", bodys, true).header("Authorization", "APPCODE " + appcode).header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); String result = request.body(); logger.debug("阿里雲介面識別結果:" + result); JSONObject jsonObject = JSON.parseObject(result); if (jsonObject.containsKey("status") && jsonObject.getString("status").equals("0")) { //識別正確 {"status":"0","msg":"ok","result":{"type":"en4","code":"5sfw"}} JSONObject rs = jsonObject.getJSONObject("result"); return rs.getString("code"); } else if (jsonObject.containsKey("status") && jsonObject.getString("status").equals("210")) { //識別錯誤 {"status":"210","msg":"未知錯誤","result":""} logger.error("阿里雲介面識別失敗:" + result); } return null; } }
//模擬登陸
private void login() {
//獲取驗證碼 (1)通過呼叫上面AliYun方法獲取到 識別圖片之後的驗證碼
String vldcode = AliYun.getLoginCode(loginConfig.getUrl() + LoginConfig.CODE_URL); // 驗證碼圖片的URL
//登入 (2)使用者名稱和密碼 再攜帶上驗證碼 模擬登陸
String url = loginConfig.getUrl() + LoginConfig.LOGIN_URL.replace("{username}", loginConfig.getUsername()).replace("{password}", loginConfig.getPassword()).replace("{vldcode}", vldcode);
HttpRequest request = HttpRequest.get(url);
//獲取登入後的資料
Map<String, List<String>> headers = request.headers();
List<String> cookies = headers.get("Set-Cookie"); // (3)獲取Cookie
if (null != cookies && cookies.size() > 0) {
String cookie = cookies.get(0).split(";")[0];
String key = RedisKeyList.getLoginSessionId();
Jedis jedis = RedisClient.getJedis();
try {
jedis.set(key, cookie); // (4)存入Redis
} catch (Exception ignored) {
} finally {
RedisClient.returnResource(jedis);
}
}
}
// 攜帶有識別前驗證碼(圖片)base64編碼 識別之後的驗證碼 cookie public void 獲取資料(){
//使用過程 虛擬碼
//如果Redis 中 SeesionId(Cookie獲取的值)為空
if (Redis.getLoginSessionId == null) {
//重新登陸
login();
}
...
...
//請求介面,獲取資料
if (資料獲取失敗) {
//意味著登陸已過期,把Redis存的值清空
loginConfig.cleanSessionId();
獲取資料();//再次呼叫獲取資料
return null;
}
}
- 思路:
(1)通過圖片URL獲取圖片,呼叫阿里市場購買的介面,識別圖片,獲取驗證碼
(https://market.aliyun.com/products/57126001/cmapi014396.html#sku=yuncode839600006)阿里市場
(2)模擬登陸,並將Cookie存入Redis, 以後的資料獲取只需要攜帶Cookie即可
(3)當Cookie過期(其他人登陸網站,因為是管理網站,其他人登陸的情況少),重新獲取
- 分析:
但是,以上程式碼總是出現錯誤,Cookie總是不正確的。
(1)在分析登陸之後,我懷疑是 通過獲取URL獲取圖片驗證碼時,與(使用者名稱,密碼,驗證碼)時是不同的,也就是說,我每次模擬登陸時使用的驗證碼總是過期的。
(2)於是思考之後,得到新思路:
- 請求圖片URL,識別圖片獲取驗證碼,儲存此次請求的imgCookie.
- 模擬登陸 (使用者名稱 密碼 驗證碼) 並攜帶imgCookie.
- 新思路程式碼:
public class CookieAndCodeDTO extends BaseDTO {
// 識別前 驗證碼base64編碼
private String base64;
// 識別後 驗證碼
private String code;
//cookie
private String cookie;
public String getBase64() {
return base64;
}
public void setBase64(String base64) {
this.base64 = base64;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getCookie() {
return cookie;
}
public void setCookie(String cookie) {
this.cookie = cookie;
}
}
// 圖片識別以及攜帶Cookie/**
* Created by Administrator on 2018-2-1.
*/
public class AliYun {
private static Logger logger = Logger.getLogger(AliYun.class);
/**
* Base64編碼 解碼
* @param imgURL 驗證碼(圖片)請求路徑
* @return CookieAndCodeDTO (攜帶Cookie 和 驗證碼Base64編碼)
*/
public static CookieAndCodeDTO getImageStrFromUrl(String imgURL) {
byte[] data = null;
InputStream inStream = null;
CookieAndCodeDTO cookieAndCodeDTO = new CookieAndCodeDTO();
try {
// 建立URL
URL url = new URL(imgURL);
// 建立連結
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
//建立傳輸物件
Map<String,List<String>> headers=conn.getHeaderFields();
List<String> cookies = headers.get("Set-Cookie");
if (null != cookies && cookies.size() > 0) {
//獲取圖片時的Cookie
String cookie = cookies.get(0).split(";")[0];
cookieAndCodeDTO.setCookie(cookie);
}
inStream = conn.getInputStream();
BufferedImage src = ImageIO.read(inStream);
File file = new File("D:\\temp.jpg");
ImageIO.write(src, "jpg", file);
InputStream inputStream = new FileInputStream(file);
data = new byte[inputStream.available()];
inputStream.read(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 對位元組陣列Base64編碼
BASE64Encoder encoder = new BASE64Encoder();
// 返回Base64編碼過的位元組陣列字串
cookieAndCodeDTO.setBase64(encoder.encode(data));
return cookieAndCodeDTO;
}
/**
* 識別驗證碼(圖片)
* @param imgPath 驗證碼(圖片)請求路徑
* @return CookieAndCodeDTO (攜帶Cookie 和 驗證碼)
*/
public static CookieAndCodeDTO getLoginCode(String imgPath) {
CookieAndCodeDTO cookieAndCodeDTO = getImageStrFromUrl(imgPath);
String baseImg = cookieAndCodeDTO.getBase64();//base64轉換
baseImg = baseImg.replaceAll("\\r\\n", "");
String host = "http://jisuyzmsb.market.alicloudapi.com";
String path = "/captcha/recognize";
String appcode = "你購買介面之後的CODE";
Map<String, String> bodys = new HashMap<String, String>();
bodys.put("pic", baseImg);
HttpRequest request = HttpRequest.post(host + path + "?type=en4", bodys, true).header("Authorization", "APPCODE " + appcode).header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
String result = request.body();
logger.debug("阿里雲介面識別結果:" + result);
// result 出現過格式異常的情況
JSONObject jsonObject = JSON.parseObject(result);
if (jsonObject.containsKey("status") && jsonObject.getString("status").equals("0")) {
//識別正確 {"status":"0","msg":"ok","result":{"type":"en4","code":"5sfw"}}
JSONObject rs = jsonObject.getJSONObject("result");
cookieAndCodeDTO.setCode(rs.getString("code"));
return cookieAndCodeDTO;
} else if (jsonObject.containsKey("status") && jsonObject.getString("status").equals("210")) {
//識別錯誤 {"status":"210","msg":"未知錯誤","result":""}
logger.error("阿里雲介面識別失敗:" + result);
}
return null;
}
}
//模擬登陸 並儲存Cookieprivate void login() {
CookieAndCodeDTO cookieAndCodeDTO = AliYun.getLoginCode(LoginConfig.getUrl() + LoginConfig.CODE_URL); //(1)獲取 Cookie 和 驗證碼
assert cookieAndCodeDTO != null;
String vldcode = cookieAndCodeDTO.getCode();
assert vldcode != null;
String url = loginConfig.getUrl() + LoginConfig.LOGIN_URL.replace("{username}", loginConfig.getUsername()).replace("{password}", loginConfig.getPassword()).replace("{vldcode}", vldcode);
// (2) 模擬登陸 攜帶Cookie
HttpRequest request = HttpRequest.get(url).header("Cookie", cookieAndCodeDTO.getCookie());
// (3) 儲存Cookie
if (null != cookieAndCodeDTO.getCookie()) {
String key = RedisKeyList.getLoginSessionId();
Jedis jedis = RedisClient.getJedis();
try {
if (jedis != null) {
jedis.set(key, cookieAndCodeDTO.getCookie());
}
} catch (Exception ignored) {
} finally {
RedisClient.returnResource(jedis);
}
}
}
- 結果:
虛擬碼同上。
執行成功。(程式碼有許多不完善的地方,解釋也可能不是很準確,如果有知道的可以留言)