获取最新完整地址库
背景
一些涉及到地址或者快递的系统,都会用到地址库,但是地址库每年都会更新,并且各个平台地址库都还不一样,比如菜鸟、淘宝、京东等,都有他们自己的地址库,如果没有一个标准的地址库的话,很难说明白这个地址到底是哪里。针对这个痛点,我们完全可以使用国家统计局的地址库,以国家的地址库为标准。
抓取地址库
将下面这段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版直接上传在这里了,如果要直接下载的小伙伴可以到我的主页找到资源下载。