获取最新完整地址库

获取最新完整地址库

背景

一些涉及到地址或者快递的系统,都会用到地址库,但是地址库每年都会更新,并且各个平台地址库都还不一样,比如菜鸟、淘宝、京东等,都有他们自己的地址库,如果没有一个标准的地址库的话,很难说明白这个地址到底是哪里。针对这个痛点,我们完全可以使用国家统计局的地址库,以国家的地址库为标准。

抓取地址库

将下面这段js代码复制到下面这个页面,通过控制台执行
链接:https://www.stats.gov.cn/sj/tjbz/qhdm/

Js代码:

/*
获取统计局所有城市名称原始数据
将此js代码复制到下面地址中F12控制台执行
https://www.stats.gov.cn/sj/tjbz/qhdm/
可能需要低版本chrome,不然他们网页gbk格式的请求会乱码,chrome 41没有乱码,Chrome 46这版本win10能用。或者篡改Content-Type响应头为Content-Type: text/html; charset=gb2312也可解决新版Chrome乱码问题,比如:FildderScript OnBeforeResponse中添加:
if (oSession.HostnameIs("www.stats.gov.cn")){
	if(/tjyqhdmhcxhfdm\/\d+/.test(oSession.fullUrl)){
		oSession.oResponse.headers["Content-Type"]="text/html; charset=gb2312";
	}
}
*/
(function(){
	var Year=2023;
	var LoadMaxLevel=4;//采集几层
	var SaveName="国家统计局地址库";
	var Level={
			1:{n:"省",k:"shen"},
			2:{n:"市",k:"si"},
			3:{n:"区",k:"qu"},
			4:{n:"镇",k:"zhen"}
	};
	window.StopLoad=false;//true手动停止运行,"End"假装采集完成
	var DATA=window.DATA||[];
	var LogAll=true;
	var Load_Thread_Count=4;//模拟线程数
	var Load_Max_Try=10;//错误重试次数
	var Load_Wait_Child=91;//此城市下级列表已抓取完毕,等待子级完成抓取
	var Load_Full_End=92;//此城市包括下级全部抓取完毕
	if(!window.URL){
		throw new Error("浏览器版本太低");
	};
	var loadReqCount=0;
	function ajax(url,True,False){
		loadReqCount++;
		var ajax=new XMLHttpRequest();
		ajax.timeout=20000;
		ajax.open("GET",url);
		ajax.onreadystatechange=function(){
			if(ajax.readyState==4){
				if(ajax.status==200){
					True(ajax.responseText);
				}else{
					False();
				}
			}
		}
		ajax.send();
	}
	function cityClass(name,url,code){
		this.name=name;
		this.url=url;
		this.code=code;
		this.child=[];
		this.load=0;
	}
	cityClass.prototype={
			getValue:function(){
				var obj={
						name:this.name
						,code:(this.code+"000000000000").substr(0,12)
						,child:[]
				};
				for(var i=0;i<this.child.length;i++){
					obj.child.push(this.child[i].getValue());
				}
				obj.child.sort(function(a,b){
					return a.code.localeCompare(b.code);
				});
				return obj;
			}
	}
	function load_shen_all(True){
		DATA=[];
		var path="https://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/"+Year;
		ajax(path+"/index.html",function(text){
			const reg = /href="([^"]+)"[^>]*>([^<]+)/g;
			var idx;
			var match;
			if((idx=text.indexOf('<tr class="provincetr">'))+1){
				//reg.lastIndex=idx;
				while(match=reg.exec(text)){
					var url=match[1];
					console.log("url="+url);
					if(url.indexOf("//")==-1 && url.indexOf("/")!=0){
						url=path+"/"+url;
						var name=match[2];
						console.log("name="+name);
						var ct=new cityClass(name,url,/(\d+).html/.exec(url)[1]);
						DATA.push(ct);
						console.log("code="+(/(\d+).html/.exec(url)[1]));
					}
				}
				True();
			}else{
				console.error("未发现省份数据");
			}
		},function(){
			console.error("读取省份列表出错","程序终止");
		});
	}
	var logX=$('<div class="LogX" style="position: fixed;bottom: 80px;right: 100px;padding: 50px;background: #0ca;color: #fff;font-size: 16px;width: 600px;z-index:9999999"></div>');
	$("body").append(logX);
	var logXn=0;
	function LogX(txt){
		logXn++;
		if(LogAll || LoadMaxLevel<4 || logXn%100==0){
			logX.text(txt);
		}
	};
	function load_x_childs(itm, next){
		var city=itm.obj,levelObj=Level[itm.level],levelNextObj=Level[itm.level+1];
		city.load++;
		if(city.load>Load_Max_Try){
			console.error("读取"+levelObj.n+"["+city.name+"]超过"+Load_Max_Try+"次");
			next();
			return;
		};
		LogX("读取"+loadReqCount+"次"+getJD()+" ["+city.name+"]"+levelObj.n);
		if(!city.url){
			console.warn("无url",city);
			next();
			return;
		};
		ajax(city.url,function(text){
			/*if(!/统计用区划代码<\/td>/.test(text)){//保证中文和没有要输入验证码
				console.error("内容未匹配",city.url);
				city.load=Load_Max_Try;
				next();
				return;
			};*/
			var reg=/class="(citytr|countytr|towntr|villagetr)".+?<\/tr>/ig;
			//const reg = /href="([^"]+)"[^>]*>([^<]+)/g;
			var match;
			var mode="";
			var swapItem=null;
			while(match=reg.exec(text)){
				var err=function(msg){
					console.error(msg,city,match[0]);
					city.load=Load_Max_Try;
					next();
				};
				!mode&&(mode=match[1]);
				if(mode!=match[1]){
					err("前后类型不匹配");
					return;
				};
				//villagetr直接非法
				var reg2=/class="(citytr|countytr|towntr)".+?<td>(?:<a href='(.+?)'>)?(.+?)<.+?>([^<>]+)(?:<\/a>)?<\/td><\/tr>/ig;
				//class="citytr"><td><a href="12/1201.html">120100000000</a></td><td><a href="12/1201.html">市辖区</a></td></tr>
				const reg3 = /<a href="([^"]+)">([^<]+)<\/a>/g;
				var match2,match3;
				if(match2=reg2.exec(match[0])){
					var i=1;
					var url,code,name;
					while ((match3 = reg3.exec(match[0]))) {
						if(i==1){
							url=match3[1];
							code=match3[2];
						}else{
							name=match3[2];
						}
						i++;
					}
					if(url && url.indexOf("//")==-1 && url.indexOf("/")!=0){
						url=city.url.substring(0,city.url.lastIndexOf("/"))+"/"+url;
						console.log("url====",url);
						console.log("code====",code);
						console.log("name====",name);
						//如果是镇,上级为市,越过了区,追加一个区,code为上级code+00,保持兼容,如:东莞
						/*if(itm.level==2 && match2[1]=="towntr"){
							if(!swapItem){
								console.log("没有区级,追加一个同名的:"+city.name,city);
								var o=new cityClass(city.name,city.url,city.code);
								o.load=Load_Wait_Child;
								city.child.push(o);
								swapItem=o;
							};
						};*/
						if(!url&&name=="市辖区"){
							//NOOP 没有链接的市辖区直接去除
						}else{
							(swapItem||city).child.push(new cityClass(name,url,code));
						};
					}
				}else{
					console.log("未知模式");
				};
			};
			delete city.url;
			city.load=Load_Wait_Child;
			JD[levelNextObj.k+"_count"]+=city.child.length;
			next();
		},function(){
			setTimeout(function(){
				load_x_childs(itm, next);
			},1000);
		});
	};

	var load_end=function(isErr){
		StopLoad="End";
		if(isErr){
			console.error("出错终止", getJD());
			return;
		}
		var logTxt="完成:"+(Date.now()-RunLoad.T1)/1000+"秒"+getJD();
		console.log(logTxt);
		LogX(logTxt);
		var data=[];
		for(var i=0;i<DATA.length;i++){
			data.push(DATA[i].getValue());
		}
		var saveData={};
		window[SaveName]=saveData;
		saveData.year=Year;
		saveData.cityList=data;
		var url=URL.createObjectURL(
				new Blob([
					new Uint8Array([0xEF,0xBB,0xBF])
					,"var "+SaveName+"="
					,JSON.stringify(saveData,null,"\t")
					]
				,{"type":"text/plain"})
		);
		var downA=document.createElement("A");
		downA.innerHTML="下载查询好城市的文件";
		downA.href=url;
		downA.download=SaveName+".txt";
		logX.append(downA);
		downA.click();
		console.log("--完成--");
	};
	var threadCount=0;
	function thread(){
		threadCount++;
		var itm=findNext(DATA,1);
		if(!itm||!itm.obj){
			//最后循环full计数
			findNext(DATA,1);
			findNext(DATA,1);
			findNext(DATA,1);
			findNext(DATA,1);
			threadCount--;
			if(threadCount==0){
				load_end(!!itm);
			};
			return;
		};
		var next=function(){
			threadCount--;
			thread();
		};
		load_x_childs(itm, next);
	};
	function findNext(childs,level,parent){
		if(level>=LoadMaxLevel){//超过了需要加载的层次
			setFullLoad(parent,level-1);
			return;
		};
		if(StopLoad){
			//已停止
			if(StopLoad=="End"){
				return;
			};
			//手动中断运行
			return {};
		};
		var isFull=true;
		for(var i=0;i<childs.length;i++){
			var itm=childs[i];
			//处理完成了的
			if(itm.load==Load_Full_End){
				continue;
			};
			isFull=false;
			if(itm.load==Load_Wait_Child){
				//看看下级有没有没处理的
				var rtv=findNext(itm.child,level+1,itm);
				if(rtv){
					return rtv;
				};
			}else if(itm.load>Load_Max_Try){
				//存在加载失败的,中断运行
				return {};
			};
			//加载这个
			if(!itm.load){
				return {obj:itm,level:level};
			};
		};
		if(isFull&&parent){
			setFullLoad(parent,level-1);
		};
	};
	function setFullLoad(itm,level){
		if(itm.load==Load_Wait_Child){
			JD[Level[level].k+"_ok"]++;
		};
		itm.load=Load_Full_End;
	};
	function clearLoadErr(childs){
		for(var i=0;i<childs.length;i++){
			var itm=childs[i];
			itm.load=itm.load>50?itm.load:!itm.url?1:0;
			clearLoadErr(itm.child);
		};
	};
	function getJD(){
		var str="省:"+JD.shen_ok+"/"+JD.shen_count;
		str+=" 市:"+JD.si_ok+"/"+JD.si_count;
		str+=" 区:"+JD.qu_ok+"/"+JD.qu_count;
		str+=" 镇:"+JD.zhen_count;
		return " >>进度:"+str;
	};
	var JD={
			shen_ok:0
			,shen_count:0
			,si_ok:0
			,si_count:0
			,qu_ok:0
			,qu_count:0
			,zhen_count:0
	};
	window.RunLoad=function(){
		RunLoad.T1=Date.now();
		function start(){
			JD.shen_count=DATA.length;
			for(var i=0;i<Load_Thread_Count;i++){
				thread();
			};
		};
		if(DATA.length){
			console.log("恢复采集...");
			clearLoadErr(DATA);
			start();
		}else{
			load_shen_all(start);
		}
		window.DATA=DATA;
	}
})();//@ sourceURL=console.js

