本文更新(移步查阅):

19-04-15 新采集了2018的省市区三级坐标和行政区域边界

19-03-22 采集了2018的城市数据

18-11-28 采集了2017的城市数据

数据下载 GitHub:https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov/releases

相关更新情况,请查阅我发布的其他文章,本文以下内容不再更新。

18-01-28早上6:30的火车,从三亚回老家,票难买啊。好激动~

声明:文中涉及到的数据和第三方接口、url仅供学习使用,请勿它用~

这几天都在磨着搭建本地测试环境,看到省市区数据表里面是空的,想着以前的老数据还是13年采集的,含省市区县4级数据共4.8万条,时间久了,使用过程中发现有些新的城市名称数据库中没有,县级数据从来就没有用到过,想着还是重新采集一份。

新采集的省市区数据有3589条,这次并没有把县级数据采过来,需要的时候再添加也挺好。

数据来源

国家统计局统计标准《2016年统计用区划代码和城乡划分代码(截止2016年07月31日)》,这个是2017-05-16发布的,当前是最新的。

数据采集

对于数据采集,根据工作需要,对于一些小的数据采集功能有些接触。因为对html和js熟些,很早以前就用IE浏览器对本地html文件支持任意跨域ajax请求数据、和支持读写Excel文件,就直接写一个html文件作为采集工具给别人使用,批量查询人员资料、考试结果什么的功能。所以采集省市区数据主要用的js。

1. 抓取原始数据

打开网页http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/index.html省份的数据就有了,进入市级页面,然后进入区级页面,还可以进入县级页面。整个流程地址结构非常简单,数据格式也很好提取。

进入网页后打开浏览器控制台,执行下面代码,这段代码仅仅包含采集省市区的,把县级的阉割掉了,13年的老代码有县级的。很早以前写的代码,风格有点丑,不过能能正常使用就是好的,这个采集是“单线程的”,因为这些数据少,速度并不慢:

