前言

  最近收到客服反馈,零碎的省市区数据如同不准,并且缺了一些地区。通过询问共事得悉,数据库内的数据是从老我的项目拷贝过去的,有些年头了。难怪会缺一些数据。正好最近在对接网商银行,发现网商提供了省市区的数据的接口。这就很难受了哇,抄起键盘就是干,很快的就把同步程序写好了。

  而后在同步的过程中,发现网商提供的数据和数据库有些对不上。于是默默的关上淘宝京东增加收货地址,看看到底是谁错了。比照到前面发现都有些差别。这就很蛋疼了。看来这个时候谁都不能置信了,只能信国家了。于是我关上了中华人民共和国民政部网站来比对异样的数据。

  比照的过程中,石锤网商数据不准。值得的是褒扬淘宝京东曾经同步了最新的数据了。然而呢,我并没有找到它们的数据接口。为了修改零碎的数据,只能本人爬取了。

锁定爬取指标

爬取地址如下:

https://preview.www.mca.gov.c...

  爬取原理很简略,就是解析HTML元素,而后获取到相应的属性值保留下来就好了。因为应用Java进行开发,所以选用Jsoup来实现这个工作。

<!-- HTML解析器 --><dependency>  <groupId>org.jsoup</groupId>  <artifactId>jsoup</artifactId>  <version>1.13.1</version></dependency>

网页数据分析

  因为须要解析HTML能力取到数据,所以须要晓得数据存储在什么元素上。咱们能够关上chrom的控制台,而后选中对应的数据,即可查看存储数据的元素。

  通过剖析,发现每一行数据都是存储在一个<tr>标签下。咱们须要的 区域码区域名称存储在第一和第二个<td>内 。与此同时还要很多空白<td>标签,在编写代码是须要将其过滤掉。

定义根底代码

  先定义好咱们的爬取指标,以及Area实体类。

public class AreaSpider{    // 爬取指标    private static final String TARGET = "http://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html";    @Data    @AllArgsConstructor    private static class Area {        // 区域码        private String code;        // 区域名称        private String name;        // 父级        private String parent;    }}

爬虫代码编写

public static void main(String[] args) throws IOException{  // 申请网页  Jsoup.connect(TARGET).timeout(10000).get()    // 筛选出 tr 标签    .select("tr")    // 筛选出 tr 下的 td 标签    .forEach(tr -> tr.select("td")    // 过滤 值为空的 td 标签    .stream().filter(td -> StringUtils.isNotBlank(td.text()))    // 输入后果    .forEach(td -> System.out.println(td.text())));}

解析后果

代码优化

  通过下面的代码,咱们曾经爬取到了页面上的数据。然而并没有达到咱们的预期,所以进一步解决将其转换为Area实体。

public static void main(String[] args) throws IOException{  // 申请网页  List<Area> areaList = Jsoup.connect(TARGET).timeout(10000).get()    // 筛选出 tr 标签    .select("tr")    // 筛选出 tr 下的 td 标签    .stream().map(tr -> tr.select("td")    // 过滤 值为空的 td 标签,并转换为 td 列表    .stream().filter(td -> StringUtils.isNotBlank(td.text())).collect(Collectors.toList()))    // 后面提到,区域码和区域名称别离存储在 第一和第二个td,所以过滤掉不符合规范的数据行。    .filter(e -> e.size() == 2)    // 转换为 area 对象    .map(e -> new Area(e.get(0).text(), e.get(1).text(), "0")).collect(Collectors.toList());      // 遍历数据  areaList.forEach(area -> System.out.println(JSONUtil.toJsonStr(area)));}

解析后果

  至此,离咱们想要的数据还差了父级区域码 ,咱们能够通过区域码计算出来。以河北省为例:河北省:130000石家庄市:130100长安区:130102能够发现法则:0000 结尾是省份,00是市。所以就有了如下代码:

private static String calcParent(String areaCode){    // 省 - 针对第一行非凡解决    if(areaCode.contains("0000") || areaCode.equals("行政区划代码")){        return "0";    // 市    }else if (areaCode.contains("00")) {        return String.valueOf(Integer.parseInt(areaCode) / 10000 * 10000);    // 区    }else {        return String.valueOf(Integer.parseInt(areaCode) / 100 * 100);    }}

最终代码

public class AreaSpider{    // 爬取指标    private static final String TARGET = "http://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html";    @Data    @AllArgsConstructor    private static class Area{        // 区域码        private String code;        // 区域名称        private String name;        // 父级        private String parent;    }    public static void main(String[] args) throws IOException{        // 申请网页        List<Area> areaList = Jsoup.connect(TARGET).timeout(10000).get()                // 筛选出 tr 标签                .select("tr")                // 筛选出 tr 下的 td 标签                .stream().map(tr -> tr.select("td")                // 过滤 值为空的 td 标签,并转换为 td 列表                .stream().filter(td -> StringUtils.isNotBlank(td.text())).collect(Collectors.toList()))                // 后面提到,区域码和区域名称别离存储在 第一和第二个td,所以过滤掉不符合规范的数据行。                .filter(e -> e.size() == 2)                // 转换为 area 对象                .map(e -> new Area(e.get(0).text(), e.get(1).text(), calcParent(e.get(0).text()))).collect(Collectors.toList());        // 去除 第一行 "行政区划代码|单位名称"        areaList.remove(0);        areaList.forEach(area -> System.out.println(JSONUtil.toJsonStr(area)));    }    private static String calcParent(String areaCode){        // 省 - 针对第一行非凡解决        if(areaCode.contains("0000") || areaCode.equals("行政区划代码")){            return "0";        // 市        }else if (areaCode.contains("00")) {            return String.valueOf(Integer.parseInt(areaCode) / 10000 * 10000);        // 区        }else {            return String.valueOf(Integer.parseInt(areaCode) / 100 * 100);        }    }}

数据修改

  因为咱们须要的是省市区三级数据联动,然而了直辖市只有两级,所以咱们人工的给它加上一级。以北京市为例:变成了 北京 -> 北京市- >东城区,对于其余的直辖市也是同样的解决逻辑。

  修改好的数据奉上,有须要的小伙伴能够自取哦。

  • JSON-2020-11县以上行政区划代码
  • SQL-2020-11县以上行政区划代码
对于直辖市也能够做两级的,这个次要看产品的需要吧

总结

  总体来讲,这个爬虫比较简单,只有简略的几行代码。毕竟网站也没啥反扒的机制,所以很轻松的就拿到了数据。

结尾

  嘿嘿话说,你都爬过哪些网站呢?

  如果感觉对你有帮忙,能够多多评论,多多点赞哦,也能够到我的主页看看,说不定有你喜爱的文章,也能够顺手点个关注哦,谢谢。

  我是不一样的科技宅,每天提高一点点,体验不一样的生存。咱们下期见!