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

     最终数据库实现

  • 相关阅读:
    c# vs2010 excel 上传oracle数据
    Viola-Jones人脸检測
    apache commons-configuration包读取配置文件
    linux 读取文件
    linux 统计某个文件的行数
    linux 判空处理
    linux 查看某个目录下文件的数量
    nginx 配置文件正确性测试
    使用postman上传excel文件测试导入excel
    java 反射获取字段为List类型中的泛型类型
  • 原文地址:https://www.cnblogs.com/xiandong/p/8587558.html
Copyright © 2020-2023  润新知