一、引言

        为了实现地址选择等功能,经常需要获取详细地区信息,本文面向这一目标设计了数据库,并通过jsoup爬虫爬取最新数据。

二、数据库设计

        数据库结构,此处较为简单,就保存了每个区域的层级、名称及其上级区域的代码,以便查询层级关系。

-- ----------------------------
-- Table structure for district
-- ----------------------------
DROP TABLE IF EXISTS `district`;
CREATE TABLE `district`  (
  `_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
  `region_name` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT '地区名称',
  `region_short_name` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL COMMENT '地区简称',
  `region_code` varchar(10) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL COMMENT '行政地区编号',
  `region_level` tinyint(3) UNSIGNED NOT NULL COMMENT '地区级别 1-省、自治区、直辖市 2-地级市、地区、自治州、盟 3-市辖区、县级市、县',
  `region_parent_id` int(10) UNSIGNED NOT NULL COMMENT '所属上级地区编号',
  PRIMARY KEY (`_id`) USING BTREE
);

三、获取数据并生成导入脚本

        在民政网站可以查询全国地区信息,地址:县以上行政区划代码

        可以发现数据具有极强的顺序性规律:所有地域都紧邻其上级地区之后,即所有市都在其所属省份之后而不穿插其他省份,所有县都在其所属市之后而不穿插其他市。这为我们之后的数据处理提供了极大的方便。

        爬取前,先通过F12查看网页结构,可以发现:省、市地区的class为xl722830,而县一级的class为xl732830,不能通过一个类名获取所有信息,因此本文采取的方法为遍历所有tr获取地区代码和名称。为将数据插入库中,同时输出了sql脚本。代码如下:

public class Districter {
    public static void main(String[] args) {
        try {
            //2020年12月中华人民共和国县以上行政区划代码网页
            Document doc = Jsoup.connect("http://www.mca.gov.cn/article/sj/xzqh/2020/20201201.html").get();
            Element eDiv = doc.getElementById("2020年12月份县以上行政区划代码_28320");
            Element tBody = eDiv.getElementsByTag("tbody").get(0);
            Elements trs = tBody.getElementsByTag("tr");
            // 以上为爬取部分,trs保存所有地域数据的行,

            // 处理trs获取地区代码和名称
            List<String> codes = new ArrayList<String>();
            List<String> names = new ArrayList<String>();
            for (int i = 3; i < trs.size()-9; i++) {    // 跳过不属于地区信息的tr
                Elements info = trs.get(i).getElementsByTag("td");
                String code = info.get(1).text().trim();
                String name = info.get(2).text().trim();
                codes.add(code);
                names.add(name);
            }

            // 判断数据正确性
            assert codes.size()== names.size():"地区代码与地区名称数目不相等!!";
            processDistricts(codes, names);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置标记判断直辖市等特殊情况,一次遍历处理地区数据
     * @param codeList
     * @param nameList
     * @throws IOException
     */
    private static void processDistricts(List<String> codeList, List<String> nameList) throws IOException {
        // 输出sql脚本
        String filePath="path/to/script";
        BufferedWriter writer = new BufferedWriter(new FileWriter(filePath));

        int num = codeList.size();
        // x、y分别保存i之前的省、市级地区的下标,即当前市县所属于的上级地区
        int x=0,y=0;
        // 标记一级地区(省、直辖市等)下是否有二级地区(市)
        boolean has2=false;
        String code;
        for (int i = 0; i < num; i++) {
            code=codeList.get(i);
            if (code.endsWith("0000")){
                System.out.println(codeList.get(i) +"\t"+nameList.get(i)+",1级");
                writer.write("insert into district(region_name,region_code,region_level,region_parent_id) values('"
                        +nameList.get(i)+"','"+codeList.get(i)+"',1,'0');"
                        +"\n");
                has2=false;
                x=i;
            }else if (code.endsWith("00")){
                System.out.println(codeList.get(i) + "\t" + nameList.get(i) + "\t" + "2级,属于" + nameList.get(x));
                writer.write("insert into district(region_name,region_code,region_level,region_parent_id) values('"
                                +nameList.get(i)+"','"+codeList.get(i)+"',2,'"+codeList.get(x)+"');"+"\n");
                has2=true;
                y=i;
            }else {
                if (has2){
                    System.out.println(codeList.get(i) + "\t" + nameList.get(i) + "\t" + "3级,属于:" + nameList.get(y));
                    writer.write("insert into district(region_name,region_code,region_level,region_parent_id) values('"
                            +nameList.get(i)+"','"+codeList.get(i)+"',3,'"+codeList.get(y)+"');"
                            +"\n");
                }else {
                    System.out.println(codeList.get(i) + "\t" + nameList.get(i) + "\t" + "3级,属于:" + nameList.get(x));
                    writer.write("insert into district(region_name,region_code,region_level,region_parent_id) values('"
                            +nameList.get(i)+"','"+codeList.get(i)+"',3,'"+codeList.get(x)+"');"
                            +"\n");
                }

            }
        }
        writer.close();
    }

}

四、使用

基本的SQL语句就好。

1.查看所有一级地区:

select * from district where region_parent_id='0';

2.查看北京市各下级地区:

select * from district where region_parent_id='110000';

3.搜索陕西省下属地区

select * from district where region_parent_id=(select region_code from district where region_name='陕西省');

更多推荐