java爬蟲12306,爬取所有的站點和車次,並導入postgreSQL數據庫


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);

 最終數據庫實現

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM