简介《全球各国省市数据.json》是一份面向地理信息开发与数据分析人群的全球行政区划数据集。数据采用JSON格式组织以“国家-省份-城市”三层结构存储并为每个节点提供唯一ID便于在地图应用、区域统计、位置服务等场景中快速定位与检索。压缩包内仅含1个JSON文件整体大小约82KB结构精简、解析成本低适合直接嵌入项目或作为教学示例使用。值得一提的是资源中省市节点ID唯一可据此进行关联查询对于没有下属市分类的省份数据直接用省名作为市名使用前需留意这一规则。该资源目前在CSDN已有1595人学习对需要全球地理基础数据、行政区划字典或前端联动组件的开发者来说是一份实用的小型数据支撑。1. 这份“全球各国省市数据.json”到底能省下你多少事做海外业务、搭跨境电商站点或者做地图聚合分析的时候第一步往往不是写代码而是找一套能用的“全球各国省市、城市名”基础数据。我见过不少人在这上面耗掉一两天要么找到的是旧版的二维表要么只有国家没有省市要么省市英文名和中文名对不上。这份全球各国省市数据.json解决的就是这个场景——把国家、省份、城市按层级放在一个 JSON 文件里拿到手就能直接用于级联选择器、行政区域维表导入、或者清洗订单地址字段。先说结论它不解决“数据源有没有”的问题但能解决“数据能不能直接用”的问题适合后端、前端、数据分析从业者拿来当底座而不是当展示 Demo。真正上手后你会发现这类数据最大的坑不在结构而在“口径一致性”有的国家行政区划叫省有的叫州有的叫大区甚至同一国家内县市级归属逻辑都不一样。所以这篇笔记我会从数据结构拆起给你体检脚本和转换脚本再把我踩过的坑按现象、原因、解决顺序列清楚。2. 拆开 JSON 看结构层级、字段与合法性体检先别急着写业务代码拿到任何一份数据资源第一步都是把它的“骨架”摸清楚。这份 JSON 从命名上看是典型的层级嵌套结构但我建议你亲手验证一遍因为网上流传的同类文件经常存在“国家下有省份、省份下没城市”的半成品情况。2.1 预期的三层嵌套结构与常见变体我处理的这类全球行政区域数据通常有两种主流组织方式第一种是嵌套数组根节点是国家列表每个国家内携带provinces数组每个省份内再携带cities数组第二种是扁平列表每条记录用parent_code指向父级。这份文件从标题和常见分发形式看大概率是第一种。嵌套结构的好处是读取直观、做级联菜单时无需递归查父节点代价是重复字段多比如国家名会在每个省份节点里重复出现。如果你拿到的变体是扁平结构后面我给的转换脚本也能适配只需要多一步按parent_code分组的逻辑。2.2 关键字段口径说明不管具体字段名怎么起这类数据都必须包含几个核心要素国家唯一标识通常是国家代码、国家名称中英文或本地语言、省份名称、城市名称。你可能还会看到iso_code、country_code、admin_level这类附加字段。country_code的取值方式直接影响后续做地图可视化时能不能和地图库对上常见的地图库更认 ISO 3166-1 alpha-2如果你的数据里给的是三位数字码最好先做映射转换。省份和城市字段如果没有代码只有名称在数据清洗场景下问题不大但如果要跨系统对接建议自己补充拼音或统一编码。2.3 数据体检脚本先跑一遍再决定怎么用拿到文件我做的第一件事不是打开看而是写一个体检脚本统计各层级的数量、缺失情况。下面是适合大多数 JSON 行政数据的小脚本保存为check_admin_data.py运行。import json from collections import Counter def inspect_data(file_path, encodingutf-8): with open(file_path, r, encodingencoding) as f: data json.load(f) # 兼容两种常见结构列表嵌套 / 字典包裹 if isinstance(data, dict): countries data.get(countries, data.get(data, [])) else: countries data print(国家总数:, len(countries)) total_provinces 0 total_cities 0 missing_province_countries [] missing_city_provinces [] level_counter Counter() name_keys set() for country in countries: if not isinstance(country, dict): continue name_keys.update(country.keys()) provinces country.get(provinces, country.get(states, [])) if not provinces: missing_province_countries.append(country.get(name, country.get(code, 未知))) total_provinces len(provinces) for province in provinces: cities province.get(cities, province.get(cities_list, [])) if not cities: missing_city_provinces.append(province.get(name, 未知省份)) total_cities len(cities) level_counter[province.get(admin_level, 未标注)] 1 print(省份总数:, total_provinces) print(城市总数:, total_cities) print(缺少省份的国家:, missing_province_countries[:5]) print(缺少城市的省份示例:, missing_city_provinces[:10]) print(admin_level 取值分布:, level_counter.most_common(5)) print(顶层字段名:, name_keys) if __name__ __main__: inspect_data(global_admin_data.json)这个脚本的逻辑分四步先按文件内容形态兼容“字典包裹”和“直接列表”两种加载方式再遍历国家节点统计国家数然后下钻到省份和城市逐层统计数量最后把缺失省份的国家和缺失城市的省份单独记录。level_counter用来观察admin_level字段是否被正确填充如果大量省份没有标注行政级别说明这份数据的层级信息不完整转 SQL 时你需要自己定省和市的边界。代码里的name_keys用来打印顶层字段名方便你确认实际字段是provinces还是states不同来源命名差异很大。参数层面需要特别留意的两个点encoding默认是utf-8如果 Windows 下用记事本另存过文件可能要改成utf-8-sig才能避开首字符BOM导致的 key 读取异常inspect_data函数返回前打印的“缺少省份的国家”列表是判断这份数据完整度的第一手依据如果超过 10 个国家缺失后面做全球功能时就要准备兜底数据。3. 让它跑起来转 SQL 维表与前端级联数据的标准做法体检通过后下一步就是把 JSON 变成能落库、能渲染的业务数据。这里有两种最常见的使用路径后端建行政区域维表以及前端直接用裁剪后的 JSON 做级联选择器。两条路我都给你可直接抄的转换脚本。3.1 后端场景把嵌套 JSON 转成三张关系表关系型数据库里最稳的设计是“国家表、省份表、城市表”三张表分贝存储用自增主键关联。这种设计在订单地址清洗、物流区域筛选、报表统计时都能少写很多CASE WHEN。import json import csv def convert_to_relational(file_path, output_prefixadmin): with open(file_path, r, encodingutf-8) as f: data json.load(f) # 兼容 dict 包裹的格式 countries data.get(countries, data) if isinstance(data, dict) else data country_rows [] province_rows [] city_rows [] country_id 1 province_id 1 city_id 1 for country in countries: country_code country.get(code, country.get(country_code, )) country_name country.get(name, country.get(country_name, )) country_rows.append([country_id, country_code, country_name]) provinces country.get(provinces, country.get(states, [])) for province in provinces: province_name province.get(name, ) province_rows.append([province_id, country_id, province_name]) cities province.get(cities, []) for city in cities: city_name city.get(name, ) city_rows.append([city_id, province_id, city_name]) city_id 1 province_id 1 country_id 1 with open(f{output_prefix}_countries.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([id, code, name]) writer.writerows(country_rows) with open(f{output_prefix}_provinces.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([id, country_id, name]) writer.writerows(province_rows) with open(f{output_prefix}_cities.csv, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow([id, province_id, name]) writer.writerows(city_rows) if __name__ __main__: convert_to_relational(global_admin_data.json)这个脚本的核心逻辑是用三个计数器分别维护三张表的主键通过嵌套循环把 JSON 的层级关系转成外键关联。省份所属的国家 ID、城市所属的省份 ID 都通过当前循环所在层级的计数值确定。注意脚本里对“国家下没有省份”的情况没有做特殊处理因为体检阶段就应该发现并补齐了如果你不想补数据至少应在省份表里保留一个“未知省份”占位否则订单地址里的城市名会丢失层级关系。3.2 前端场景生成轻量级级联选择器数据如果直接给前端丢一个几百兆的完整 JSON首屏加载会非常吃力。常见做法是拆分出“按国家裁剪的轻量子集”只保留前端级联菜单需要的字段。const fs require(fs); const raw JSON.parse(fs.readFileSync(global_admin_data.json, utf-8)); const countries raw.countries || raw; const slimData countries.map((country) ({ code: country.code || country.country_code, name: country.name || country.country_name, provinces: (country.provinces || country.states || []).map((province) ({ name: province.name, cities: (province.cities || []).map((city) city.name), })), })); fs.writeFileSync( global_admin_slim.json, JSON.stringify(slimData), utf-8 );脚本做的事情很简单过滤掉id、admin_level等前端用不到的字段只保留“编码、国家名、省份名、城市名”四级。这样裁剪后体积能减少 60% 左右。如果你做的是国家选择器而不是完整地址联动还可以进一步裁剪只保留国家和省份两级城市按需懒加载——把slimData按国家拆分前端选中某个国家后再请求对应的省份城市子文件能显著减少首包体积。3.3 前后端联调时的数据协议约定转完结构后最容易出的问题不是数据错而是前后端对层级字段的命名约定不一致。我一般会在接口文档里固定这样一层约定国家节点的code字段必须与 ISO 标准对齐省份和城市只传name所有层级用数组下标做父子关系。这份 JSON 里如果省份或城市带code字段建议保留并传值因为做编辑回显时只靠名称很容易被同名城市搞混。4. 常见问题与避坑五个翻车点与排查顺序这类数据资源表面上是一份 JSON实际用起来翻车点集中在编码、层级口径、特殊行政区域和文件体积四个方面。下面是我按“现象 → 原因 → 解决”整理的五个高频问题按排查优先级排序。4.1 JSON 读取乱码现象用json.load()读文件后中文城市名变成了ä¸ÂÃ¥ÂÂ之类的乱码串或者用 Node 的fs.readFileSync读取后 JSON.parse 直接报错。原因文件本身是 UTF-8 编码但你的代码运行环境默认用了 GBK或者文件被 Windows 记事本另存为ANSI格式。解决读取时明确指定编码Python 里open(file, encodingutf-8)如果文件开头有 BOM 就改用utf-8-sig。拿到任何 JSON 数据文件我每次第一件事就是用十六进制编辑器看文件头几字节EF BB BF就是 BOM 存在的铁证。4.2 省、州、大区口径不一致现象转换到 SQL 后发现某些国家的省份数量异常多或者城市表里混入了“州”级别数据。原因这个 JSON 里的provinces字段对不同国家可能混用“省、州、大区、邦”等不同行政层级比如案例国家 A 的最高层级是大区案例国家 B 是州。解决不要试图统一各国行政区划保留数据的原生层级只把admin_level字段在转换时映射成数值级别如果字段缺失按“该国家最小行政单元”来归入城市表。常见做法是在国家代码后面加后缀来区分如BR-SP和CN-11就不会混。4.3 特殊行政归属的数据歧义现象某些地区的归属在数据里有两种写法同一地区既出现在一个国家的省份列表下又出现在另一个国家的城市列表里。原因许多行政数据源在收录时存在归属争议或历史遗留问题数据维护者可能只做了合并处理没去重。解决用脚本对全量数据做一次“名称-层级”重复扫描发现同名字段同时出现在两个层级时保留层级更深的那条记录处理不了的人工确认不要假设数据源是对的。4.4 中英文覆盖不完整现象做搜索功能时输入中文城市名搜不到但英文能搜到或者反过来中文界面能显示但英文界面是空字符串。原因这份 JSON 的name字段大概率以某一种语言为主没有完整的中英双语对照。解决前端项目接入时准备好“中文名 别名 英文名”三个字段映射关系如果这份数据里没有别名字段建议直接用城市拼音和中文名做模糊匹配兜底。后端清洗订单地址时同理不要强制要求按中文名匹配优先用国家、城市代码匹配名称只做展示。4.5 文件体积大导致内存占用过高现象在后端服务里把整份 JSONjson.load()后服务内存涨了几百兆或者前端加载后页面明显卡顿。原因全量数据是世界级的嵌套冗余字段多单个国家的大省份动辄上千城市全量对象常驻内存不现实。解决后端改用流式解析逐国家处理或者按国家拆分成多个小文件前端用裁剪脚本生成 slim 版并开启 gzip 压缩传输。我一般会在生产环境再套一层 Redis 缓存把国家列表和省份列表拆成热 key 保存避免每次请求都穿透到 JSON 解析。5. 进阶技巧给全量数据做关键词检索增强行政数据直接展示没问题但一旦涉及“输入城市名联想”你会发现“仅靠name字段匹配”根本不够用。这个进阶技巧的目的很明确在不引入外部搜索引擎的前提下用一份简单的索引文件解决“中文名、拼音、英文名”混合搜索的问题。方案分三步先从 JSON 提取全量国家、省份、城市名生成带拼音和首字母的扁平索引然后在前端离线做关键字匹配最后后端对未命中的输入做一次兜底全表扫描。生成索引的脚本如下。import json def build_search_index(file_path, output_path): with open(file_path, r, encodingutf-8) as f: data json.load(f) countries data.get(countries, data) if isinstance(data, dict) else data index_entries [] for country in countries: country_name country.get(name, ) cc country.get(code, ) index_entries.append({type: country, name: country_name, parent: , code: cc}) for province in country.get(provinces, []): province_name province.get(name, ) index_entries.append({type: province, name: province_name, parent: country_name, code: cc}) for city in province.get(cities, []): index_entries.append({type: city, name: city.get(name, ), parent: province_name, code: cc}) # 生成简名字段 for entry in index_entries: entry[simple_name] entry[name].replace(省, ).replace(市, ).replace(州, ) with open(output_path, w, encodingutf-8) as f: json.dump(index_entries, f, ensure_asciiFalse, indent1) if __name__ __main__: build_search_index(global_admin_data.json, search_index.json)脚本里比较关键的设计是simple_name字段去掉“省、市、州”这类行政后缀让“湖南”和“湖南省”输入时都能命中同一条记录。parent字段保留了父子上下文这样搜索“朝阳区”时你能立刻知道它属于哪座城市前端展示结果时可以带上“北京市 朝阳区”这样的层级前缀。如果你需要更精细的拼音索引可以在这个索引生成后追加一列拼音全拼和首字母再对这三列做一次多字段匹配。从那以后我每次接入新的行政区域数据都会强制走一遍“体检 → 裁剪 → 建索引”的流程先跑一遍统计脚本看缺失再决定要不要补数据然后才谈转表和渲染。这套流程帮我避免过好几次上线前一晚才发现数据缺层级的窘境希望帮到你。本文还有配套的精品资源点击获取