最近收到客服反应,系统的省市区数据好像不许,而且缺了一些地区。通过询问同事得知,数据库内的数据是从老项目拷贝过来的,有些年头了。难怪会缺一些数据。正好最近在对接网商银行,发现网商提供了省市区的数据的接口。这就很舒服了哇,抄起键盘就是干,很快的就把同步程序写好了。html
而后在同步的过程当中,发现网商提供的数据和数据库有些对不上。因而默默的打开淘宝
和京东
添加收货地址,看看究竟是谁错了。对比到后面发现都有些差别。这就很蛋疼了。看来这个时候谁都不能相信了,只能信国家了。因而我打开了中华人民共和国民政部网站来比对异常的数据。java
对比的过程当中,石锤网商数据不许。值得的是表扬淘宝
和京东
已经同步了最新的数据了。可是呢,我并无找到它们的数据接口。为了修正系统的数据,只能本身爬取了。git
爬取地址以下:github
https://preview.www.mca.gov.c...
爬取原理很简单,就是解析HTML元素,而后获取到相应的属性值保存下来就行了。因为使用Java进行开发,因此选用Jsoup来完成这个工做。sql
<!-- HTML解析器 --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.13.1</version> </dependency>
因为须要解析HTML才能取到数据,因此须要知道数据存储在什么元素上。咱们能够打开chrom的控制台,而后选中对应的数据,便可查看存储数据的元素。数据库
经过分析,发现每一行数据都是存储在一个<tr>
标签下。咱们须要的 区域码
和区域名称
存储在第一和第二个<td>
内 。与此同时还要不少空白<td>
标签,在编写代码是须要将其过滤掉。json
先定义好咱们的爬取目标,以及Area
实体类。ide
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); } } }
因为咱们须要的是省市区三级数据联动,可是了直辖市只有两级,因此咱们人工的给它加上一级。以北京市为例:变成了 北京 -> 北京市- >东城区,对于其余的直辖市也是一样的处理逻辑。
修正好的数据奉上,有须要的小伙伴能够自取哦。
对于直辖市也能够作两级的,这个主要看产品的需求吧
整体来说,这个爬虫比较简单,只有简单的几行代码。毕竟网站也没啥反扒的机制,因此很轻松的就拿到了数据。
嘿嘿话说,你都爬过哪些网站呢?
若是以为对你有帮助,能够多多评论,多多点赞哦,也能够到个人主页看看,说不定有你喜欢的文章,也能够随手点个关注哦,谢谢。
我是不同的科技宅,天天进步一点点,体验不同的生活。咱们下期见!