从统计局采集最新的省市区县数据,纯js

2018-01-27 19:16:26来源:cnblogs.com作者:xiangyuecn人点击

分享

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.phpPOSTt=汉字&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/10226964

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台