本文更新(移步查閱):
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