前言
FBI WARNING: 作为一个中途自学转行到正式参加工作大半年的萌新码农,从大家的文章中学到了不少,当然最大的一个感受就是学的越多,不懂的也就越多。值得庆幸的是,学习的过程并没有那么枯燥,反而有种海绵吸水般的满足感与幸福感,或许这本身就是学习的乐趣吧,这篇文章也是本人第一篇技术文章,写得不好或者不对的地方,还请各位观众老爷多多包涵并且指出来,我铁汉柔情-王富贵在此谢谢大家了。
背景介绍
相信只要是前端的同学都不会对省市区三级联动这个词感到陌生,即使没写过同样的功能,也大多看过,具体怎么实现的今天的这篇文章就不说了(网上相关的文章也很多),主要说一下省市区数据。
省市区数据通常是一个JSON文件、一个数据量较大的obj变量或者来自后台接口,处于好奇,上周临近下班划水的时候在网上搜了一圈发现网上并没有统一的数据来源,拿到的数据结构也大相径庭,翻的过程中发现了一个靠谱(没有之一)的数据来源-国家统计局,下面是具体数据页面:
最新县及县以上行政区划代码(截止2016年7月31日)
点进去之后我们发现,它的文章结构是这样的:
准备工作
数据结构
来源有了,我们现在要确定的就是数据结构。 网上很多类似的省市区数据,比如这样的:
obj['43']['name'] //湖南省
obj['43']['01']['name'] //长沙市
obj['43']['01']['04']['name'] //岳麓区
复制代码
环境
确定了数据结构,我们就来做一下必要的准备工作,我们需要:
- 最新LTS版NodeNode官网
- 初始化package.json
npm init -y
- 安装axios
npm install -S axios
- 安装cheerio
npm 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!');
});
})()
复制代码
最后祝大家新年快乐,完。