经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Python » 查看文章
历年行政区划码成品下载,欢迎白瞟,拿走不谢
来源:cnblogs  作者:顾志兵  时间:2024/3/13 9:18:15  对本文有异议

几乎所有业务系统,都会涉及行政区域。国家统计局 官网上公开了所有的区域编码,一年一更新。但只能在线查看,没有提供完整数据库下载的连接。为此,我编写了一个简陋的 python 脚本,抓取了近几年的数据,供大家下载。如果这里的下载成品中没有你需要的数据,可以根据自己的要求,修改脚本,再运行起来去官网抓取即可。

?? 特别说明

  • 本脚本使用的 python 版本为 3.12.2
  • 本脚本仅在 windows11 下验证通过,未在 Linux 上验证过

行政区划码的特点

  • 编码是长度固定为12位的纯数字

    比如北京的编码 110000000000, 长度为12位,并且全部为数字,同时第1位数字不为0,也就是说,在数据库存储区位码时,可以直接使用 number 类型,而不必是 varchar。

  • 区划码共5个等级,见下表:

    等级 行政级别 示例
    1 省/直辖市 · 四川省
    · 北京市
    2 · 四川省/成都市
    · 北京市/市辖区
    3 区/县 · 四川省/成都市/武候区
    · 陕西省/咸阳市/泾阳县
    4 街道/乡镇 · 四川省/成都市/武候区/石羊街道
    · 陕西省/咸阳市/泾阳县/永乐镇
    5 社区/村委会 · 四川省/成都市/武候区/石羊街道/府城社区居委会
    · 陕西省/咸阳市/泾阳县/永乐镇/磨子桥村委会
  • 各等级所占数字位数及开始位置如下

    1. 四川省/成都市/武候区/石羊街道/府城社区居委会
    2. +----+----+----+-----+-----+
    3. | 51 | 01 | 07 | 063 | 009 |
    4. +----+----+----+-----+-----+

成品下载

年份 3级数据 4级数据 5级数据
2023 共3629条 点击下载 共4,4903条 点击下载 敬请期待
2022 共3634条 点击下载 共4,4907条 点击下载 敬请期待
2021 共3640条 点击下载 共4,4918条 点击下载 敬请期待
2020 共3644条 点击下载 共4,5180条 点击下载 敬请期待
2019 共3645条 点击下载 共4,6672条 点击下载 敬请期待

通常下载后,需要将数据保存到 MySql 数据库。假定你的 MySql 数据库信息如下:

  • 用户名:root
  • 密 码:root
  • 端 口:3306
  • 数据库名:my_db
  • 下载后的Sql文件位置为:d:\admin_area_2023_level-4.sql

则执行以下脚本将数据写入到 MySql

  1. mysql -uroot -proot -P3306 my_db < D:\admin_area_2023_level-4.sql

根据实测情况,抓取不同等级范围的数据,耗时差别巨大,详情如下:

  • 3级数据:3分钟左右
  • 4级数据:30分钟左右
  • 5级数据:8个小时以上

Python 脚本

