区划代码 node 版爬虫尝试
前言
对于区划代码数据,很多人都不会陌生,大多公司数据库都会维护一份区划代码,包含省市区等数据。区划信息跟用户信息息息相关,往往由于历史原因很多数据都是比较老的数据,且不会轻易更改。网上也有很多人提供的数据,或许大多数数据已经老旧,尽管并不会影响太多。
网上只提供数据,好像很少有人提供方法。最近有时间就来做一次爬虫的初尝,有想法但无奈没学 python,就拼凑了个 node 版的。
第一步 找资源
地名服务资源一般只有政府部门才有权威性,比对某些网上提供的资源发现并不靠谱,特别是县以下的区划代码。搜索到以下资源:
结果发现,政府没有提供统一的数据,区划信息比较分散,县级以上的数据有提供 2017 最新版的数据,但县级以下的数据只有 2013 年 和 2015 年的数据,以及每年来县级以下的数据变更情况。但 2013 和 2015 以不同的方式展示,一个页面的数据还是比较容易获取,如 2013 年的数据。但 2015 年数据尽管是分散在不同的页面中,还是有一定的规律的。另外博雅地名分享网的数据相对比较新,但与官方比较还是有些差异。这次以 2015 的数据为例
第二步 搭环境
node 环境提供了众多包,可以方便的实现一些功能,下面只是这些工具包在本次爬虫的所用到的功能,更多资料网上有很多,不过多说明(其实我是不完全会用这些工具,只是用了部分功能去实现而已~~捂脸)
request // 请求
cheerio // node 版的 jQuery
iconv-lite // 请求返回的数据转码
async // 处理并发请求数
第三步 书写代码
观察区划代码,你就会发现一些规律,根据这些规律,可以把省市区的数据格式化成自己想要的格式
省级或直辖市: 第三,四位是 00
市级: 第五,六位是 00
省级直辖县: 第三,四位是 90
编码规则 引自维基百科
代码从左至右的含义是:
第一、二位表示省级行政单位(省、自治区、直辖市、特别行政区),其中第一位代表大区。
第三、四位表示地级行政单位(地级市、地区、自治州、盟及省级单位直属县级单位的汇总码)。
对于省(自治区)下属单位:01-20,51-70表示省辖市(地级市);21-50表示地区(自治州、盟);90表示省(自治区)直辖县级行政区划的汇总。
对于直辖市下属单位:01表示市辖区的汇总;02表示县的汇总。
第五、六位表示县级行政单位(县、自治县、市辖区、县级市、旗、自治旗、林区、特区)。
对于地级市下属单位:01-20表示市辖区(特区);21-80表示县(旗、自治县、自治旗、林区);81-99表示地级市代管的县级市。
对于直辖市所辖县级行政单位:01-20、51-80代表市辖区;21-50代表县(自治县)。
对于地区(自治州、盟)下属单位:01-20表示县级市;21-80表示县(旗、自治县、自治旗)。
对于省级直辖县级行政单位:同地区。
- 需要把获取的数据写入 json 文件就需要 fs
// require 需要的包
const fs = require('fs');
const entryUrl = 'http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2015/index.html';
const writeJSON = fs.createWriteStream(__dirname + '/data/list-2015.json')
// 待写入的 JSON 数据
let listJSON = {};
- 需要获取首页的数据

function captureUrl(url, fn, provinceName) {
request.get({
url: url,
encoding: null //让 body 直接是 buffer,也有别的方法实现
}, (error, response, body) => {
if (!error && response.statusCode == 200) {
// 页面编码为 gb2312 需要转码
const convertedHtml = iconv.decode(body, 'gb2312');
// 请求返回数据解析 jQuery 对象
const $ = cheerio.load(convertedHtml, {
decodeEntities: false
});
$.href = url;
$.provinceName = provinceName;
fn($);
}
if (error) {
console.log('error: ' + url)
}
});
}
captureUrl(entryUrl, function (res) {
parseHtml.deal(res, entryUrl);
});
- 根据页面结构获取想要的数据,这里是链接和城市名,浏览器 f12 都能看得懂的哈~

