最近收到客服反应,系统的省市区数据好像不准,并且缺了一些地区。经过询问同事得知,数据库内的数据是从老项目拷贝过来的,有些年头了。难怪会缺一些数据。正好最近在对接网商银行,发现网商提供了省市区的数据的接口。这就很舒服了哇,抄起键盘就是干,很快的就把同步程序写好了。
然后在同步的过程中,发现网商提供的数据和数据库有些对不上。于是默默的打开淘宝和京东添加收货地址,看看到底是谁错了。对比到后面发现都有些差异。这就很蛋疼了。看来这个时候谁都不能相信了,只能信国家了。于是我打开了中华人民共和国民政部网站来比对异常的数据。
对比的过程中,石锤网商数据不准。值得的是表扬淘宝和京东已经同步了最新的数据了。但是呢,我并没有找到它们的数据接口。为了修正系统的数据,只能自己爬取了。
爬取地址如下:
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);
}
}
}
由于我们需要的是省市区三级数据联动,但是了直辖市只有两级,所以我们人工的给它加上一级。以北京市为例:变成了 北京 -> 北京市- >东城区,对于其他的直辖市也是同样的处理逻辑。
修正好的数据奉上,有需要的小伙伴可以自取哦。
对于直辖市也可以做两级的,这个主要看产品的需求吧
总体来讲,这个爬虫比较简单,只有简单的几行代码。毕竟网站也没啥反扒的机制,所以很轻松的就拿到了数据。
嘿嘿话说,你都爬过哪些网站呢?
如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。
我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!
要求:写一个省市区(或者年月日)的三级联动,实现地区或时间的下拉选择。
实现技术: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`来获取选择的省市区的数据。
*请认真填写需求信息,我们会在24小时内与您取得联系。