1. 目录

  • 1. 目录
  • 2. 背景
  • 3. 设计
    • 3.1. 分析
    • 3.2. 设计
  • 4. 环境
  • 5. 实现
    • 5.1. 核心
    • 5.2. 定义实体
    • 5.3. 数据库操作

2. 背景

很多地方需要用到 解析地理区域 这块数据,但是人家是一个网页。

2016 年我用 Java 实现了一版,感觉使用起来不是很方便,后来又在 2020 年 用 Python 又实现了一次,代码量明显少了很大一部分。

3. 设计

3.1. 分析

地理区域的数据结构比较经典,完全是树形数据结构,随着树的深度增大,数据增长在10倍左右。

  • 根节点的数量是 1
  • 省/直辖市 数量为 33
  • 城市 354
  • 区县 3331

3.2. 设计

  • 在实现上,使用了 Pythonpymysql 的类库。

  • 在数据存储上,利用了数据库,在数据库选择上,本着简单易用的原则选择了 MySQL ,数据库表设计如下:


CREATE TABLE areas (  code VARCHAR(30) DEFAULT NULL,name VARCHAR(100) DEFAULT NULL,lv INT(11) DEFAULT NULL,sup_code VARCHAR(30) DEFAULT NULL,flag VARCHAR(6) DEFAULT NULL,url VARCHAR(60) DEFAULT NULL
) ;
  • 因为涉及到对数据库的操作,我这里按照设计思想,对数据库的操作,都封装在一个 DbUtil.py
  • 数据大的过程中,需要考虑分页

4. 环境

  • Pycharm 2021.2.2
  • Python 3.10
  • Anaconda3
插件 说明
pymysql
BeautifulSoup

通过 Python Interpreter 安装 或者 pip 安装都可以,自行视实际情况自行选择

5. 实现

AreaMa.py 中,依次执行 init_province()init_city()init_county()init_town()init_village()

  • init_province(): 第一级 省|直辖市
  • init_city(): 第二级 地级市
  • init_county(): 第三级 区县
  • init_town(): 第四级 乡镇街道
  • init_village(): 第四级 自然村/社区

5.1. 核心


