18 01 28早上6:30的火車,從三亞回老家,票難買啊。好激動~ 聲明:文中涉及到的數據和第三方介面、url僅供學習使用,請勿它用~ 這幾天都在磨著搭建本地測試環境,看到省市區數據表裡面是空的,想著以前的老數據還是13年採集的,含省市區縣4級數據共4.8萬條,時間久了,使用過程中發現有些新的城市 ...
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,數據全部搞完:
數據問題
id編號和國家統計局的編號基本一致,方便以後更新。
id重覆項目前是沒有(已優化過了),不過以前採集後直接對統計局的編號進行簡單縮短後會有重覆現象(算是精度丟失)。
拼音首碼取的是第一個字前兩個字母和後兩個字首字母,意圖是讓第一個字相同名稱的儘量能排序在一起。排序1:
黑龍江helj、湖北hub、湖南hun
;排序2:湖北hb、黑龍江hlj、湖南hn
,排序一勝出。因為區名字是直接去掉市、區尾碼,存在那麼幾對名字變得完全一樣的,需要手動吧市區尾碼加上,不然會產生小問題。
最終數據已上傳了一份到CSDN,含所有代碼和本文檔:
http://download.csdn.net/download/xiangyuecn/10226964