//立即执行代码
RunLoad()

注意

JS中的年份和等级都是可以修改的,还有就是国家统计局的链接有时会有变化,可以先提前在国家统计局查询最新的链接,目前2024年查询到的链接是:https://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm
查询方式:
在这里插入图片描述
在这里插入图片描述
注意链接只要前面一部分。

PS:

这js里面有一部分是正则表达式,小弟对正则表达式是一知半解,希望有大佬可以优化一下,优化之后请通知小弟改正

执行完成

在这里插入图片描述
执行完成之后得到的是一个js的json格式文本。
在这里插入图片描述

JAVA将JSON文本转换成excel

如果有java的小伙伴,可以使用下面的方法将这个json格式转换成excel,然后导入数据库直接使用。

package com.qk.common.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

/**
 * @时间:2024年8月28日 上午10:06:47
 * @作者:秦二少
 * @描述:从国家统计局获取地址库
 */
public class getAddress {
	/**
	 * @时间:2024年8月28日 上午10:20:17
	 * @作者:秦二少
	 * @描述:地址库代码
	 */
	public static class Address{
		public String code;
		public String name;
		public Integer level;
		public String fCode;
		public String getCode() {
			return code;
		}
		public void setCode(String code) {
			this.code = code;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public Integer getLevel() {
			return level;
		}
		public void setLevel(Integer level) {
			this.level = level;
		}
		public String getfCode() {
			return fCode;
		}
		public void setfCode(String fCode) {
			this.fCode = fCode;
		}
		@Override
		public String toString() {
			return "Address [code=" + code + ", name=" + name + ", level=" + level + ", fCode=" + fCode + "]";
		}
	}
	public static void main(String[] args) {
		byte[] dat = readFile("src/test/java/com/test/address.txt");//测试推单
		String res = new String(dat);
		JSONObject json=JSON.parseObject(res);
		//System.out.println("--获取到的地址库JSON="+json);
		JSONArray shens=json.getJSONArray("cityList");
		//解析地址库
		List<Address> list=new ArrayList<Address>();
		for(int i=0;i<shens.size();i++){
			JSONObject shen=shens.getJSONObject(i);
			System.out.println("=开始解析省:"+shen.getString("name"));
			Address ad=new Address();
			ad.setCode(shen.getString("code"));
			ad.setfCode("0");
			ad.setLevel(1);
			ad.setName(shen.getString("name"));
			list.add(ad);
			//解析此省级下的市级
			JSONArray citys=shen.getJSONArray("child");
			for(int j=0;j<citys.size();j++){
				JSONObject city=citys.getJSONObject(j);
				System.out.println("=开始解析省:"+shen.getString("name")+"--市:"+city.getString("name"));
				ad=new Address();
				ad.setCode(city.getString("code"));
				ad.setfCode(shen.getString("code"));
				ad.setLevel(2);
				ad.setName(city.getString("name"));
				list.add(ad);
				//解析此市级下面的区县级
				JSONArray diss=city.getJSONArray("child");
				for(int d=0;d<diss.size();d++){
					JSONObject dis=diss.getJSONObject(d);
					System.out.println("=开始解析省:"+shen.getString("name")+"--市:"+city.getString("name")+"--区:"+dis.getString("name"));
					ad=new Address();
					ad.setCode(dis.getString("code"));
					ad.setfCode(city.getString("code"));
					ad.setLevel(3);
					ad.setName(dis.getString("name"));
					list.add(ad);
					//解析此区县级下面的镇级
					JSONArray xzs=dis.getJSONArray("child");
					for(int k=0;k<xzs.size();k++){
						JSONObject xz=xzs.getJSONObject(k);
						System.out.println("=开始解析省:"+shen.getString("name")+"--市:"+city.getString("name")+"--区:"+dis.getString("name")+"--镇:"+xz.getString("name"));
						ad=new Address();
						ad.setCode(xz.getString("code"));
						ad.setfCode(dis.getString("code"));
						ad.setLevel(4);
						ad.setName(xz.getString("name"));
						list.add(ad);
					}
				}
			}
		}
		System.out.println("---数据解析完成,开始生成EXCEL....");
		//生成EXCEL
		//设置excel相关内容
		String title = "地址库"+BaseResultUtils.getDataStringByFormat("yyyyMMdd") + ".xlsx";
		String[] headers = new String[] {"序号","地区代码","地区名称","等级","父级代码"};
		List<Object[]> dataList = new ArrayList<Object[]>();
        Object[] objs = null;
		for (Address mc : list) {
			objs = new Object[headers.length];//每行加序号
			objs[1] = mc.getCode();
			objs[2] = mc.getName();
			objs[3] = mc.getLevel();
			objs[4] = mc.getfCode();
			dataList.add(objs);
		}
        try {
            FileOutputStream out = new FileOutputStream("E:/"+title);
            ExcelOutUtil ex = new ExcelOutUtil(title, headers, dataList);//没有标题
            ex.export(out);
            System.out.println("---生成EXCEL成功....");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("---生成EXCEL失败:"+e.getMessage());
        }
	}
	/**
	 * @类名: getAddress.java 
	 * @时间: 2024年8月28日 上午11:58:26 
	 * @作者: 秦二少
	 * @param filename
	 * @return
	 * @描述:  读取文件
	 */
	private static byte[] readFile(String filename) {
		  File file = new File(filename);
		  if(!file.exists()) {
			  return null;
		  }
	      InputStream in = null;
	      try {
	          in = new FileInputStream(file);
	          int length = (int) file.length();
	          byte[] buf = new byte[length];
	          int byteRead = in.read(buf);
	          if(byteRead==-1)
	        	  return null;
	          return buf;
	      } catch (IOException e1) {
	          e1.printStackTrace();
	      } finally {
	          if (in != null) {
	              try {
	                  in.close();
	              } catch (IOException e1) {
	            	  e1.printStackTrace();
	              }
	          }
	      }
		  return null;
	}
}

最后说明

这里不能直接上传文件,不然我就把我搞好的json版和excel版直接上传在这里了,如果要直接下载的小伙伴可以到我的主页找到资源下载。

完毕!

北京市---市辖区---东城区---东华门街道办事处 北京市---市辖区---东城区---景山街道办事处 北京市---市辖区---东城区---交道口街道办事处 北京市---市辖区---东城区---安定门街道办事处 北京市---市辖区---东城区---北新桥街道办事处 北京市---市辖区---东城区---东四街道办事处 北京市---市辖区---东城区---朝阳门街道办事处 北京市---市辖区---东城区---建国门街道办事处 北京市---市辖区---东城区---东直门街道办事处 北京市---市辖区---东城区---和平里街道办事处 北京市---市辖区---东城区---前门街道办事处 北京市---市辖区---东城区---崇文门外街道办事处 北京市---市辖区---东城区---东花市街道办事处 北京市---市辖区---东城区---龙潭街道办事处 北京市---市辖区---东城区---体育馆路街道办事处 北京市---市辖区---东城区---天坛街道办事处 北京市---市辖区---东城区---永定门外街道办事处 北京市---市辖区---西城区---西长安街街道办事处 北京市---市辖区---西城区---新街口街道办事处 北京市---市辖区---西城区---月坛街道办事处 北京市---市辖区---西城区---展览路街道办事处 北京市---市辖区---西城区---德胜街道办事处 北京市---市辖区---西城区---金融街街道办事处 北京市---市辖区---西城区---什刹海街道办事处 北京市---市辖区---西城区---大栅栏街道办事处 北京市---市辖区---西城区---天桥街道办事处 北京市---市辖区---西城区---椿树街道办事处 北京市---市辖区---西城区---陶然亭街道办事处 北京市---市辖区---西城区---广安门内街道办事处 北京市---市辖区---西城区---牛街街道办事处 北京市---市辖区---西城区---白纸坊街道办事处 北京市---市辖区---西城区---广安门外街道办事处 北京市---市辖区---朝阳区---建外街道办事处 北京市---市辖区---朝阳区---朝外街道办事处 北京市---市辖区---朝阳区---呼家楼街道办事处 北京市---市辖区---朝阳区---三里屯街道办事处 北京市---市辖区---朝阳区---左家庄街道办事处 北京市---市辖区---朝阳区---香河园街道办事处 北京市---市辖区---朝阳区---和平街街道办事处 北京市---市辖区---朝阳区---安贞街道办事处 北京市---市辖区---朝阳区---亚运村街道办事处 北京市---市辖区---朝阳区---小关街道办事处 北京市---市辖区---朝阳区---酒仙桥街道办事处 北京市---市辖区---朝阳区---麦子店街道办事处 北京市---市辖区---朝阳区---团结湖街道办事处 北京市---市辖区---朝阳区---六里屯街道办事处 北京市---市辖区---朝阳区---八里庄街道办事处 北京市---市辖区---朝阳区---双井街道办事处 北京市---市辖区---朝阳区---劲松街道办事处 北京市---市辖区---朝阳区---潘家园街道办事处 北京市---市辖区---朝阳区---垡头街道办事处 北京市---市辖区---朝阳区---南磨房地区办事处 北京市---市辖区---朝阳区---高碑店地区办事处 北京市---市辖区---朝阳区---将台地区办事处
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值