脚本代码

  1. import requests
  2. from lxml import etree
  3. import pymysql
  4. import traceback
  5. import time
  6. # 关闭 https 相关的警告
  7. requests.packages.urllib3.disable_warnings()
  8. # 国家统计局 (National Bureau Of Statistics) 行政区划数据抓取的主URL
  9. HOME_URL = "https://www.stats.gov.cn/sj/tjbz/tjyqhdmhcxhfdm/2023"
  10. # 是否开启打印输出
  11. ENABLE_PRINT = False
  12. # 最大抓取深度,最抓取到哪一个层级的区域数据,总共5级
  13. MAX_GRAB_LEVEL = 4
  14. # 是否开启将数据写入到MySql
  15. ENABLE_MYSQL_INSERTION = True
  16. # 遇到列值为 null 时,是否跳过这条记录,继续向下执行
  17. SKIP_NULL_COLUMN_VALUE = True
  18. # 抓取的最大数据条数,主要用于调代码,避免输出内容太多,负数代表抓取所有
  19. MAX_GRAB_COUNT = -1
  20. # 当前正在处理的省份,用于判断是否是直辖市
  21. current_province_name = None
  22. # 当前正在处理的城市名,用于判断提交MySql时,日志输出
  23. current_city_name = None
  24. # 连接MySql,请根据实际情况修改
  25. try:
  26. db = pymysql.connect(host='localhost', user='root', passwd='root', port=3306, db="my_db")
  27. cursor = db.cursor()
  28. print('连接Mysql成功!')
  29. except:
  30. print('连接MySql失败')
  31. exit
  32. def print_info(message:str):
  33. '''
  34. 自定义一个内容输出方法,主要目的是可以统一控制是否输出,用于调试
  35. '''
  36. if ENABLE_PRINT:
  37. print(message)
  38. def insert_area_to_mysql(code:str, name:str, level:int, parent_code:str):
  39. '''
  40. 插入一条记录到MySql,但不提交
  41. 参数:
  42. code(str): 区域编码
  43. name(str): 区域名称
  44. level(int): 区域等级,
  45. 1: 省/直辖市
  46. 2: 市
  47. 3: 区/县
  48. 4: 乡镇/街道
  49. 5: 社区/村委会
  50. parent_code(str): 父级编码
  51. '''
  52. if not ENABLE_MYSQL_INSERTION:
  53. return
  54. if code is None or name is None:
  55. print("发现null值:code={}, name={}, level={}, parent_code={}".format(code, name, level, parent_code))
  56. if SKIP_NULL_COLUMN_VALUE:
  57. return
  58. else:
  59. db.close()
  60. print("插入到MySql时遇到 Null 列值,程序将退出")
  61. exit()
  62. sql = "insert into admin_area_2023(`code`, `name`, `level`, `parent_code`) values ('{}', '{}', {}, '{}')".format(code, name, level, parent_code)
  63. sql = sql.replace("'None'", 'NULL')
  64. print_info(sql)
  65. cursor.execute(sql)
  66. def commit_for_mysql():
  67. global db, current_province_name
  68. try:
  69. db.commit()
  70. print("保存<{}·{}>行政区划数据到MySql成功".format(current_province_name, current_city_name))
  71. except Exception as e:
  72. db.rollback()
  73. print("保存" + current_province_name + "的行政区划数据到MySql失败")
  74. print(traceback.format_exc())
  75. def get_admin_area_html(url:str):
  76. try_count = 0
  77. while try_count < 3:
  78. try_count += 1
  79. try:
  80. if try_count == 1:
  81. time.sleep(0.1)
  82. # 第一次抓取失败
  83. elif try_count == 2:
  84. time.sleep(1)
  85. else:
  86. time.sleep(2)
  87. response = requests.get(url)
  88. response.encoding = response.apparent_encoding
  89. return etree.HTML(response.text)
  90. except Exception:
  91. if try_count > 3:
  92. print(traceback.format_exc())
  93. print("连续 {} 次抓取 {} 页面时发生错误, 将放弃本页面的数据抓取。可能被服务怀疑是爬虫,拒绝了网络连接,因此休息10秒".format(try_count, url))
  94. time.sleep(10)
  95. return None
  96. else:
  97. print("第 {} 次抓取 {} 网页文本失败".format(try_count, url))
  98. def grap_all_provinces():
  99. '''
  100. 抓取所有省份
  101. '''
  102. html = get_admin_area_html(HOME_URL + "/index.html")
  103. province_nodes = html.xpath('//*/tr[@class="provincetr"]/td/a')
  104. grabed_count = 0
  105. for province_node in province_nodes:
  106. grabed_count += 1
  107. province_city_link = HOME_URL + "/" + province_node.attrib["href"]
  108. province_code = province_node.attrib["href"][0:2] + '0000000000'
  109. province_name = province_node.text.strip()
  110. global current_province_name
  111. current_province_name = province_name
  112. print_info("province_code={}, province_name={}".format(province_code, province_name))
  113. insert_area_to_mysql(province_code, province_name, 1, None)
  114. if MAX_GRAB_LEVEL >= 2:
  115. grab_province_cities(province_city_link, province_code, province_name)
  116. if MAX_GRAB_COUNT > 0 and grabed_count >= MAX_GRAB_COUNT:
  117. break
  118. def grab_province_cities(province_city_link:str, province_code:str, province_name:str):
  119. '''
  120. 抓取单个省/直辖市下的城市/区县
  121. 参数:
  122. province_city_link(str): 省/直辖市区域页面的完整 url
  123. province_code(str): 城市所属的省份编码
  124. province_name(str): 城市所属的省份名称
  125. '''
  126. print("开始抓取省份({})的城市列表, URL={}".format(province_name, province_city_link))
  127. html = get_admin_area_html(province_city_link)
  128. if html is None:
  129. print("抓取省份({})的城市列表失败".format(province_name))
  130. return
  131. cityNodes = html.xpath('//*/tr[@class="citytr"]')
  132. grabed_count = 0
  133. global current_city_name
  134. for cityNode in cityNodes:
  135. link_nodes = cityNode.xpath('./*/a')
  136. city_code = link_nodes[0].text
  137. city_name = link_nodes[1].text.strip()
  138. current_city_name = city_name
  139. insert_area_to_mysql(city_code, city_name, 2, province_code)
  140. print_info("city_code={}, city_name={}".format(city_code, city_name))
  141. if MAX_GRAB_LEVEL >= 3 and link_nodes[1].attrib.has_key("href"):
  142. county_link = province_city_link[0:province_city_link.rfind('/')] + "/" + link_nodes[1].attrib["href"]
  143. grap_city_couties(county_link, city_code, city_name)
  144. # 以城市为最小提交单位
  145. commit_for_mysql()
  146. if MAX_GRAB_COUNT > 0 and grabed_count >= MAX_GRAB_COUNT:
  147. break
  148. def grap_city_couties(city_county_link:str, city_code:str, city_name:str):
  149. '''
  150. 抓取单个城市下的区/县
  151. 参数:
  152. city_county_link(str): 城市区/县页面的完整 url
  153. city_code(str): 城市的编码
  154. city_name(str): 城市的名称
  155. '''
  156. print("开始抓取城市({})的区/县列表, URL={}".format(city_name, city_county_link))
  157. html = get_admin_area_html(city_county_link)
  158. if html is None:
  159. print("抓取城市({})的区/县列表失败".format(city_name))
  160. return
  161. county_nodes = html.xpath('//*/tr[@class="countytr"]')
  162. grabed_count = 0
  163. global current_province_name
  164. for county_node in county_nodes:
  165. grabed_count += 1
  166. county_link_nodes = county_node.xpath("./*/a")
  167. if len(county_link_nodes) == 0:
  168. # 没有<a>标签,通常是直辖市的市辖区,内容抓取方式不同
  169. county_code = county_node.xpath("./td")[0].text
  170. county_name = county_node.xpath("./td")[1].text
  171. insert_area_to_mysql(county_code, county_name, 3, city_code)
  172. print_info("county_code={}, county_name={}, parent_code={}".format(county_code, county_name, city_code))
  173. else:
  174. county_code = county_link_nodes[0].text
  175. county_name = county_link_nodes[1].text
  176. insert_area_to_mysql(county_code, county_name, 3, city_code)
  177. print_info("county_code={}, county_name={}, level=2, parent_code = {}".format(county_code, county_name, city_code))
  178. if MAX_GRAB_LEVEL >= 4 and county_link_nodes[1].attrib.has_key("href"):
  179. town_link = city_county_link[0:city_county_link.rfind("/")] + "/" + county_link_nodes[1].attrib["href"]
  180. grap_county_towns(town_link, county_code, county_name)
  181. if MAX_GRAB_COUNT > 0 and grabed_count >= MAX_GRAB_COUNT:
  182. break
  183. def grap_county_towns(county_town_link:str, county_code:str, county_name:str):
  184. '''
  185. 抓取单个区/县下的乡镇/街道
  186. 参数:
  187. county_town_link(str): 乡镇/街道数据页面完整的 url
  188. county_code(str): 区/县的编码
  189. county_name(str): 区/县的名称
  190. '''
  191. print("开始抓取区县({})的街道/乡镇列表, URL={}".format(county_name, county_town_link))
  192. html = get_admin_area_html(county_town_link)
  193. if html is None:
  194. print("抓取区县({})的街道/乡镇列表失败".format(county_name))
  195. return
  196. town_nodes = html.xpath('//*/tr[@class="towntr"]')
  197. grabed_count = 0
  198. for town_node in town_nodes:
  199. grabed_count += 1
  200. village_link_nodes = town_node.xpath('./*/a')
  201. town_code = village_link_nodes[0].text
  202. town_name = village_link_nodes[1].text
  203. print_info("town_code={}, town_name={}".format(town_code, town_name))
  204. insert_area_to_mysql(town_code, town_name, 4, county_code)
  205. if MAX_GRAB_LEVEL >= 5 and village_link_nodes[1].attrib.has_key("href"):
  206. village_link = county_town_link[0:county_town_link.rfind("/")] + "/" + village_link_nodes[1].attrib["href"]
  207. grap_town_villages(village_link, town_code, town_name)
  208. if MAX_GRAB_COUNT > 0 and grabed_count >= MAX_GRAB_COUNT:
  209. break
  210. def grap_town_villages(town_village_url:str, town_code:str, town_name:str):
  211. '''
  212. 抓取单个街道/乡镇下的社区/村委会
  213. 参数:
  214. town_village_url(str): 社区/村委会数据页面完整的 url
  215. town_code(str): 街道/乡镇的编码
  216. town_name(str): 街道/乡镇的名称
  217. '''
  218. print_info("开始抓取街道/乡镇下({})的社区/村委会列表, URL={}".format(town_name, town_village_url))
  219. html = get_admin_area_html(town_village_url)
  220. if html is None:
  221. print("抓取街道/乡镇下({})的社区/村委会列表失败".format(town_name))
  222. return
  223. village_nodes = html.xpath('//*/tr[@class="villagetr"]')
  224. grabed_count = 0
  225. for village_node in village_nodes:
  226. grabed_count += 1
  227. village_info_columns = village_node.xpath('./td')
  228. village_code = village_info_columns[0].text
  229. village_name = village_info_columns[2].text
  230. insert_area_to_mysql(village_code, village_name, 5, town_code)
  231. print_info("village_code={}, village_code={}".format(village_code, village_name))
  232. if MAX_GRAB_COUNT > 0 and grabed_count >= MAX_GRAB_COUNT:
  233. break
  234. # 正式执行数据抓取任务
  235. grap_all_provinces()
  236. db.close()