/*
获取城市名称http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/index.html
*/
(function(){
if(!window.URL){
throw new Error("浏览器版本太低");
};
function ajax(url,True,False){
var ajax=new XMLHttpRequest();
ajax.timeout=1000;
ajax.open("GET",url);
ajax.onreadystatechange=function(){
if(ajax.readyState==4){
if(ajax.status==200){
True(ajax.responseText);
}else{
False();
}
}
}
ajax.send();
}
function msg(){
console.log.apply(console, arguments);
} function cityClass(name,url,code){
this.name=name;
this.url=url;
this.code=code;
this.child=[];
this.tryCount=0;
}
cityClass.prototype={
getValue:function(){
var obj={name:this.name,code:this.code,child:[]};
for(var i=0;i<this.child.length;i++){
obj.child.push(this.child[i].getValue());
}
return obj;
}
} function load_all(True){
var path="http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016";
ajax(path+"/index.html",function(text){
var reg=/href='(.+?)'>(.+?)<br/ig,match;
var idx;
if((idx=text.indexOf("<tr class='provincetr'>"))+1){
reg.lastIndex=idx;
while(match=reg.exec(text)){
var url=match[1];
if(url.indexOf("//")==-1 && url.indexOf("/")!=0){
url=path+"/"+url;
}
var name=match[2];
DATA.push(new cityClass(name,url,0));
}
True();
}else{
msg("未发现省份数据");
}
},function(){
msg("读取省份列表出错","程序终止");
});
}
function load_shen(True, False){
var city=DATA[JD.shen];
city.tryCount++;
if(city.tryCount>3){
msg("读取省份["+city.name+"]超过3次");
False();
return;
}; function get(){
msg("读取省份["+city.name+"]", getJD());
save(); city.child[JD.si].tryCount=0;
load_si(function(){
JD.shen++;
if(JD.shen>=DATA.length){
JD.shen=0;
True();
return;
};
DATA[JD.shen].tryCount=0; load_shen(True,False);
},function(){
False();
});
} if(city.child.length){
get();
}else{
ajax(city.url,function(text){
var reg=/<tr class='citytr'>.+?href='(.+?)'>(.+?)<.+?'>(.+?)</ig;
var match;
while(match=reg.exec(text)){
var url=match[1];
if(url.indexOf("//")==-1 && url.indexOf("/")!=0){
url=city.url.substring(0,city.url.lastIndexOf("/"))+"/"+url;
}
var code=match[2];
var name=match[3];
city.child.push(new cityClass(name,url,code));
} JD.si=0;
get();
},function(){
load_shen(True,False);
});
};
} function load_si(True,False){
var shen=DATA[JD.shen];
var city=shen.child[JD.si];
city.tryCount++;
if(city.tryCount>3){
msg("读取城市["+city.name+"]超过3次");
False();
return;
}; function get(){
msg("___读取城市["+city.name+"]", getJD()); city.child[JD.xian].tryCount=0;
JD.si++;
if(JD.si>=shen.child.length){
JD.si=0;
True();
return;
};
shen.child[JD.si].tryCount=0; load_si(True,False);
} if(city.child.length){
get();
}else{
ajax(city.url,function(text){
var reg=/class='(?:countytr|towntr)'.+?<\/tr>/ig;
var match;
while(match=reg.exec(text)){
var reg2=/class='(?:countytr|towntr)'.+?(?:<td><a href='(.+?)'>(.+?)<.+?'>(.+?)<|<td>(.+?)<.+?<td>(.+?)<)/ig;
var match2;
if(match2=reg2.exec(match[0])){
var url=match2[1]||"";
if(url.indexOf("//")==-1 && url.indexOf("/")!=0){
url=city.url.substring(0,city.url.lastIndexOf("/"))+"/"+url;
}
var code=match2[2]||match2[4];
var name=match2[3]||match2[5];
city.child.push(new cityClass(name,url,code));
}else{
msg("未知城市模式:");
msg(city.url);
msg(match[0]);
throw new Error("end");
}
} JD.xian=0;
get();
},function(){
load_si(True,False);
});
};
} function getJD(){
var str="省:"+(JD.shen+1)+"/"+DATA.length;
var shen=DATA[JD.shen];
if(shen){
str+=" 市:"+(JD.si+1)+"/"+shen.child.length;
var si=shen.child[JD.si];
if(si){
str+=" 县:"+(JD.xian+1)+"/"+si.child.length;
}else{
str+=" 县:"+JD.xian;
}
}else{
str+=" 市:"+JD.si+" 县:"+JD.xian;
}
return str;
}
function save(){ } var DATA=[];
var JD;
window.RunLoad=function(shen,si,xian){
RunLoad.T1=Date.now();
JD={
shen:shen||0
,si:si||0
,xian:xian||0
} function get(){
DATA[JD.shen].tryCount=0;
load_shen(function(){
console.log("完成:"+(Date.now()-RunLoad.T1)/1000+"秒");
save(); var data=[];
for(var i=0;i<DATA.length;i++){
data.push(DATA[i].getValue());
} var url=URL.createObjectURL(
new Blob([
new Uint8Array([0xEF,0xBB,0xBF])
,"var CITY_LIST="
,JSON.stringify(data,null,"\t")
]
,{"type":"text/plain"})
);
var downA=document.createElement("A");
downA.innerHTML="下载查询好城市的文件";
downA.href=url;
downA.download="data.txt";
document.body.appendChild(downA);
downA.click(); msg("--完成--");
},function(){
save();
msg("当前进度:", getJD());
});
} var data=localStorage["load_data"];
if(data){
DATA=JSON.parse(data);
get();
}else{
load_all(get);
}
}
})();//@ sourceURL=console.js //立即执行代码
RunLoad()

采集截图:

2. 处理数据和拼音标注

数据处理就简单些了,比如编号格式化、名称格式化等。

拼音标注:这个需要找一个接口对文字进行拼音翻译,只有一个要求:重庆能正常的翻译成chong qing即可,翻译成zhong qing的就low了。满足这个条件,百度上搜索到的翻译小网站80%就被干掉了。

浏览器中打开找到的翻译接口http://www.qqxiuzi.cn/zh/pinyin/,截止到目前是能正常调用的,因为要用ajax请求数据,在页面里面就没有跨域的问题,查看网页源码,把token值记录下来,这个网站翻译请求需要带这个token,注意~刷新页面要重新获取:

拼音这个因为数据量比较多,采用了“4个线程”采集,先把第一步采集到的文件打开,把数据复制到打开的翻译网站浏览器控制台里面执行(相当于把数据导入),然后执行下面代码:

/*
拼音翻译
http://www.qqxiuzi.cn/zh/pinyin/ http://www.qqxiuzi.cn/zh/pinyin/show.php
POST
t=汉字&d=1&s=null&k=1&b=null&h=null&u=null&v=1&y=null&z=null&token=页面token请求一次获取 先加载数据
控制台输入data.txt
*/
window.PageToken=window.PageToken||"";
var FixTrim=function(name){
return name.replace(/^\s+|\s+$/g,"");
};
var CITY_LIST2;
var QueryPinYin=function(end){
if(!window.PageToken){
console.error("Need PageToken");
return;
};
var ids=[]; var fixCode=function(o){
if(o.deep==0){
o.orgCode="0";
}else{
o.orgCode=o.code;
if(o.deep==1){
o.code=o.code.substr(o,4);
}else{
o.code=o.code.replace(/(000000|000)$/g,"");//有少部分区多3位
};
};
return o;
};
var fix=function(o,p){
var name=o.name;
if(o.deep==0){
name=name.replace(/(市|省|(维吾尔|壮族|回族)?自治区)$/ig,"");
}else if(o.deep==1){
if(name=="市辖区"){
name=p.o2.name;
}else if(/行政区划$/ig.test(name)){
name="直辖市";
}else if(name.length>2){
name=name.replace(/市$/ig,"");
};
}else{
if(name.length>2 && name!="市辖区"
&& !/(自治.|地区|矿区)$/.test(name)){//直接排除会有同名的
name=name.replace(/(市|区|县|镇|管委会|街道办事处)$/ig,"");
};
};
var o2={
name:name
,ext_name:o.name
,id:+o.code||0
,ext_id:+o.orgCode
,pid:p&&+p.code||0
,deep:o.deep
};
o.o2=o2;
return o2;
};
for(var i=0;i<CITY_LIST.length;i++){
var shen=CITY_LIST[i];
shen.deep=0;
for(var i2=0;i2<shen.child.length;i2++){
var si=shen.child[i2];
if(!shen.code){
shen.code=si.code.substr(0,2);
ids.push(fix(fixCode(shen)));
};
si.deep=1;
ids.push(fix(fixCode(si),shen)); for(var i3=0;i3<si.child.length;i3++){
var qu=si.child[i3];
qu.deep=2;
ids.push(fix(fixCode(qu),si));
};
};
};
CITY_LIST2=ids;
//console.log(JSON.stringify(ids,null,"\t"))
//return; var idx=-1;
var run=function(stack){
stack=+stack||0;
idx++;
if(idx>=ids.length){
thread--;
if(thread==0){
end();
};
return;
}; var idx_=idx;
var id=ids[idx];
if(id.P){
stack++;
if(stack%50==0){
setTimeout(function(){run()});
}else{
run(stack);
};
return;
}; var name=id.name;
var tryCount=0;
var tryLoad=function(){
$.ajax({
url:"/zh/pinyin/show.php"
,data:"t="+encodeURIComponent(name)+"&d=1&s=null&k=1&b=null&h=null&u=null&v=1&y=null&z=null&token="+PageToken
,type:"POST"
,dataType:"text"
,timeout:1000
,error:function(e){
if(tryCount>3){
console.error("--QueryPinYin error--"+e);
run();
return;
};
tryCount++;
tryLoad();
}
,success:function(txt){
txt=FixTrim(txt.replace(/<.+?>/g,"").replace(/\s+/g," "));
id.P=txt;
console.log("--"+idx_+"-QueryPinYin "+name+":"+txt+" --");
run();
}
});
};
tryLoad();
}; var thread=4;
run();
run();
run();
run();
}; var ViewDown=function(){
console.log("完成:"+(Date.now()-RunPinYin.T1)/1000+"秒");
window.CITY_LIST_PINYIN=CITY_LIST2;
var url=URL.createObjectURL(
new Blob([
new Uint8Array([0xEF,0xBB,0xBF])
,"var CITY_LIST_PINYIN="
,JSON.stringify(CITY_LIST2,null,"\t")
]
,{"type":"text/plain"})
);
var downA=document.createElement("A");
downA.innerHTML="下载查询好城市的文件";
downA.href=url;
downA.download="data-pinyin.txt";
document.body.appendChild(downA);
downA.click();
}; var RunPinYin=function(){
RunPinYin.T1=Date.now();
QueryPinYin(ViewDown);
}; //立即执行代码
if(window.CITY_LIST){
if(!PageToken){
PageToken=prompt("Token");
};
RunPinYin();
}else{
console.error("data.txt未输入");
};

这时候会提示输入token,把刚才找到的token粘贴进去,然后就开始工作了:

还挺快的,2分钟多点全部翻译完成。

3. 格式化成CSV

数据全部有了,导出成比较正常使用的格式,CSV最好了。这个导出比较简单,任意网页控制台把第二部保存的文件打开,复制数据到任意网页控制台,然后输入以下代码:

/*
格式并且输出为csv 先加载数据
控制台输入data-pinyin.txt 导入数据库:
文件格式Unicode,文字为字符流
检查id重复项,修正id
转入area_city
增加港澳台、海外两个省级
检查名称重复项,修正名称
select * from area_city where len(name)=1
select pid,name,count(*) from area_city group by pid,name having COUNT(*)>1
*/ var FixTrim=function(name){
return name.replace(/^\s+|\s+$/g,"");
};
function CSVName(name){
return '"'+FixTrim(name).replace(/"/g,'""')+'"';
}; var CITY_CSV=["id,pid,deep,name,pinyin_prefix,pinyin,ext_id,ext_name"];
for(var i=0;i<CITY_LIST_PINYIN.length;i++){
var o=CITY_LIST_PINYIN[i];
var pf="";
var pinyin=FixTrim(o.P).toLowerCase();
var ps=pinyin.split(" ");
for(var j=0;j<ps.length&&j<3;j++){
pf+=ps[j].substr(0,j==0?2:1);
}; CITY_CSV.push(o.id+","+o.pid+","+o.deep+","+CSVName(o.name)
+","+CSVName(pf)+","+CSVName(o.P)
+","+CSVName(o.ext_id+"")+","+CSVName(o.ext_name));
}; var url=URL.createObjectURL(
new Blob([
new Uint8Array([0xEF,0xBB,0xBF])
,CITY_CSV.join("\n")
]
,{"type":"text/plain"})
);
var downA=document.createElement("A");
downA.innerHTML="下载查询好城市的文件";
downA.href=url;
downA.download="ok_data.csv";
document.body.appendChild(downA);
downA.click();

OK,数据全部搞完:

数据问题

  1. id编号和国家统计局的编号基本一致,方便以后更新。

  2. id重复项目前是没有(已优化过了),不过以前采集后直接对统计局的编号进行简单缩短后会有重复现象(算是精度丢失)。

  3. 拼音前缀取的是第一个字前两个字母和后两个字首字母,意图是让第一个字相同名称的尽量能排序在一起。排序1:黑龙江helj、湖北hub、湖南hun;排序2:湖北hb、黑龙江hlj、湖南hn,排序一胜出。

  4. 因为区名字是直接去掉市、区后缀,存在那么几对名字变得完全一样的,需要手动吧市区后缀加上,不然会产生小问题。

  5. 最终数据已上传了一份到CSDN,含所有代码和本文档:http://download.csdn.net/download/xiangyuecn/10226964GitHub下载最新数据

从统计局采集最新的省市区县数据,纯js的更多相关文章

  1. 从统计局采集最新的省市区镇数据,用js在浏览器中运行 V2

    本文描述的是对国家统计局于2019-01-31发布的<2018年统计用区划代码和城乡划分代码(截止2018年10月31日)>的采集. 相对于用于和采集2016版.2017版的js代码做了比 ...

  2. Pyqt QComboBox 省市区县联动效果

    在Qt中, QComboBox方法窗口组件允许用户从列表清单中选择,在web中就是select标签,下拉选项. 省市区县的联动就是currentIndexChanged 获取当前的Index,通过这个 ...

  3. echarts地图边界数据的实时获取与应用,省市区县多级联动【附最新geoJson文件下载】

    首先,来看下效果图 在线体验地址:https://hxkj.vip/demo/echartsMap/,并提供实时geoJson数据文件下载 echarts官方社区链接地址(可在线编辑):https:/ ...

  4. 从高德采集最新的省市区三级坐标和行政区域边界,用js在浏览器中运行

    本文描述的是对国家统计局于2019-01-31发布的<2018年统计用区划代码和城乡划分代码(截止2018年10月31日)>中省市区三级的坐标和行政区域边界的采集. 本文更新(移步查阅): ...

  5. 根据经纬度坐标获得省市区县行政区划城市名称,自建数据库 java python php c# .net 均适用

    目录 步骤一.下载省市区边界数据 步骤二.解析CSV文件导入数据库 步骤三.在程序中根据坐标解析获得城市 在LBS应用中,根据坐标来解析获得对应是哪个城市是一个很常见的功能,比如App里面通过手机定位 ...

  6. 循序渐进VUE+Element 前端应用开发(21)--- 省市区县联动处理的组件使用

    在很多应用中,往往都涉及到记录用户所在省份.城市.区县或者街道等信息,一般我们可以通过联动的Select或者类似的界面组件进行展示,或者使用Element中的el-cascader界面组件进行展示,而 ...

  7. 全国省市级联数据sql语句 mysql版

    全国省市级联数据sql语句 mysql版 --省级 provincial create table provincial ( provincialID int, provincialName ), p ...

  8. Mysql 保留最新的10条数据

    Mysql每天执行计划,保留最新的10条数据,其余的删除 1.Mysql 保留最新的10条数据 sql语句: DELETE tb FROM tbname AS tb,( SELECT id FROM ...

  9. sql 查询某个条件多条数据中最新的一条数据或最老的一条数据

    sql 查询某个条件下多条数据中最新的一条数据或最老的一条数据 test_user表结构如下: 需求:查询李四.王五.李二创建的最初时间或者最新时间 1:查询最初的创建时间: SELECT * FRO ...

随机推荐

  1. 13、多进程multiprocessing、进程池

    内容相关: multiprocessing: 进程的创建与运行 进程常用相关函数 进程池: 为什么要有进程池 进程池的创建与运行:串行.并行 回调函数 多进程multiprocessing: pyth ...

  2. VirtualBox下安装CentOS7系统

    本文假定你已经知道如何安装VirtualBox虚拟机软件,并且已经安装好了. 首先我们需要准备好centos的iso镜像文件,可以从centos的官网下载. 以下操作使用的VirtualBox版本号是 ...

  3. LeetCode题解之Binary Tree Level Order Traversal II

    1.题目描述 2.题目分析 先遍历,再反转. 3.代码 vector<vector<int>> levelOrderBottom(TreeNode* root) { vecto ...

  4. MSSQL一种取代游标的方案

    今天看到一篇文章写的自己整理记录下,据说比用游标快. DECLARE @字段1 数据类型; DECLARE @字段2 数据类型; DECLARE @TMP_WHILE_ID INT; ,),TMP_W ...

  5. MySQL 复制夯住一例排查以及原理探讨

    目录 目录 一 引子 二 故障分析 三 故障解决 四 原理探讨 五 小结 文/温国兵 一 引子 研发反应,有台从库和主库不同步.由于业务读操作是针对从库的,数据不同步必定会带来数据的不一致,业务获取的 ...

  6. [MapReduce_4] MapTask 并发数的决定机制

    0. 说明 介绍 && Map 个数 & Reduce 个数指定 && Map 切片计算 1. 介绍 一个 job 的 Map 阶段并行度由客户端在提交 job ...

  7. Lua基础之MetaTable(6)

    Lua基础之MetaTable(6) 转载地址:http://nova-fusion.com/2011/06/30/lua-metatables-tutorial/ 关于MetaTable的补充:ht ...

  8. <项目需求规格说明书> - 福大易宝

    福大易宝-<项目需求规格说明书> 流程 在开会时统一了项目的开发背景,具体功能,然后在同一个工作环境下进行该说明书的编写. 分工 1.李佳铭.杜宏庆共同负责引言和项目描述的内容. 2.刘双 ...

  9. NGINX 安装于配置

    just a simple example, for more information -> http://nginx.org/en/docs/.1.vi /etc/yum.repos.d/ng ...

  10. redis面试必问

    1.项目中缓存是如何使用的?为什么要用缓存?缓存使用不当会造成什么后果? 面试题剖析 为什么要用缓存? 用缓存,主要有两个用途:高性能.高并发. 高性能 假设这么个场景,你有个操作,一个请求过来,吭哧 ...