地址是相对路径,就需要根据请求的 url 地址来拼接出下一个页面真实地址
// 相对路径地址转换
function combineLink({
originUrl,
spliceUrl
}) {
if (typeof originUrl == 'string' && /^http/.test(originUrl)) {
const lastIndex = originUrl.lastIndexOf('/');
return originUrl.substr(0, lastIndex + 1) + spliceUrl;
}
}
有了 cheerio 可以方便的获取页面节点,从而拿到我们想要的数据
const parseHtml = {
captureProvince($) {
const linkList = $('.provincetr a');
let provinceArr = [];
linkList.map((index, item) => {
// 循环 10 个省份能完好执行异步回调,超过就出现问题
// if (index < 10) {
const $item = $(item);
provinceArr.push({
url: combineLink({
originUrl: entryUrl,
spliceUrl: $item.attr('href')
}),
name: $item.text().trim()
});
// }
});
return provinceArr;
}
}
有了链接就可以做下一步的处理,同样观察页面结构,获取有用的数据。这么多链接请求,到底什么时候会请求完成?这时候就有请 Promise 出场了
// 省份链接
const provinceArr = parseHtml.captureProvince(res);
Promise.all(provinceArr.map(item => {
const provinceName = item.name;
const provinceUrl = item.url;
return new Promise(resolve => {
captureUrl(provinceUrl, resolve, provinceName);
});
})).then(res => {
// 所有请求都结束
}).catch(err => {
// 异常处理
});
以为就这样顺利的走下去,就大功告成了。可遗憾的是新的问题出现了。在抓取博雅网的时候并发请求过多,请求失败的异常链接会有很多,但这些链接并没有问题。这是网站的防御机制,我们伪造的合法请求触发网站的防御机制,或许叫 CC 攻击来实现 DDOS 。貌似这么做对别的网站造成损失可能就要被请喝茶,请慎重,这时候就需要限制并发数不妨碍别人网站经营,asyn 有很强大的功能,这里只用了 mapList 方法,目的限制并发数
Promise.all(provinceArr.map(item => {
const provinceName = item.name;
const provinceUrl = item.url;
return new Promise(resolve => {
captureUrl(provinceUrl, resolve, provinceName);
});
}))
.then(res => {
let tempArr = [];
res.map(item => {
// 解析市一级数据
tempArr = tempArr.concat(this.captureCity(item, item.href))
});
return new Promise(resolve => {
async.mapLimit(tempArr, 10, function (item, callback) {
let temp = [];
captureUrl(item, function ($) {
console.log(item);
// 解析区县一级数据
temp = temp.concat(parseHtml.captureCountry($));
callback(null, temp);
});
}, function (err, result) {
let tempArr = [];
result.map(item => {
tempArr = tempArr.concat(item);
});
resolve(tempArr);
});
});
}).then(res => {
return new Promise(resolve => {
async.mapLimit(res, 10, function (item, callback) {
let temp = [];
captureUrl(item, function ($) {
console.log(item);
// 解析街道或者乡镇一级数据
parseHtml.captureTown($);
// 省份超过 10 个 callback 无效。找不到原因,不得已写法
// fs.createWriteStream(__dirname + '/data/list-2015.json').write(JSON.stringify(listJSON));
callback(null);
});
}, function (err, result) {
console.log(err)
resolve(true)
});
});
}).then(res => {
// 所有请求完成后 callback
console.log('区划信息写入成功~')
writeJSON.write(JSON.stringify(listJSON));
}).catch(err => {
console.log(err)
});
处理数据过程中,可以对源数据加以处理,获取数据展示
{
"110000000":"北京市",
"110101000":"东城区",
"110101001":"东华门街道",
"110101002":"景山街道"
...
...
}
第四步 update 数据
根据区划代码变更的页面获取数据来更新已经获取到的 2015 年的数据,采取同样的方法去拿到链接获取页面数据,发现这些变更情况的链接有重定向,需要做相应的处理才能拿到真实链接
function getRedirectUrl($) {
const scriptText = $('script').eq(0).text().trim();
const matchArr = scriptText.match(/^window.location.href="(\S*)";/);
if (matchArr && matchArr[1]) {
return matchArr[1];
} else {
console.log('重定向页面转换错误~')
}
}
在调试的过程中发现变更情况页面是表格类型,不能很方便的拿到数据并区分开来,不能只是左侧的数据需要 delete,右侧的数据 add。 我的思路是根据变更原因来分类处理,此部分代码没有写完

