本文更新(移步查阅):
19-04-15 新采集了2018的省市区三级坐标和行政区域边界
19-03-22 采集了2018的城市数据
18-11-28 采集了2017的城市数据javascript数据下载 GitHub:https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov/releases
相关更新状况,请查阅我发布的其余文章,本文如下内容再也不更新。php
18-01-28早上6:30的火车,从三亚回老家,票难买啊。好激动~
声明:文中涉及到的数据和第三方接口、url仅供学习使用,请勿它用~html
这几天都在磨着搭建本地测试环境,看到省市区数据表里面是空的,想着之前的老数据仍是13年采集的,含省市区县4级数据共4.8万条,时间久了,使用过程当中发现有些新的城市名称数据库中没有,县级数据历来就没有用到过,想着仍是从新采集一份。java
新采集的省市区数据有3589条,此次并无把县级数据采过来,须要的时候再添加也挺好。git
国家统计局统计标准《2016年统计用区划代码和城乡划分代码(截止2016年07月31日)》,这个是2017-05-16发布的,当前是最新的。
github
对于数据采集,根据工做须要,对于一些小的数据采集功能有些接触。由于对html和js熟些,很早之前就用IE浏览器对本地html文件支持任意跨域ajax请求数据、和支持读写Excel文件,就直接写一个html文件做为采集工具给别人使用,批量查询人员资料、考试结果什么的功能。因此采集省市区数据主要用的js。ajax
打开网页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()
采集截图:
浏览器
数据处理就简单些了,好比编号格式化、名称格式化等。
拼音标注:这个须要找一个接口对文字进行拼音翻译,只有一个要求:重庆能正常的翻译成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分钟多点所有翻译完成。
数据所有有了,导出成比较正常使用的格式,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,数据所有搞完:
id编号和国家统计局的编号基本一致,方便之后更新。
id重复项目前是没有(已优化过了),不过之前采集后直接对统计局的编号进行简单缩短后会有重复现象(算是精度丢失)。
拼音前缀取的是第一个字前两个字母和后两个字首字母,意图是让第一个字相同名称的尽可能能排序在一块儿。排序1:黑龙江helj、湖北hub、湖南hun
;排序2:湖北hb、黑龙江hlj、湖南hn
,排序一胜出。
由于区名字是直接去掉市、区后缀,存在那么几对名字变得彻底同样的,须要手动吧市区后缀加上,否则会产生小问题。
最终数据已上传了一份到CSDN,含全部代码和本文档:,GitHub下载最新数据http://download.csdn.net/download/xiangyuecn/10226964