spider.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. 'use strict'
  2. var http = require('http')
  3. var async = require('async')
  4. var iconv = require('iconv-lite')
  5. var BufferHelper = require('bufferhelper')
  6. var al = 0
  7. var ai = 0
  8. /**
  9. * 从国家统计局(http://www.stats.gov.cn/)抓取县级以及县级以上行政区划数据
  10. * @author modood <https://github.com/modood>
  11. * @datetime 2016-12-19 16:32
  12. */
  13. function fetch (callback) {
  14. // 数据截止 2016 年 07 月 31 日(发布时间:2017-03-10 10:33)
  15. http.get('http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201703/t20170310_1471429.html', function (res) {
  16. var rawData = ''
  17. var statusCode = res.statusCode
  18. console.log('[1/1] 正在抓取省份、城市和区县数据...')
  19. if (statusCode !== 200) {
  20. res.resume()
  21. return callback(new Error('Request Failed. Status Code: ' + statusCode))
  22. }
  23. res.setEncoding('utf8')
  24. res.on('data', function (chunk) {
  25. rawData += chunk
  26. })
  27. res.on('end', function () {
  28. var current
  29. var result = {}
  30. var reg = /<span lang="EN-US">(.*?)<span>(?:&nbsp;)+ <\/span><\/span>(?:<\/b>)?(?:<b>)?<span style="font-family: 宋体">(.*?)<\/span>/g
  31. while ((current = reg.exec(rawData)) !== null) {
  32. result[current[1]] = current[2].trim()
  33. }
  34. return callback(null, result)
  35. })
  36. }).on('error', callback)
  37. }
  38. /**
  39. * 从国家统计局(http://www.stats.gov.cn/)抓取城乡行政区划数据
  40. * @author modood <https://github.com/modood>
  41. * @datetime 2016-12-19 16:35
  42. */
  43. function fetchStreets (area, callback) {
  44. var html = ''
  45. var areaCode = area.code
  46. var areaName = area.name
  47. // 特殊城市单独处理(中山市、东莞市、儋州市没有县级行政区划)
  48. switch (areaCode) {
  49. case '441900': html = '44/4419.html'; break
  50. case '442000': html = '44/4420.html'; break
  51. case '460400': html = '46/4604.html'; break
  52. default: html = areaCode.substr(0, 2) + '/' + areaCode.substr(2, 2) + '/' + areaCode + '.html'
  53. }
  54. // 数据截止 2016 年 07 月 31 日(发布时间:2017-05-16)
  55. http.get('http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/' + html, function (res) {
  56. var bufferHelper = new BufferHelper()
  57. var statusCode = res.statusCode
  58. if (statusCode !== 200 && statusCode !== 404) {
  59. res.resume()
  60. return fetchStreets(area, callback)
  61. }
  62. // 特殊城市或区县抓取乡镇数据不打印输出
  63. if ([
  64. '441900', // 东莞市
  65. '442000', // 中山市
  66. '460400', // 儋州市
  67. '460321', // 三沙市-西沙群岛
  68. '460322', // 三沙市-南沙群岛
  69. '460323', // 三沙市-中沙群岛的岛礁及其海域
  70. '620201' // 嘉峪关市
  71. ].indexOf(areaCode) === -1) {
  72. console.log('[' + ++ai + '/' + al + '] 正在抓取乡镇数据,当前区县:', areaCode, areaName)
  73. }
  74. if (statusCode === 404) {
  75. res.resume()
  76. return callback(null, {})
  77. }
  78. res.on('data', function (chunk) {
  79. bufferHelper.concat(chunk)
  80. })
  81. res.on('end', function () {
  82. var rawData = iconv.decode(bufferHelper.toBuffer(), 'GBK')
  83. var current
  84. var result = {}
  85. var reg = /<tr class='.*?'><td><a href=.*?>(.*?)<\/a><\/td><td><a href=.*?>(.*?)<\/a><\/td><\/tr>/g
  86. while ((current = reg.exec(rawData)) !== null) {
  87. result[current[1]] = current[2].trim()
  88. }
  89. return callback(null, result)
  90. })
  91. }).on('error', function () {
  92. console.log('连接超时,马上重试...')
  93. fetchStreets(area, callback)
  94. })
  95. }
  96. /**
  97. * 提取省份、城市和区县数据
  98. * @author modood <https://github.com/modood>
  99. * @datetime 2016-12-20 13:18
  100. */
  101. function pick (callback) {
  102. fetch(function (err, data) {
  103. var provinces = []
  104. var cities = []
  105. var areas = []
  106. if (err) return callback(err)
  107. for (var k in data) {
  108. if (k.substr(2, 4) === '0000') {
  109. // 省份数据
  110. provinces.push({
  111. code: k,
  112. name: data[k]
  113. })
  114. } else if (k.substr(4, 2) === '00' && k.substr(2, 4) !== '0000') {
  115. // 城市数据
  116. cities.push({
  117. code: k,
  118. name: data[k],
  119. parent_code: k.substr(0, 2) + '0000'
  120. })
  121. } else if (k.substr(4, 2) !== '00' && data[k] !== '市辖区') {
  122. // 区县数据
  123. areas.push({
  124. code: k,
  125. name: data[k],
  126. parent_code: k.substr(0, 4) + '00'
  127. })
  128. }
  129. }
  130. return callback(null, {
  131. provinces: provinces,
  132. cities: cities,
  133. areas: areas
  134. })
  135. })
  136. }
  137. /**
  138. * 提取乡镇数据
  139. * @author modood <https://github.com/modood>
  140. * @datetime 2016-12-20 13:17
  141. */
  142. function pickStreets (areas, callback) {
  143. var streets = []
  144. async.mapLimit(areas, 10, function (item, cb) {
  145. al = areas.length
  146. fetchStreets(item, function (err, data) {
  147. if (err) return cb(err)
  148. for (var k in data) {
  149. // 乡镇数据
  150. streets.push({
  151. code: k,
  152. name: data[k],
  153. parent_code: k.substr(0, 6)
  154. })
  155. }
  156. return cb(null)
  157. })
  158. }, function (err) {
  159. if (err) console.log('getStreets timeout, ignored:\n', err)
  160. return callback(null, streets)
  161. })
  162. }
  163. /**
  164. * 特殊城市单独处理
  165. * @author modood <https://github.com/modood>
  166. * @datetime 2016-12-20 15:11
  167. */
  168. function handleSpecialCities (callback) {
  169. // 1. 中山市、东莞市、儋州市没有县级行政区划
  170. // 2. 嘉峪关市下有一个县级行政区划叫市辖区(code: 620201),
  171. // 因此也视为没有县级行政区划,但是 code 需要保留处理。
  172. // 3. 三沙市下有县级行政区划,但是在 “最新县及县以上行政区划代码” 中
  173. // 没有,因此需要手动加上。
  174. // 4. 福建省泉州市金门县没有乡镇级行政区划
  175. var areas = [
  176. { code: '442000', name: '中山市', parent_code: '442000' },
  177. { code: '441900', name: '东莞市', parent_code: '441900' },
  178. { code: '460400', name: '儋州市', parent_code: '460400' },
  179. { code: '620201', name: '嘉峪关市', parent_code: '620200' },
  180. { code: '460321', name: '西沙群岛', parent_code: '460300' },
  181. { code: '460322', name: '南沙群岛', parent_code: '460300' },
  182. { code: '460323', name: '中沙群岛的岛礁及其海域', parent_code: '460300' }
  183. ]
  184. var streets = []
  185. async.each(areas, function (area, cb) {
  186. fetchStreets(area, function (err, data) {
  187. if (err) return cb(err)
  188. for (var k in data) {
  189. streets.push({
  190. code: k,
  191. name: data[k],
  192. parent_code: k.substr(0, 6)
  193. })
  194. }
  195. return cb(null)
  196. })
  197. }, function (err, result) {
  198. if (err) return callback(err)
  199. return callback(null, {
  200. areas: areas,
  201. streets: streets
  202. })
  203. })
  204. }
  205. /**
  206. * 对抓取到的数据进行处理,提取出“省份”、“城市”、“区县”和“乡镇”四种数据
  207. * @author modood <https://github.com/modood>
  208. * @datetime 2016-12-19 16:37
  209. */
  210. exports.getData = function (callback) {
  211. async.auto({
  212. pca: function (cb) {
  213. pick(cb)
  214. },
  215. streets: ['pca', function (result, cb) {
  216. var areas = result.pca.areas
  217. pickStreets(areas, cb)
  218. }]
  219. }, function (err, result) {
  220. if (err) return callback(err)
  221. handleSpecialCities(function (err, r) {
  222. if (err) return callback(err)
  223. var areas = result.pca.areas.concat(r.areas)
  224. var streets = result.streets.concat(r.streets)
  225. return callback(null, {
  226. provinces: result.pca.provinces,
  227. cities: result.pca.cities,
  228. areas: areas,
  229. streets: streets
  230. })
  231. })
  232. })
  233. }