https://github.com/lxd7788/Train 代碼地址
准備
安裝postgreSQL數據庫,和可視化工具pgadmin3,或者其他數據庫
實現功能,抓取12306全部的站點,並實現通過站點查詢出所有經過次站點的車次,通過車次查出次列車經過的城市
分析
分析12306,找合適的接口,最符合要求的是查詢車次的這張頁面,但是有驗證碼,無形增加了難度
經過分析,合適的頁面是車票預訂的頁面,查詢兩個站點直接的車次,用火狐自帶的f12工具,點擊查詢清晰的看到只有一條get請求
再看響應的內容,json,根據經驗這是我們想要東西
通過這條鏈接,我們可以得到兩站點之間的車次信息,我們只需要車次的名稱就好了,通過字符串或者正則都可以,正則不太熟,我用的是字符串
分析怎么才能把全國的站點和車次都抓取到,並且實現彼此查詢的功能?
站點和城市多對多關系,理應建立三張表,用中間表關聯.最后放棄了三表的想法,使用一張表聯合主鍵實現
只要獲取到全國的城市站點,通過for循環兩兩測試,不就可以得到全部的火車車次了,並且兩列都是主鍵,同時還解決了兩個城市之間車次重復的問題,
1 CREATE TABLE public.t_city 2 ( 3 city_name character varying(64) NOT NULL, 4 train_num character varying(64) NOT NULL, 5 CONSTRAINT t_city_pkey PRIMARY KEY (city_name, train_num) 6 )
下一步要找到全國的火車站點
從頁面的一條js中,找到一條連接 https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9049
一條json數據
接下來,開始寫程序,解析數據
數據庫鏈接(換了台電腦改成了mysql數據庫
public class DbUtil { private static final String DBDRIVER = "com.mysql.jdbc.Driver"; private static final String url = "jdbc:mysql://127.0.0.1:3306/lianxi"; private static final String USER = "root"; private static final String PASSWORD = "123"; public Connection getConn() throws ClassNotFoundException, SQLException { Class.forName(DBDRIVER); Connection conn = DriverManager.getConnection(url, USER, PASSWORD); return conn; } public void closeConn(Connection conn) throws SQLException { if (conn != null) { conn.close(); } } public static void main(String args[]) { DbUtil dbUtil = new DbUtil(); Connection conn=null; try { conn = dbUtil.getConn(); System.out.println(conn + "數據庫連接成功"); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); System.out.println("失敗"); }finally { try { dbUtil.closeConn(conn); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
數據庫用sqlyog直接建好表
創建一個CityMd類
public class CityMd { private String cityName; private String trainName 、、、、、、
創建方法,實現數據庫數據添加
public class DbChange { public int add(Connection conn,CityMd cityMd) { String sql="insert into t_city values(?,?,null)"; int n=0; try { PreparedStatement p=conn.prepareStatement(sql); p.setString(1, cityMd.getCityName()); p.setString(2, cityMd.getTrainName()); n=p.executeUpdate(); p.close(); } catch (SQLException e) { // TODO Auto-generated catch block //e.printStackTrace(); }finally { try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return n; } }
抓取全部的站點,返回一個二維數組,通過之前的url,發現參數有出發地和目的地,並且是字母編號的形式,所以把城市和編號同時抓取下來
分析json數據,每個成熟以@分隔,其次又以|分隔,所以可以用字符串分隔,正則很方便,不過熟悉沒用
public class CityUtil { public String[][] getCity() throws Exception{ String cityurl="https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9048"; HttpGet httpget=new HttpGet(cityurl); CloseableHttpClient httPclient=HttpClients.createDefault(); CloseableHttpResponse Response=httPclient.execute(httpget); HttpEntity entity=Response.getEntity(); String result=EntityUtils.toString(entity,"utf-8"); //System.out.println("請求結果"+result); int l1=result.indexOf("'"); int l2=result.length(); String city=result.substring(l1+1, l2); String[] c=city.split("@"); //導入二維數組 int l=c.length-1; String[][] str=new String[l][2]; for(int i=1;i<c.length;i++) { String[] cc=c[i].split("[|]"); //System.out.println(cc[1]+" "+cc[2]); str[i-1][0]=cc[1]; str[i-1][1]=cc[2]; } return str; } }
這樣就得到了一個全部站點的數組
接下來寫兩地間車次的方法,兩地之間肯定會有很多火車,所以返回數組
public class GetUtil { public String[] getList(String url) {
CloseableHttpClient httPclient=HttpClients.createDefault();
HttpGet httpgett=new HttpGet(url); CloseableHttpResponse Response; String result1=null; try { Response = httPclient.execute(httpgett); HttpEntity entity=Response.getEntity(); result1=EntityUtils.toString(entity,"utf-8"); //System.out.println("請求結果"+result1); } catch (ClientProtocolException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } JSONObject jSONObject =JSONObject.fromObject(result1); Object listObject=jSONObject.get("data"); jSONObject=JSONObject.fromObject(listObject); JSONArray json=jSONObject.getJSONArray("result"); //存放火車列次的數組 String[] strs=new String[json.size()]; for (int i = 0; i < json.size(); i++) { String str=json.getString(i); String[] arr=str.split("[|]"); strs[i]=arr[3]; } return strs; } }
剩下的就是,整合起來,開始測試
get請求的url參數有四個,第一個是時間,第二個是出發點,第三個是目的地,最后一個成人票這個不是關鍵直接寫死
時間也寫死,但是有一個問題就是,每天的車次肯定會有差異,這樣只看一看的車次數據肯定不精准。想一個辦法就是,跑完數據之后,換個時間再抓一次,反正重復的自己會跳過去、、、、、、、
一定注意要加時間間隔,開始跑沒有加,沒一會12306就給限制請求了
GetUtil getUtil=new GetUtil(); //兩地之間的車次 DbUtil dbUtil=new DbUtil(); //獲取conn Zhuanhua zh=new Zhuanhua(); //集合轉數組 String trainurl="https://kyfw.12306.cn/otn/leftTicket/queryO?"; String train_date="leftTicketDTO.train_date=2018-03-20"; String from_station=null; String to_station=null; String newurl=null; //獲取conn //Connection conn=dbUtil.getConn(); DbChange db=new DbChange(); //獲取全部城市和它代號 CityUtil cu=new CityUtil(); String[][] str=cu.getCity(); //循環所有情況 int count=0; Connection conn=null; for(int i=10; i<str.length; i++) { Thread.sleep(1000); for(int j=0; j<str.length; j++) { Random r = new Random(); int nnn=r.nextInt(6); Thread.sleep(nnn*2000); //拼接鏈接請求鏈接 from_station=str[i][1]; to_station=str[j][1]; //排除出發和目的是一個 if(from_station.equals(to_station)) { continue; } newurl=trainurl+train_date+"&leftTicketDTO.from_station=" +from_station+"&leftTicketDTO.to_station="+to_station+"&purpose_codes=ADULT"; //調用方法,獲取兩地之間的火車數組 String[] ss=getUtil.getList(newurl); for(int k=0;k<ss.length;k++) { CityMd cityMd=new CityMd(str[i][0],ss[k]); conn=dbUtil.getConn(); int nn=db.add(conn, cityMd); System.out.println("運行第"+k+"次出發地:"+str[i][0]+"==>目的地:"+str[j][0]); count+=nn; } } } System.out.println("共計導入數據"+count);
最終數據庫實現