手把手教你用Node爬取国家统计局最新省市区数据并生成一个JSON文件

前言

FBI WARNING: 作为一个中途自学转行到正式参加工作大半年的萌新码农,从大家的文章中学到了不少,当然最大的一个感受就是学的越多,不懂的也就越多。值得庆幸的是,学习的过程并没有那么枯燥,反而有种海绵吸水般的满足感与幸福感,或许这本身就是学习的乐趣吧,这篇文章也是本人第一篇技术文章,写得不好或者不对的地方,还请各位观众老爷多多包涵并且指出来,我铁汉柔情-王富贵在此谢谢大家了。

背景介绍

相信只要是前端的同学都不会对省市区三级联动这个词感到陌生,即使没写过同样的功能,也大多看过,具体怎么实现的今天的这篇文章就不说了(网上相关的文章也很多),主要说一下省市区数据。
省市区数据通常是一个JSON文件、一个数据量较大的obj变量或者来自后台接口,处于好奇,上周临近下班划水的时候在网上搜了一圈发现网上并没有统一的数据来源,拿到的数据结构也大相径庭,翻的过程中发现了一个靠谱(没有之一)的数据来源-国家统计局,下面是具体数据页面:
最新县及县以上行政区划代码(截止2016年7月31日)
点进去之后我们发现,它的文章结构是这样的:

HTML结构是这样的:

这么规范的结构这么权威的数据,不爬你爬谁!

准备工作

数据结构

来源有了,我们现在要确定的就是数据结构。 网上很多类似的省市区数据,比如这样的:

这样的:

类似这样的数据结构夹杂着比较多的数组,我也很喜欢用数组,操作数组的方法多且方便,我超喜欢用数组的,但是在这里会冲淡关联数据(包含数据与被包含数据)之间的对应关系,易用性也欠缺了那么一点,我理想的数据结构是这样的(不一定是最好的,但是对我来说,算是比较好用的一种数据结构),知道一个地区的编码,能直接查找到它的名称:

obj['43']['name'] //湖南省
obj['43']['01']['name'] //长沙市
obj['43']['01']['04']['name'] //岳麓区
复制代码

环境

确定了数据结构,我们就来做一下必要的准备工作,我们需要:

  • 最新LTS版NodeNode官网
  • 初始化package.jsonnpm init -y
  • 安装axiosnpm install -S axios
  • 安装cheerionpm install -S cheerio

axios是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中,用起来突出一个爽。
cheerio是一个以jquery语法为核心的服务器爬虫库。

代码实现

引入库并确定爬取网站,因为要另存为一个文件,所以我们要用到node的内置库fs。

const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');

const URL = 'http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201703/t20170310_1471429.html';

复制代码

因为要用到async和await语法,所以我们先用async闭包把代码包起来,好直接在后续代码中直接调用await,acync和await语法请参考阮老师的教程

(async function () {
    
})()
复制代码

首先我们要通过get请求获得整个页面的数据,然后通过cheerio包装,并且把含有关键省市区信息的tag筛选出来。

const data = (await axios.get(URL)).data; //异步获得整个页面数据
const $ = cheerio.load(data);
const tagArr = Array.from($('.TRS_PreAppend .MsoNormal'));
复制代码

接下来我们需要处理页面数据我们靠什么来区分这条数据是省数据还是市数据还是区县数据呢?答案是城市编码和城市名称之间的空格字符的长度

每条数组数据获取到的关键信息是这样的

110000     北京市
110100          市辖区
110101       东城区
复制代码

所以我们需要封装一个通过标签来返回空格长度的函数,并且把关键长度存入变量

function getSpaceLength(tag) {
        if (!tag) return false;
        if (tag.nodeType) tag = $(tag);
        return tag.text().trim().match(/\s+/)[0].length
    }
    
    let dataObj = {}, lengthArr = [];//总数据Object和区分数据的长度数组

    tagArr.slice(0, 3).forEach(tag => {
        let tempLen = getSpaceLength(tag);
        if (lengthArr.indexOf(tempLen) < 0) lengthArr.push(tempLen);
    })
    
    const [provinceKey, cityKey, areaKey] = lengthArr;
