1. 程式人生 > >Java帶有驗證碼的模擬登陸

Java帶有驗證碼的模擬登陸

  • 需求:

        最近得到一個需求,需要模擬登陸網頁,然後通過網頁介面獲取相應資料。一共兩個網頁,其中沒有驗證碼的網頁比較容易的模擬登陸成功。但是另一個帶有驗證碼(圖片)卻總是登陸失敗。

  • 程式碼:
獲取識別後的驗證碼
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)於是思考之後,得到新思路:

  1. 請求圖片URL,識別圖片獲取驗證碼,儲存此次請求的imgCookie.
  2. 模擬登陸  (使用者名稱 密碼  驗證碼) 並攜帶imgCookie.
  • 新思路程式碼:
用於攜帶 驗證碼base64編碼  驗證碼   cookie   的DTO
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;
    }
}
//模擬登陸 並儲存Cookie
private 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);
            }
        }

    }
  • 結果:

虛擬碼同上。

執行成功。(程式碼有許多不完善的地方,解釋也可能不是很準確,如果有知道的可以留言)