整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

实战|省市区三级联动数据爬取

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

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

  对比的过程中,石锤网商数据不准。值得的是表扬淘宝京东已经同步了最新的数据了。但是呢,我并没有找到它们的数据接口。为了修正系统的数据,只能自己爬取了。

锁定爬取目标

爬取地址如下:

https://preview.www.mca.gov.cn/article/sj/xzqh/2020/2020/202101041104.html

  爬取原理很简单,就是解析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县以上行政区划代码

对于直辖市也可以做两级的,这个主要看产品的需求吧

总结

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

结尾

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

  如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。

  我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!

用PHP+MySql+Ajax+jQuery实现省市区三级联动功能

要求:写一个省市区(或者年月日)的三级联动,实现地区或时间的下拉选择。

实现技术:php ajax

实现:省级下拉变化时市下拉区下拉跟着变化,市级下拉变化时区下拉跟着变化。

使用chinastates表查询

Ajax加载数据

1.这是chinastates表

2.做一个简单php:Ajax_eg.php

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title></title>

<script src="bootstrap/js/jquery-1.11.2.min.js"></script>

</head>

<style>

.sanji{

margin-left: 550px;

margin-top: 150px;

}

</style>

<body>

<div class="sanji"> </div>

</body>

</html>

3.根据前一个页面做jquery:Ajax_ssq.js

// JavaScript Document

//当页面内容都加载完才执行

$(document).ready(function(e) {

//加载三个下拉列表

$("#sanji").html("<select id='sheng'></select><select id='shi'></select><select id='qu'></select>");

//加载显示数据

//1.加载省份

LoadSheng();

//2.加载市

LoadShi();

//3.加载区

LoadQu();

//当省份选中变化,重新加载市和区

$("#sheng").change(function(){ //当元素的值发生改变时,会发生 change 事件,该事件仅适用于文本域(text field),以及 textarea 和 select 元素。

//加载市

LoadShi();

//加载区

LoadQu();

})

//当市选中变化,重新加载区

$("#shi").change(function(){

//加载区

LoadQu();

})

});

//加载省份信息

function LoadSheng()

{

//取父级代号

var pcode ="0001";

//根据父级代号查数据

$.ajax({

//取消异步,也就是必须完成上面才能走下面

async:false,

url:"load.php",

data:{pcode:pcode},

type:"POST",

dataType:"JSON",

success: function(data){

var str="";

//遍历数组,把它放入sj

for(var k in data){

str=str+"<option value='"+data[k].[0]+"'>"+data[k].[1]+"</option>";

}

$("#sheng").html(str);

}

});

}

//加载市信息

function LoadShi()

{

//取父级代号

var pcode =$("#sheng").val();

//根据父级代号查数据

$.ajax({

//取消异步,也就是必须完成上面才能走下面

async:false,

url:"load.php",

data:{pcode:pcode},

type:"POST",

dataType:"JSON",

success: function(data){

var str="";

//遍历数组,把它放入sj

for(var k in data){

str=str+"<option value='"+data[k].[0]+"'>"+data[k].[0]+"</option>";

}

$("#shi").html(str);

}

});

}

//加载区信息

function LoadQu()

{

//取父级代号

var pcode =$("#shi").val();

//根据父级代号查数据

$.ajax({

//不需要取消异步

url:"load.php",

data:{pcode:pcode},

type:"POST",

dataType:"JSON",

success: function(data){

var str="";

//遍历数组,把它放入sj

for(var k in data){

str=str+"<option value='"+data[k].[0]+"'>"+data[k].[1]+"</option>";

}

$("#qu").html(str);

}

});

}

4.再把数据库连接起来 :load.php,把DBDA重新加载一个方法:JsonQuery

<?php

$pcode = $_POST["pcode"];

require_once "./DBDA.class.php";

$db = new DBDA();

$sql = "select * from chinastates where parentareacode='{$pcode}'";

echo $db->JsonQuery($sql,0);


封装类

<?php