复制代码

省市区的数据结构如下:

      {
            code: 110101, //编码
            name: 东城区, //名称
            key: 01, //省就取编码前两位,市取编码中间两位,区县取最后两位
            type: 3 // 省为1,市为2,县为3
        }
复制代码

所以我们需要一个函数来返回数据结构

function getObj(tag, key, type) {
        if (!tag) return false;
        let tempArr = tag.text().trim().match(/\S+/g),
            tempCode = tempArr[0],
            tempName = tempArr[1];

        return {
            code: tempCode,
            name: tempName,
            key: tempCode.slice(key, key + 2),
            type: type
        }
    }

复制代码

接下来通过switch处理每条数据,并存入getObj返回的值

let tempProvinceObj, tempCityObj, tempAreaObj, tempObj, key;//省,市,区县,临时obj, 关键Key
    tagArr.forEach(item => {
        switch (getSpaceLength(item)) {
            case provinceKey:
                key = 0;
                tempObj = getObj(item, key, 1);
                dataObj[tempObj.key] = tempProvinceObj = tempObj;
                break;
            case cityKey:
                key = 2;
                tempObj = getObj(item, key, 2);
                tempProvinceObj[tempObj.key] = tempCityObj = tempObj;
                break;
            case areaKey:
                key = 4;
                tempObj = getObj(item, key, 3);
                tempCityObj[tempObj.key] = tempAreaObj = tempObj;
                break;
            default:
                break;
        }
    })

复制代码

最后通过fs把数据存入一个json文件,大功告成。

fs.writeFile('province.json', JSON.stringify(dataObj), 'utf8', err => {
        if (err) throw err;
        console.log('It\'s saved!');
    });

复制代码

存进去的数据是这样的

可以用编辑器自带的工具美化一下

最后贴一下总代码

const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');

const URL = 'http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201703/t20170310_1471429.html';

(async function () {
    function getSpaceLength(tag) {
        if (!tag) return false;
        if (tag.nodeType) tag = $(tag);
        return tag.text().trim().match(/\s+/)[0].length
    }

    const data = (await axios.get(URL)).data; //异步获得整个页面数据
    const $ = cheerio.load(data);
    const tagArr = Array.from($('.TRS_PreAppend .MsoNormal'));
    let dataObj = {}, lengthArr = [];//总数据Object和区分数据的长度数组

    tagArr.slice(0, 3).forEach(tag => { //总共只有省市区三个关键长度所以,取前三条数组就好了
        let tempLen = getSpaceLength(tag);
        if (lengthArr.indexOf(tempLen) < 0) lengthArr.push(tempLen);
    })

    const [provinceKey, cityKey, areaKey] = lengthArr;
    function getObj(tag, key, type) {
        if (!tag) return false;
        let tempArr = tag.text().trim().match(/\S+/g),
            tempCode = tempArr[0],
            tempName = tempArr[1];

        return {
            code: tempCode,
            name: tempName,
            key: tempCode.slice(key, key + 2),
            type: type
        }
    }
    let tempProvinceObj, tempCityObj, tempAreaObj, tempObj, key;//省,市,区县,临时obj, 关键Key
    tagArr.forEach(item => {
        switch (getSpaceLength(item)) {
            case provinceKey:
                key = 0;
                tempObj = getObj(item, key, 1);
                dataObj[tempObj.key] = tempProvinceObj = tempObj;
                break;
            case cityKey:
                key = 2;
                tempObj = getObj(item, key, 2);
                tempProvinceObj[tempObj.key] = tempCityObj = tempObj;
                break;
            case areaKey:
                key = 4;
                tempObj = getObj(item, key, 3);
                tempCityObj[tempObj.key] = tempAreaObj = tempObj;
                break;
            default:
                break;
        }
    })
    fs.writeFile('province.json', JSON.stringify(dataObj), 'utf8', err => {
        if (err) throw err;
        console.log('It\'s saved!');
    });
    
})()


复制代码

最后祝大家新年快乐,完。

转载于:https://juejin.im/post/5a5b04a5f265da3e534227d5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值