调试
我使用的编辑工具是 VSCode,调试也比较方便,F5 写好 launch.json 就可调试了。 可能我只知道这种调试吧,哈哈哈~
结局
由于官方地名普查办正在进行第二次地名普查,确保 2017 年 6 月 30 号完成全国地名普查工作,并向社会提供地名服务,详见如何切实做好第二次全国地名普查验收工作
到时候看官方公布的数据情况,来决定要不要完成这个工作,目前不想浪费太多的时间在这个上面,耗费了时间得来的数据有偏差,将来可能是要负责任的, 得不偿失。在此只是提供一种思路,有兴趣的可以自己尝试,有好的方法可以推荐一下哈哈~
详细代码见 division-code
疑惑
mapLimit(coll, limit, iterate, callback)
当循环省份超过 10 个时,回调执行写入区划信息会失败,也没执行 error,是不是我的用法有问题,望能得到解答
参考资料
区划代码 node 版爬虫尝试的更多相关文章
- Atitit 爬虫 node版 attilaxA
Atitit 爬虫 node版 attilax 1.1. 貌似不跟python压实,,java的webmagic压实,,什么爬虫框架也没有,只好自己写了. 查了百度三爷资料也没有.都是自己写.. 1. ...
- Node.js aitaotu图片批量下载Node.js爬虫1.00版
即使是https网页,解析的方式也不是一致的,需要多试试. 代码: //====================================================== // aitaot ...
- Node.js abaike图片批量下载Node.js爬虫1.01版
//====================================================== // abaike图片批量下载Node.js爬虫1.01 // 1.01 修正了输出目 ...
- Node.js abaike图片批量下载Node.js爬虫1.00版
这个与前作的差别在于地址的不规律性,需要找到下一页的地址再爬过去找. //====================================================== // abaik ...
- 80行Python代码搞定全国区划代码
微信搜索:码农StayUp 主页地址:https://gozhuyinglong.github.io 源码分享:https://github.com/gozhuyinglong/blog-demos ...
- 【原】小玩node+express爬虫-2
上周写了一个node+experss的爬虫小入门.今天继续来学习一下,写一个爬虫2.0版本. 这次我们不再爬博客园了,咋玩点新的,爬爬电影天堂.因为每个周末都会在电影天堂下载一部电影来看看. talk ...
- 一个超级简单的node.js爬虫(内附表情包)
之所以会想到要写爬虫,并不是出于什么高大上的理由,仅仅是为了下载个表情包而已-- 容我先推荐一下西乔出品的神秘的程序员表情包. 这套表情包着实是抵御产品.对付测试.嘲讽队友.恐吓前任的良品, 不过不知 ...
- node.js爬虫杭州房产销售及数据可视化
现在年轻人到25岁+,总的要考虑买房结婚的问题,2016年的一波房价大涨,小伙伴们纷纷表示再也买不起上海的房产了,博主也得考虑考虑未来的发展了,思考了很久,决定去杭州工作.买房.定居.生活,之前去过很 ...
- node.js爬虫
这是一个简单的node.js爬虫项目,麻雀虽小五脏俱全. 本项目主要包含一下技术: 发送http抓取页面(http).分析页面(cheerio).中文乱码处理(bufferhelper).异步并发流程 ...
随机推荐
- Android -- 贝塞尔曲线公式的推导
1,最近看了几个不错的自定义view,发现里面都会涉及到贝塞尔曲线知识,深刻的了解到贝塞尔曲线是进阶自定义view的一座大山,so,今天先和大家来了解了解. 2,贝塞尔曲线作用十分广泛,简单举几个的栗 ...
- centos 6.5 搭建ftp服务器
linux下一般使用vsftpd作为ftp服务器. vsftpd是一款在Linux发行版中最受推崇的FTP服务器程序.特点是小巧轻快,安全易用. 下面是安装配置步骤: 1.安装vsftpd yum i ...
- 6、CC2541修改按键调节广播发送功率例程为持续发送4DB的蓝牙基站
一.目的 在 OSAL操作系统-实验31 从机广播功率修改-(20141029更新).zip 基础上进行修改,该工程是通过5向按键的上下按键来控制广播功率的加减,总共有4个档位.我们的目的是直接用最高 ...
- web中关于垃圾回收的一些观点
感觉dom大神的解惑 关于引用计数法,注意引用的方向性就行. A.addEventListner(B.func), 那么是增加了A对B的引用.如果A是不可回收的对象,比如全局的Stage,或者单例. ...
- zookeeper的安装与部署-伪集群
1.Zookeeper的下载与解压 通过后面的链接下载Zookeeper: Zookeeper下载在此我们下载zookeeper-3.4.5下载后解压至安装目录下,本文我们解压到目录:/ ...
- 优雅高效的MyBatis-Plus工具快速入门使用
目前正在维护的公司的一个项目是一个ssm架构的java项目,dao层的接口有大量数据库查询的方法,一个条件变化就要对应一个方法,再加上一些通用的curd方法,对应一张表的dao层方法有时候多达近20个 ...
- MongoDB--在windows下的安装过程及基本配置
这几天在做一个简单的后台博客系统,数据库用到了 MongoDB ,虽说官方的文档比较全,但是对于我一个英语一般的人来说,或多或少在配置的时候出现了一些问题,总结了一下在安装及创建服务的过程 Mongo ...
- mysql 分析第一步
分析mysql 慢的原因 思路 通过脚本观察 status -->看是否会出现周期性波动 一般由访高峰或缓存崩溃引起 加缓存更改 缓存失效策略 使失效时间分散 或夜间定时失效 --&g ...
- 数字图像处理(MATLAB版)学习笔记(2)——第2章 灰度变换与空间滤波
0.小叙闲言 1.本章整体结构 2.书中例子 例2.1 主要是使用函数imadjust,来熟悉一下灰度处理,体验一把 >> imread('myimage.jpg'); >> ...
- 用jQuery模拟淘宝购物车
首先我们要实现的内容的需求有如下几点: 1.在购物车页面中,当选中"全选"复选框时,所有商品前的复选框被选中,否则所有商品的复选框取消选中. 2.当所有商品前的复选框选中时,&qu ...