class DBDA{

public $host="localhost";

public $uid="root";

public $pwd="";

public $dbname="0710_info";

/*

query方法:执行用户给的sql语句,并返回相应的结果

$sql:用户需要执行的sql语句

$type:用户需要执行的sql语句的类型

return:如果是增删语句改返回true或false,如果是查询语句返回二维数组

*/

public function query($sql,$type=1){//默认true为增删改

$db = new MySQLi($this->host,$this->uid,$this->pwd,$this->dbname);

if(mysqli_connect_error()){

return "连接失败!";

}

$result = $db->query($sql);

if($type==1){

return $result;//增删改语句返回true或false

}else{

return $result->fetch_all();//查询语句返回二维数组

}

}

//此方法用于ajax中用于对取出的数据(二维数组)进行拼接字符串处理

public function StrQuery($sql,$type=1){

$db = new MySQLi($this->host,$this->uid,$this->pwd,$this->dbname);

if(mysqli_connect_error()){

return "连接失败!";

}

$result = $db->query($sql);

if($type==1){

return $result;//增删改语句返回true或false

}else{

$arr = $result->fetch_all();//查询语句返回二维数组

$str = "";

foreach($arr as $v){

$str = $str.implode("^", $v)."|";

}

$str = substr($str, 0,strlen($str)-1);

return $str;

}

}

//此方法用于ajax中用于返回为json数据类型时使用

public function JsonQuery($sql,$type=1){

$db = new MySQLi($this->host,$this->uid,$this->pwd,$this->dbname);

if(mysqli_connect_error()){

return "连接失败!";

}

$result = $db->query($sql);

if($type==1){

return $result;//增删改语句返回true或false

}else{

$arr = $result->fetch_all();//查询语句返回二维(关联)数组

return json_encode($arr);//将数组转换成json

}

}

}

实现效果:

ue实现城市三级联动,可以通过使用Vue的v-model指令和computed属性来实现。首先,需要准备一个包含省市区数据的JSON文件,例如:

```json

{

"provinces": [

{

"name": "北京市",

"cities": [

{

"name": "北京市",

"areas": [

"东城区",

"西城区",

"朝阳区",

"丰台区",

"石景山区",

"海淀区",

"门头沟区",

"房山区",

"通州区",

"顺义区",

"昌平区",

"大兴区",

"怀柔区",

"平谷区",

"密云区",

"延庆区"

]

}

]

},

{

"name": "上海市",

"cities": [

{

"name": "上海市",

"areas": [

"黄浦区",

"徐汇区",

"长宁区",

"静安区",

"普陀区",

"虹口区",

"杨浦区",

"闵行区",

//省市区数据

const data = {

provinces: [

{

name: "北京市",

cities: [

{

name: "北京市",

areas: [

"东城区",

"西城区",

"朝阳区",

"丰台区",

"石景山区",

"海淀区",

"门头沟区",

"房山区",

"通州区",

"顺义区",

"昌平区",

"大兴区",

"怀柔区",

"平谷区",

"密云区",

"延庆区"

]

}

]

},

{

name: "上海市",

cities: [

{

name: "上海市",

areas: [

"黄浦区",

"徐汇区",

"长宁区",

"静安区",

"普陀区",

"虹口区",

"杨浦区",

"闵行区"

]

}

]

},

{

name: "广东省",

cities: [

{

name: "广州市",

areas: [

"荔湾区",

"越秀区这是一个JSON文件,包含了省市区的数据。接下来,在Vue组件中使用这个数据,实现城市三级联动。

首先,需要在Vue组件中引入这个数据文件,并将其赋值给一个变量:

```javascript

import data from './data.json';

export default {

data() {

return {

provinces: data.provinces,

selectedProvince: '',

selectedCity: '',

selectedArea: ''

}

}

}

```

然后,在模板中使用v-model指令将选择的省市区绑定到对应的变量上:

```html

<template>

<div>

<select v-model="selectedProvince">

<option value="">请选择省份</option>

<option v-for="province in provinces" :value="province.name">{{ province.name }}</option>

</select>

<select v-model="selectedCity">

<option value="">请选择城市</option>

<option v-for="city in selectedProvince.cities" :value="city.name">{{ city.name }}</option>

</select>

<select v-model="selectedArea">

<option value="">请选择区域</option>

<option v-for="area in selectedCity.areas" :value="area">{{ area }}</option>

</select>

</div>

</template>

```

最后,使用computed属性来动态获取选择的省市区的数据:

```javascript

computed: {

selectedProvince() {

return this.provinces.find(province => province.name === this.selectedProvince);

},

selectedCity() {

if (this.selectedProvince) {

return this.selectedProvince.cities.find(city => city.name === this.selectedCity);

}

return null;

}

}

```

这样,当选择省份的时候,城市和区域的下拉框会根据选择的省份动态更新。当选择城市的时候,区域的下拉框会根据选择的城市动态更新。最后,可以通过访问`selectedProvince`、`selectedCity`和`selectedArea`来获取选择的省市区的数据。