如何运行

  1. 下载 Window版Python 的安装程序,并在本机安装

    如果你打算在 Linux 下运行这个程序,你也可以直接下载 Linux 版本的 python,但我还没在 Linux 环境下验证过这个脚本

  2. 打开命令行窗口,依次执行以下脚本,以安装本脚本的依赖库

    1. pip install requests
    2. pip install lxml
    3. pip install pymysql
  3. 安装 MySql 服务器并创建好相应的表

    你需要在本机上安装 MySql 数据库,并创建用于存储区划码数据的表。建表语句如下:

    1. CREATE TABLE `admin_area_2023` (
    2. `code` char(12) NOT NULL COMMENT '区域编码',
    3. `name` varchar(60) NOT NULL COMMENT '区域名称',
    4. `level` tinyint(4) NOT NULL COMMENT '区域等级:\r\n1 : 省/直辖市\r\n2 : 市\r\n3 : 区/县\r\n4 : 乡镇/街道\r\n5 : 社区/村委会',
    5. `parent_code` char(12) DEFAULT NULL COMMENT '父级区域编码',
    6. PRIMARY KEY (`code`)
    7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  4. 根据实际情况修改脚本

    一般来说,你需要修改以下几项参数

    • 要抓取哪一年的数据。搜索 HOME_URL = 即可找到代码所在行

    • 连接 MySql 的用户名、密码、端口、数据库名称。搜索 pymysql.connect 即可找到代码所在行

    • 修改SQL语句,设置要插入的表名。搜索 insert into 即可找到代码所在行

    • 设置要抓取的数据等级,默认为4级。搜索 MAX_GRAB_LEVEL 即可找到代码所在行

    还支持一些其它的冷门设置,就请自行阅读源码吧。

  5. 运行脚本

    假设本机的 python 脚本命名为 admin-area-data-spider.py, 且位于 D 盘根目录,则执行以下命令运行程序:

    1. python d:\admin-area-data-spider.py

这是我花了一上午时间,利用网友分享的 python 知识,临时编写的脚本。但由于之前从没有接触过 python,因此代码质量无法保障,请各位老鸟见量。可以确保的是,它当前在 windows 下是可以工作的。

不过通过这次临时的 python 体验后,非常喜欢这门语言,用它来快速开发各种工具和快速构建原型项目,以验证业务可行性是两个很不错的应用领域。当然,它当前在科学计算和人工智能领域的应用更广泛。

工程源码

上面已经贴出了数据抓取的 python 代码,但后期我可能还会修改,比如:

  • 拆分成多个 module
  • 添加抓取统计功能
  • 参数化脚本的执行

故这里再附上 行政区划码抓取的Gitee工程 地址,感兴趣的朋友可 watch、star、fork

原文链接:https://www.cnblogs.com/guzb/p/18064214/free-download-administrative-area-code-for-each-year

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号