提要:html
登陸流程、所需參數可見以前博客(http://www.cnblogs.com/sei-cxt/p/8429069.html)。java
1. 注意點一:驗證碼。web
2. 注意點二:Cookie。編程
3. 示例代碼和輸出結果。瀏覽器
4. 之後要考慮的問題。緩存
1. 注意點一:驗證碼。cookie
首先是獲取驗證碼,可知發送GET驗證碼請求後,12306服務端返回的是一張驗證碼圖片,能夠臨時存在根目錄下。同時,上篇博客(http://www.cnblogs.com/sei-cxt/p/8435921.html)寫的GET方法是沒有區分返回文件格式是image仍是text的,這個判斷要加上。網絡
獲得的驗證碼圖片以下,關於自動驗證驗證碼的技術暫時不討論,須要學習圖像識別技術。咱們如今只說如何本身選擇驗證碼並向服務端發送咱們選擇的驗證碼。session
如上圖,咱們發送的答案實際上是以圖片左上頂點爲原點的座標,這一點經過fiddler抓包也能夠發現,其中%2C是通過編碼的「,」。app
編碼緣由是:
Web設計人員面對的挑戰之一是,要處理操做系統之間的區別。這些不一樣會致使URL的問題:例若有些操做系統容許文件中有空格,而有些不容許。多數操做系統不反對文件名中出現#號,但在URL中#號表示文件名的結束,後面是片斷標識符。其餘特殊字符、非字母數字字符等等,在URL或另外一種操做系統中有特殊的意義,這也會產生相似的問題。爲解決這些問題,URL使用的字符必須來自ASCII固定的子集,確切地講,包括: ·大寫字母A~Z ·小寫字母a~z ·數字0~9 ·標點符號字符-_.!~*’和, …… 編碼方式很是簡單。除了前面指定的ASCII數字、字母和標點符號外,全部字符都要轉換爲字節,每一個字節要寫爲百分號後面加兩個十六進制數字。空格是一種特殊狀況,由於它太普通了。空格不是編碼爲%20,而是編碼爲加號(+)。加號自己編碼爲%2B。/ # = &和?字符在用於名時應當編碼,而用於URL各部分的分隔符時不用編碼。 ——《Java網絡編程》第七章 Elliotte Rusty Harold著 朱濤江 林劍 譯
所以咱們能夠固定寫死座標,只用輸入第幾張圖便可。
// 每一個圖的位置,直接寫死,之後可改 String[] pos = {"40,75", "112,75", "183,75", "255,75", "40,150", "112,150", "183,150", "255,150"};
2. 注意點二:Cookie。
沒有Cookie,就沒有辦法實現登陸。由於登陸有三步,不一樣步驟之間要告知信息,好比「已經成功驗證驗證碼,能夠驗證登陸信息了」。
要是沒有驗證驗證碼,直接向登陸的網站發送用戶名和密碼,會返回:{"result_message":"驗證碼校驗失敗","result_code":"5"}
經過網上查詢資料和核對fiddler請求頭和響應頭的數據得知,前一次的相應頭裏「Set-Cookie」字段能夠用於下一步驟請求頭的「Cookie」,這裏有幾個坑要注意一下。
1) 12306的「Set-Cookie」會發好幾個,獲得的數據是List<String>,直接用conn.getHeaderField("Set-Cookie")得到的cookie不全。由於getHeaderField(String name)這個方法只返回最後一次設置的值,返回類型是String(查API),必須用getHeaderFields()方法,從中找到key爲"Set-Cookie"的數據而後用「; 」鏈接起來構成一個String字符串(嘗試知有重複的數值對是容許的)。
2) 不能直接不鏈接得到的List<String>,而是屢次用conn.setRequestProperty("Cookie", cookie)發送。由於setRequestProperty(String param1, String param2)方法中,param1如果有重複,存的值會被param2覆蓋(看源碼)。
3. 示例代碼和輸出結果。
1) 工具類(HttpsRequest):
相較以前加入了POST方法,其與GET方法類似度很高,以後考慮合爲一個方法。
GET方法裏新加了區分返回類型的代碼。
新添對Cookie的處理。
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLEncoder; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Map.Entry; import javax.net.ssl.HttpsURLConnection; public class HttpsRequest { public static HttpsRequestInfo methodGet(String urlStr, Map<String, Object> params, String cookie) { String realUrl = urlStr; StringBuffer responce = new StringBuffer(); StringBuffer set_cookie = new StringBuffer(); // GET方法須要將查詢字符串拼接在url後面 if(params != null && params.size() != 0) { realUrl += encodeParam(params, "GET"); } try { // 鏈接url URL url = new URL(realUrl); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("GET"); // URL鏈接可用於輸入和/或輸出 // 若是打算使用 URL鏈接進行輸出,則將DoOutput標誌設置爲true,默認值爲false // 若是打算使用 URL鏈接進行輸入,則將DoInput標誌設置爲true,默認值爲true // conn.setDoOutput(false); // conn.setDoInput(true); // 容許鏈接使用任何可用的緩存,默認值爲true // conn.setUseCaches(true); conn.setSSLSocketFactory(MyX509TrustManager.getSSLSocketFactory()); // 設置通常請求屬性 conn.setRequestProperty("accept", "*/*"); // 客戶端能夠處理哪些數據類型 conn.setRequestProperty("connection", "Keep-Alive"); // 設置長鏈接,無過時時間 conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 使用的何種瀏覽器 if(cookie != null && !cookie.equals("")) { conn.setRequestProperty("Cookie", cookie); // 設置cookie,保證登陸 } // 創建通訊連接 conn.connect(); // 獲取cookie Map<String, List<String>> headerMap = conn.getHeaderFields(); for(Entry<String, List<String>> entry : headerMap.entrySet()) { if(entry.getKey() != null && entry.getKey().equals("Set-Cookie")) { for(String str : entry.getValue()) { set_cookie.append(str + "; "); } } } // 判斷返回格式 // getHeaderField(String name)只返回最後一次設置的值,因此找cookie的時候不能用 String type = conn.getHeaderField("Content-Type"); if(type.startsWith("image")) { // 保存驗證碼圖片 InputStream is = conn.getInputStream(); FileOutputStream fos = new FileOutputStream("CAPTCHA.jpg"); int b = 0; while((b = is.read()) != -1) { fos.write(b); } is.close(); fos.close(); } else { // 讀取response BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8")); String res = null; while((res = br.readLine()) != null) { responce.append(res); } br.close(); } } catch (IOException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } return new HttpsRequestInfo(responce.toString(), set_cookie.toString()); } public static HttpsRequestInfo methodPost(String urlStr, Map<String, Object> params, String cookie) { StringBuffer responce = new StringBuffer(); StringBuffer set_cookie = new StringBuffer(); try { // 鏈接url URL url = new URL(urlStr); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("POST"); // URL鏈接可用於輸入和/或輸出 // 若是打算使用 URL鏈接進行輸出,則將DoOutput標誌設置爲true,默認值爲false // 若是打算使用 URL鏈接進行輸入,則將DoInput標誌設置爲true,默認值爲true conn.setDoOutput(true); // conn.setDoInput(true); // 容許鏈接使用任何可用的緩存,默認值爲true // conn.setUseCaches(true); conn.setSSLSocketFactory(MyX509TrustManager.getSSLSocketFactory()); // 設置通常請求屬性 conn.setRequestProperty("accept", "*/*"); // 客戶端能夠處理哪些數據類型 conn.setRequestProperty("connection", "Keep-Alive"); // 設置長鏈接,無過時時間 conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 使用的何種瀏覽器 if(cookie != null && !cookie.equals("")) { conn.setRequestProperty("Cookie", cookie); // 設置cookie,保證登陸 } // 創建通訊連接 conn.connect(); // POST須要發送請求的數據 if(params != null && params.size() != 0) { BufferedWriter bw = new BufferedWriter( new OutputStreamWriter(conn.getOutputStream(), "utf-8")); bw.write(encodeParam(params, "POST")); bw.flush(); bw.close(); } // 獲取cookie Map<String, List<String>> headerMap = conn.getHeaderFields(); for(Entry<String, List<String>> entry : headerMap.entrySet()) { if(entry.getKey() != null && entry.getKey().equals("Set-Cookie")) { for(String str : entry.getValue()) { set_cookie.append(str + "; "); } } } // 讀取response BufferedReader br = new BufferedReader( new InputStreamReader(conn.getInputStream(), "utf-8")); String res = null; while((res = br.readLine()) != null) { responce.append(res); } br.close(); } catch (IOException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } return new HttpsRequestInfo(responce.toString(), set_cookie.toString()); } /** * 將查詢的map形式的參數轉碼並拼成查詢字符串 * @param params * @return */ private static String encodeParam(Map<String, Object> params, String method) { if(params == null || params.size() == 0) { return ""; } StringBuffer sb = null; if (method.equalsIgnoreCase("GET")) { sb = new StringBuffer("?"); } else if (method.equalsIgnoreCase("POST")) { sb = new StringBuffer(""); } for(Entry<String, Object> para : params.entrySet()) { try { sb.append(URLEncoder.encode(para.getKey(), "UTF-8")); sb.append("="); sb.append(URLEncoder.encode(para.getValue().toString(), "UTF-8")); sb.append("&"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } String query = sb.toString(); // 去掉最後一個& return query.substring(0, query.length() - 1); } /** * 選擇獲得驗證碼答案座標 * @param choose * @return */ private static String getCaptchaAnswer(String choose) { // 每一個圖的位置,直接寫死,之後可改 String[] pos = {"40,75", "112,75", "183,75", "255,75", "40,150", "112,150", "183,150", "255,150"}; String[] chooses = choose.split(","); StringBuffer answer = new StringBuffer(); for(String cho : chooses) { answer.append(pos[Integer.parseInt(cho) - 1] + ","); } String ans = answer.toString(); return ans.substring(0, ans.length() - 1); } }
2) 證書相關類(MyX509TrustManager):與以前沒有變化,不貼。
3) 網絡信息類(HttpsRequestInfo):用來保存相應返回的信息和Cookie,原先的設計不能返回兩個字符串。
public class HttpsRequestInfo { private String responce; private String cookie; public HttpsRequestInfo() { } public HttpsRequestInfo(String responce, String cookie) { super(); this.responce = responce; this.cookie = cookie; } public String getResponce() { return responce; } public void setResponce(String responce) { this.responce = responce; } public String getCookie() { return cookie; } public void setCookie(String cookie) { this.cookie = cookie; } }
4) main函數:驗證驗證碼的響應頭傳回來的Cookie爲空,所以登陸傳用戶名密碼的時候只能用發驗證碼圖片的時候傳回來的Cookie,具體緣由之後考慮。
public static void main(String[] args) { Map<String, Object> params1 = new LinkedHashMap<String, Object>(); params1.put("login_site", "E"); params1.put("module", "login"); params1.put("rand", "sjrand"); HttpsRequestInfo info1 = HttpsRequest.methodGet("https://kyfw.12306.cn/passport/captcha/captcha-image", params1, null); System.out.println(info1.getResponce()); System.out.println(info1.getCookie()); Scanner in = new Scanner(System.in); String line = in.nextLine(); in.close(); Map<String, Object> params2 = new LinkedHashMap<String, Object>(); params2.put("answer", HttpsRequest.getCaptchaAnswer(line)); params2.put("login_site", "E"); params2.put("rand", "sjrand"); HttpsRequestInfo info2 = HttpsRequest.methodPost("https://kyfw.12306.cn/passport/captcha/captcha-check", params2, info1.getCookie()); System.out.println(info2.getResponce()); System.out.println(info2.getCookie()); Map<String, Object> params3 = new LinkedHashMap<String, Object>(); params3.put("username", "******"); params3.put("password", "******"); params3.put("appid", "otn"); HttpsRequestInfo info3 = HttpsRequest.methodPost("https://kyfw.12306.cn/passport/web/login", params3, info1.getCookie()); System.out.println(info3.getResponce()); System.out.println(info3.getCookie()); }
5) 輸出結果:
BIGipServerpool_passport=300745226.50215.0000; path=/; _passport_ct=3a6f7d3cb29b4bd7b9ac3da2c41a8588t5927; Path=/passport; _passport_session=5a55bcfaa5644450b24d4f2ed8f1e7840821; Path=/passport; 2,7 {"result_message":"驗證碼校驗成功","result_code":"4"} {"result_message":"登陸成功","result_code":0,"uamtk":"nrsMZEFcdXx8EK1B_n9ODPC7-ErURBJGrANQivMxnVc091210"} uamtk=nrsMZEFcdXx8EK1B_n9ODPC7-ErURBJGrANQivMxnVc091210; Path=/passport; _passport_ct=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/passport;
4. 之後要考慮的問題。
返回信息之後要用JSON解析,而且要判斷result_code來決定以後的操做。