get.js 7.9 KB

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