提要:html
查询余票网址、所需参数可见上篇博客(http://www.cnblogs.com/sei-cxt/p/8429069.html)。java
参考网上发送https请求的案例代码写出能实现查询余票的简易程序,具体代码原理详情暂且无论。数组
关于对http和https的链接和请求,网上有多种版本,用的最多的就是HttpURLConnection、HttpsURLConnection和HttpClient。如下编写的代码参考于两个网址:浏览器
http://blog.csdn.net/qh_java/article/details/52311226缓存
http://blog.csdn.net/guozili1/article/details/53995121app
1. 工具类(HttpsRequest):暂时只写了GET方法,其中要注意的是:dom
GET方法的参数是直接追加在地址后面的,所以doOutput默认为false便可。ide
在此例中,若setRequestMethod为“POST”,会报411错误,是由于没有提供相应的BODY数据。须要把doOutput设置为true,而且使用OutputStream输出数据。(虽然不会报错了,但返回的数据为空)函数
同时有的网站不容许使用“POST”方法,当用OutputStream输出数据后会报405错误。工具
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; 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.Map; import java.util.Map.Entry; import javax.net.ssl.HttpsURLConnection; public class HttpsRequest { public static String methodGet(String urlStr, Map<String, Object> params) { String realUrl = urlStr; StringBuffer sb = new StringBuffer(); // GET方法须要将查询字符串拼接在url后面 if(params != null && params.size() != 0) { realUrl += encodeParam(params); } 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)"); // 使用的何种浏览器 // 创建通讯连接 conn.connect(); // 读取response InputStreamReader isr = new InputStreamReader(conn.getInputStream(), "utf-8"); BufferedReader br = new BufferedReader(isr); String res = null; while((res = br.readLine()) != null) { sb.append(res); } br.close(); isr.close(); } catch (IOException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } return sb.toString(); } /** * 将查询的map形式的参数转码并拼成查询字符串 * @param params * @return */ private static String encodeParam(Map<String, Object> params) { if(params == null || params.size() == 0) { return ""; } StringBuffer 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); } }
2. 证书相关类(MyX509TrustManager):之后再弄明白原理。
import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; public class MyX509TrustManager implements X509TrustManager { @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } public static SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException{ TrustManager[] tm = {new MyX509TrustManager()}; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); SSLSocketFactory ssf = sslContext.getSocketFactory(); return ssf; } }
3. main函数:
其中参数params我最开始用的HashMap,可是没法获得正确的返回数据。断点后看出拼接后的网址中查询字符串顺序有颠倒,与前篇博客中抓取的网址有细微的不一样,并验证得知这就是错误缘由。因而换为LinkedHashMap(能够按照插入的顺序遍历的Map),成功。
之后考虑换为JSON或者构造一个信息类,使用类的数组去解决问题。
正确:(使用LinkedHashMap)
错误:(使用HashMap)
public static void main(String[] args) { Map<String, Object> params = new LinkedHashMap<String, Object>(); params.put("leftTicketDTO.train_date", "2018-02-24"); params.put("leftTicketDTO.from_station", "BJP"); params.put("leftTicketDTO.to_station", "SHH"); params.put("purpose_codes", "ADULT"); String url = "https://kyfw.12306.cn/otn/leftTicket/queryZ"; String response = HttpsRequest.methodGet(url, params); System.out.println(response); }
4. 获得结果:
{"data":{"flag":"1","map":{"AOH":"上海虹桥","BJP":"北京","SHH":"上海","VNP":"北京南"},"result":["|预订|240000G1512M|G151|VNP|AOH|VNP|AOH|16:35|22:35|06:00|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P2|01|12|1|0|||||||||||无|无|无||O0M090|OM9|0","|预订|2400000G2302|G23|VNP|AOH|VNP|AOH|17:00|22:39|05:39|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P3|01|10|1|0|||||||||||无|无|无||O0M090|OM9|0","|预订|240000G1530E|G153|VNP|AOH|VNP|AOH|17:15|22:49|05:34|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P3|01|09|1|0|||||||||||无|无|无||O0M090|OM9|0","|预订|240000G1551S|G155|VNP|AOH|VNP|AOH|17:20|23:08|05:48|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P4|01|11|1|0|||||||||||无|无|无||O0M090|OM9|1","|预订|240000G1570K|G157|VNP|AOH|VNP|AOH|17:43|23:30|05:47|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P3|01|09|1|0|||||||||||无|无|无||O0M090|OM9|1","|预订|240000G1591Z|G159|VNP|AOH|VNP|AOH|17:48|23:44|05:56|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P4|01|09|1|0|||||||||||无|无|无||O0M090|OM9|0","|预订|24000000G704|G7|VNP|AOH|VNP|AOH|19:00|23:24|04:24|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P3|01|04|1|0|||||||||||无|无|无||O0M090|OM9|0","|预订|24000000G900|G9|VNP|AOH|VNP|AOH|19:05|23:39|04:34|N|usY%2BUl57hWKitIUp1d4m3n3e0ys4iJTdDfedKU6oXk7F3bAb|20180224|3|P4|01|05|1|0|||||||||||无|无|无||O0M090|OM9|0","|预订|240000T1090Z|T109|BJP|SHH|BJP|SHH|19:31|10:43|15:12|N|SPyTjS4UE%2BFObI0x4k%2BxWLp41d7MjhINm2wQh8uIolnaeFXRL%2FuqgWcGuRcAiAPUTzxg45bjonU%3D|20180224|3|P4|01|09|0|0||无||无|||无||无|无|||||1040601030|14613|0","UKEhCCI8hqymqGKMu%2B8o597E1HEFnc7PrfQE9WX27aIY0MUZDkZLk7N8xX7027vok5DOLqFeijxQ%0ArVQHPRvldY8ullIIyVt2YYVGSU4WB30nfCSyIgti51pNhRkDqgPRdDCYB34cRQ34xpSBesP53hzV%0Ay%2FqGj551m%2FeK%2BAxwtosRSxdmF95FvZwU6y0H4mbfrw5CAcLy4LfQ5lHEwRKlyOD8%2BHyUCCmiqAz8%0ATTniRABrPWmdQgu2a5nZ2yE%3D|预订|240000D3130T|D313|VNP|SHH|VNP|SHH|19:34|07:41|12:07|Y|%2FeEHM%2BOX%2FhkUjkIf%2Bhc9UBJHyhWbXMzLqkOUJ61gyvzxghhA|20180224|3|P3|01|04|0|0||||有|||无||||无||||O0O040|OO4|0","B2ptaQsZngo3cuVpKR490RKFxw%2FujNVfaO8q%2Fz9nOn%2Bv9%2BlQsoxygraTjYr8hjp4ls6MDs0jnusA%0Alafi6AR3NiTZBBs4vA41Stjj2m%2Fty%2FyLO2TDRnjk5Mzt00a7ADMG6L56oaDDK7NpYbx8pNPr7ZPP%0AMeVYdrHdxA3VSz78Sx7mRlmOfko8C6ep%2FB4PGNKvywXfmmmiWPr7p%2BRm39gQT5ISQ5WBmGDRFvbJ%0Al3CPql7gu563|预订|240000D3110J|D311|VNP|SHH|VNP|SHH|21:16|09:09|11:53|Y|%2BypbKTC2HZlFzZByUcQ3VvnTcGuNsar%2B|20180224|3|P2|01|04|0|0||||无||||||||||有|F040|F4|0","nr35RXw6%2BkkqgJ0axRZOQs8MeSPKVstyvt6jqk5NNZpZurw8b0HiI8mnxKbMGYPxoDKfGAvI4%2Bdv%0AsQoW9lSHwZK4PaPjoYpWM90B7gL1IjKy5NW6zsf95CtVyWh8Qgd0nIaDU%2F0NGpD%2FyJiX%2BnUa6igm%0AtPNlEGe2pjgpcCLkQeKs9z3dHdQS18rHxPTnChe8HRrsLTGcBzxzMW%2BbjNE%2FWQqQaLeYSwTJImP5%0Az%2F60kEOkSBw29fQO2Cbp%2Bps%3D|预订|240000D3210D|D321|VNP|SHH|VNP|SHH|21:23|09:15|11:52|Y|Qu9exXtC3AVH27oNnBfVX5zFLnqdhQ1lY5vwmAt4du%2FqF0zQ|20180224|3|P2|01|04|0|0||||有|||无||||无||||O0O040|OO4|0"]},"httpstatus":200,"messages":"","status":true}