import urllib.requestfrom bs4 import BeautifulSoup
from sip.Area import Area
from util.DbUtil import *G_HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'}G_URL = r'http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/index.html'
""" 获取URL前缀 """
G_URL_PREFIX = G_URL[0:G_URL.rindex('/') + 1]'''
爬取国家统计局网站中 关于行政区划的数据,按照层级进行编辑,最终形成一个树形结构。
包含:省/直辖市/自治区、地级市、区县、乡/镇/街道、居委会/村'''def main():""" 第一级 省|直辖市 """# init_province()""" 第二级 地级市 """# init_city()""" 第三级 区县 """# init_county()""" 第四级 乡镇街道 """init_town()""" 第四级 自然村/社区 """# init_village()if __name__ == '__main__':main()""" Init 执行入口,按照 1、2、3层级顺序执行 """def get_by_lv(lv):with UsingMysql(commit=True) as um:um.cursor.execute(f'select code,name,lv,sup_code,url from areas where lv=%s', lv)data = um.cursor.fetchall()return data""" 由于街道数量量比较大,所以要分页处理 """def init_village():do_init_village(4)def do_init_village(lv):pag = UsingMysql(numPerPage=20)sql = r'select code,name,lv,sup_code,url from areas where lv=%s'param = lvfor ret in pag.queryForList(sql, param):paramsList = DataToJson(ret)get_village(paramsList)""" 由于街道数量量比较大,所以要分页处理 """def init_town():do_init_town(3)# print(get_by_lv(1))'''
code,name,lv,sup_code,url
'''def DataToJson(data):jsonData = []for row in data:result = {}result['code'] = row[0]result['name'] = row[1]result['lv'] = row[2]result['sup_code'] = row[3]result['url'] = row[4]jsonData.append(result)return jsonDatadef do_init_town(lv):pag = UsingMysql(numPerPage=20)sql = r'select code,name,lv,sup_code,url from areas where lv=%s'param = lvfor ret in pag.queryForList(sql, param):paramsList = DataToJson(ret)# print(len(paramsList))get_town(paramsList)'''
批量插入数据库
'''def batch_insert(_list):with UsingMysql(commit=True) as um:um.cursor.executemany(""" INSERT INTO areas (code,name,lv,sup_code,url) VALUES (%s,%s,%s,%s,%s)""",_list)def splicing(paramsList):_list = list()for el in paramsList:_list.append((el.code, el.name, el.lv, el.sup_code, el.url))batch_insert(_list)'''
数据字典,维护 层级对应的 DIV标签
'''
G_LV_DICT = {1: 'provincetr', 2: 'citytr', 3: 'countytr', 4: 'towntr', 5: 'villagetr'}'''
核心方法 通用处理方法,封装处理解析逻辑
'''def commons(_list, lv):all_area = []'''利用父级来遍历子级'''for lp in _list:_url = lp['url']if _url is None or len(_url) == 0:continueelse:req = urllib.request.Request(url=get_to_url(_url, lp['code'], lv), headers=G_HEADERS)res = urllib.request.urlopen(req)html = res.read()soup = BeautifulSoup(html, 'html.parser', from_encoding='gb2312')all_tr = soup.find_all('tr', attrs={'class': G_LV_DICT[lv]}, limit=30)for row in all_tr:if lv == 5:""" 社区的元素发生变更,需要特殊处理"""a_ary = row.find_all('td')all_area.append(Area(None, a_ary[2].get_text(), a_ary[0].get_text(), lp['code'], lv))else:a_ary = row.find_all('a')if len(a_ary):all_area.append(Area(a_ary[0].get('href'), a_ary[1].get_text(), a_ary[0].get_text(), lp['code'], lv))else:aa_ary = row.find_all('td')all_area.append(Area(None, aa_ary[1].get_text(), aa_ary[0].get_text(), lp['code'], lv))'''每次批量写入'''splicing(all_area)'''写完后 置空'''all_area = []'''
性化处理URL地址,并根据层级获取对应URL
'''def get_to_url(url, code, lv):urs = {2: G_URL_PREFIX + url,3: G_URL_PREFIX + url,4: G_URL_PREFIX + code[0:2] + '/' + url,5: G_URL_PREFIX + code[0:2] + '/' + code[2:4] + '/' + url,}return urs.get(lv, None)'''
省/自治区
'''def init_province():all_area = []req = urllib.request.Request(url=G_URL, headers=G_HEADERS)res = urllib.request.urlopen(req)html = res.read()soup = BeautifulSoup(html, 'html.parser', from_encoding='gb2312')all_tr = soup.find_all('tr', attrs={'class': 'provincetr'}, limit=10)for row in all_tr:for r_td in row.find_all('a'):ars = Area(r_td.get('href'), r_td.get_text(), r_td.get('href')[0:2], '0', 1)all_area.append(ars)splicing(all_area)'''
市
'''def init_city():_list = get_by_lv(1)commons(_list, 2)'''
区县
'''def init_county():_list = get_by_lv(2)commons(_list, 3)'''
街道
'''def get_town(paramsList):return commons(paramsList, 4)'''
居委会
'''def get_village(paramsList):return commons(paramsList, 5)

5.2. 定义实体

名称 说明
name 名称
code 编码
url 地址
sup_code 上级编码
lv 层级

class Area:"Area class 行政区域实例"def __init__(self, url, name, code, sup_code, lv):self.name = nameself.code = codeself.url = urlself.sup_code = sup_codeself.lv = lvdef __str__(self) -> str:return 'URL:%s\t  NAME:%s\t  Code:%s\t SUPER_CODE:%s\t LV:%s\t' % (self.url, self.name, self.code, self.sup_code, self.lv)

5.3. 数据库操作


import pymysql
import math""" 用pymysql 操作数据库 """def get_connection():host = '127.0.0.1'port = 13306db = 'area'user = 'root'password = 'xxx'conn = pymysql.connect(host=host, port=port, db=db, user=user, password=password)return conndef initClientEncode(conn):'''mysql client encoding=utf8'''curs = conn.cursor()curs.execute("SET NAMES utf8")conn.commit()return curs""" 使用 with 的方式来优化代码 """class UsingMysql(object):def __init__(self, commit=True, log_label=' In total', numPerPage=20):""":param commit: 是否在最后提交事务(设置为False的时候方便单元测试):param log_label:  自定义log的文字"""self._commit = commitself._log_label = log_labelself.numPerPage = numPerPagedef queryForList(self, sql, param=None):totalPageNum = self.__calTotalPages(sql, param)for pageIndex in range(totalPageNum):yield self.__queryEachPage(sql, pageIndex, param)def __createPaginaionQuerySql(self, sql, currentPageIndex):startIndex = self.__calStartIndex(currentPageIndex)qSql = r'select * from (%s) total_table limit %s,%s' % (sql, startIndex, self.numPerPage)return qSqldef __queryEachPage(self, sql, currentPageIndex, param=None):curs = initClientEncode(get_connection())qSql = self.__createPaginaionQuerySql(sql, currentPageIndex)if param is None:curs.execute(qSql)else:curs.execute(qSql, param)result = curs.fetchall()curs.close()return resultdef __calTotalRowsNum(self, sql, param=None):''' 计算总行数 '''tSql = r'select count(*) from (%s) total_table' % sqlcurs = initClientEncode(get_connection())if param is None:curs.execute(tSql)else:curs.execute(tSql, param)result = curs.fetchone()curs.close()totalRowsNum = 0if result != None:totalRowsNum = int(result[0])return totalRowsNumdef __calTotalPages(self, sql, param):''' 计算总页数 '''totalRowsNum = self.__calTotalRowsNum(sql, param)totalPages = 0tempTotal = totalRowsNum / self.numPerPageif (totalRowsNum % self.numPerPage) == 0:totalPages = tempTotalelse:totalPages = math.ceil(tempTotal)return totalPagesdef __calStartIndex(self, currentPageIndex):startIndex = currentPageIndex * self.numPerPagereturn startIndex;def __calLastIndex(self, totalRows, totalPages, currentPageIndex):'''计算结束时候的索引'''lastIndex = 0if totalRows < self.numPerPage:lastIndex = totalRowselif ((totalRows % self.numPerPage == 0)or (totalRows % self.numPerPage != 0 and currentPageIndex < totalPages)):lastIndex = currentPageIndex * self.numPerPageelif (totalRows % self.numPerPage != 0 and currentPageIndex == totalPages):  # 最后一页lastIndex = totalRowsreturn lastIndexdef __enter__(self):""" 在进入的时候自动获取连接和cursor """conn = get_connection()cursor = conn.cursor(pymysql.cursors.DictCursor)conn.autocommit = Falseself._conn = connself._cursor = cursorreturn selfdef __exit__(self, *exc_info):""" 提交事务 """if self._commit:self._conn.commit()""" 在退出的时候自动关闭连接和cursor """self._cursor.close()self._conn.close()@propertydef cursor(self):return self._cursor

Python-20:解析行政区域Python版相关推荐

  1. 【python PDF解析】python 读取PDF文件内容

    一.问题描述 利用python,去读取pdf文本内容. 二.效果 三.运行环境 python2.7 四.需要安装的库 pip install pdfminer 五.实现源代码 代码1(win64) # ...

  2. python分片操作_【python原理解析】python中分片的实现原理及使用技巧

    首先:说明什么是序列? 序列中的每一个元素都会被分配一个序号,即元素的位置,也称为索引:在python中的序列包含:字符串.列表和元组 然后是:什么是分片? 分片就是通过操作索引访问及获得序列的一个或 ...

  3. 了解女友的心还不如了解Python之在Python中解析和修改XML

    2021年12月15日 10:14 ·  阅读 30 摘要: 工作中我们时常需要解析用不同语言编写的数据.Python 提供了许多库来解析或拆分用其他语言编写的数据.在这篇 Python XML 解析 ...

  4. python列表解析的新方法

    python 列表解析我感觉是python非常灵活的一个地方,一开始接触它的时候,特别是之前学过其它的语言, 你会感觉很不习惯,怎么看怎么不对劲,老是觉的哪个地方怪怪的,这就是列表解析的魔力所在. p ...

  5. 面试官问我:如何在 Python 中解析和修改 XML

    摘要:我们经常需要解析用不同语言编写的数据.Python提供了许多库来解析或拆分用其他语言编写的数据.在此 Python XML 解析器教程中,您将学习如何使用 Python 解析 XML. 本文分享 ...

  6. python html解析_Python HTML解析器

    python html解析 Python html.parser module provides us with the HTMLParser class, which can be sub-clas ...

  7. Python 常见的 170 道面试题全解析:2022 版

    Python 常见的 170 道面试题全解析:2019 版 语言特性 1.谈谈对 Python 和其他语言的区别 答:Python 是一门语法简洁优美,功能强大无比,应用领域非常广泛,具有强大完备的第 ...

  8. 时间语义解析工具 Python版,从文本中提取时间,并解析其含义,在线使用,时间语义识别

    时常我们需要从文本中,提取出时间信息,并将这个信息标准化,例如: [新华社报2021-9-9]国家统计局今天发布了2021年8月份全国CPI(居民消费价格指数) 需要从中抽取出 2021-9-9 和 ...

  9. python爬取图片全网通_荣耀20 PRO现货发售 华为官方解析何为Python爬虫

    荣耀20 PRO现货发售 华为官方解析何为Python爬虫 2019-07-08 10:51:56 0点赞 1收藏 0评论 7月8日,荣耀手机官微宣布,荣耀20 PRO现已全面开放购.荣耀20 PRO ...

最新文章

  1. 《2018-2019全球IPv6支持度白皮书》发布,江北新区IPv6示范区建设正式启动
  2. 像证券交易员一样思考和行动_3纪律与心态
  3. linux c语言乘法口诀,shell 脚本实现乘法口诀表的两种方法——shell与C语言
  4. stm32 定时器_如何计算STM32定时器、独立看门狗和窗口看门狗
  5. 用elemet-ui组件实现弹窗里的树形结构和拖拽功能
  6. OpenShift 4 - 镜像漏洞扫描软件 Clair
  7. qt 二次开发 研华daq_研华DAQ数据采集卡编程
  8. 在线编程JavaScript
  9. java基础代码-实现键盘输入
  10. Ubuntu系统基本操作
  11. [营销]浅谈如何提高网站PR值
  12. 将.fits数据转换为.png图像
  13. 企业微信开发(一)常见问题收集及解决方案
  14. “云适配”获1亿元B+轮融资,盯上了大企业的移动化需求
  15. 紫薯第9章动态规划,从入门到入土, dp 它tnl(背包代码模板部分)
  16. 杰理AC693N介绍
  17. 论 致命错误c0000005
  18. 服务器改为电脑要修改什么,怎么样更改电脑服务器名
  19. 负数的二进制,原,反,补
  20. 【2019年05月21日】A股ROE最高排名

热门文章

  1. Day 4 Data Flow Analysis-Foundations
  2. (附源码)springboot高校党建信息管理系统 毕业设计 051541
  3. U-Boot的移植U-Boot Practically Porting Guide(转)
  4. Eclipse安装阿里巴巴代码规范插件p3c
  5. python自动获取163邮箱验证码
  6. 游戏可以教给我们什么?
  7. SimpleITK使用
  8. 即时通讯:“漂流瓶”功能下线,竟然是这种原因!
  9. 【论文精读】Deep Marching Cubes: Learning Explicit Surface Representations
  10. XC6SLX100-3FGG484C规格、XC7A15T-2CPG236I产品概述及应用