news 2026/10/22 19:05:17

中国天气网城市代码全解析:编码逻辑、获取方法与实战避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
中国天气网城市代码全解析:编码逻辑、获取方法与实战避坑指南

1. 城市代码到底是个什么东西

做数据采集或者天气类应用开发的朋友,大概率都碰过这么一个场景:想拉某个城市的实时天气,接口文档翻来翻去,发现最关键的不是请求地址,也不是返回格式,而是一个看起来毫不起眼的数字串——城市代码。中国天气网作为国内气象数据的重要出口,它给每个城市、每个区县都分配了一个唯一的九位数字编码,这个编码就是打开天气数据大门的钥匙。

我第一次接触这个东西是在做一个出行提醒的小工具,当时天真地以为直接拿城市名去请求就行了,结果接口返回的数据要么是空的,要么直接报错。后来才搞明白,这套体系里城市名只是给人看的,机器认的是代码。举个例子,北京对应的代码是101010100,上海是101020100,广州是101280101。你会发现这些数字并不是随便编的,前三位101基本固定,中间三位代表省份或大区,后三位才是具体的城市或区县。这个规律一旦摸清楚,后面批量处理就轻松多了。

这篇文章适合谁看?如果你正在做天气数据采集、出行类应用、农业气象服务,或者单纯想给自己的小项目加个天气模块,那这套城市代码的获取、解析和使用方法就是绕不开的基础功。我会从代码的结构讲起,然后一步步带你拿到完整的城市代码表,再讲怎么在实际项目里用好它,最后把我踩过的坑和排查经验都倒出来。不管你是刚入门的新手,还是已经写过几年代码的老手,应该都能从里面找到能直接抄作业的东西。

需要提前说明的是,城市代码本身只是一串标识符,不涉及任何敏感信息,它的价值在于帮你精准定位到想要的那个城市。很多人卡住不是因为技术难,而是因为不知道去哪里找、找到了又不知道怎么用。接下来的内容就是把这层窗户纸捅破。

2. 城市代码的编码逻辑与结构拆解

2.1 九位数字背后的分层设计

中国天气网的城市代码采用九位纯数字格式,这个长度不是拍脑袋定的,而是为了容纳从省级到区县级的完整行政层级。我把它拆成三段来看:前三位是固定前缀,目前见到的绝大多数都是101,这个前缀可以理解为整个编码体系的命名空间;中间三位是区域标识,用来区分不同的省份或者大区,比如北京是010,上海是020,广东是280;最后三位是城市或区县序号,从001开始递增。

拿几个实际例子来验证一下。北京101010100,拆开就是101-010-100;天津101030100,拆开是101-030-100;石家庄101090101,拆开是101-090-101。你会发现中间三位的变化是有规律的,相邻省份的编号往往也挨着。这个规律在批量处理的时候特别有用,比如你想抓某个省下面所有城市的天气,就可以先确定中间三位的范围,然后在这个范围内遍历后三位。

不过要注意,这个规律并不是严格按行政区划顺序来的,有些省份的编号跨度比较大,还有些区县的编号并不连续。所以靠推算只能得到一个大致范围,真正要拿到精确的代码表,还是得从官方渠道获取完整的映射关系。我试过用推算的方式去猜某个县的代码,结果猜了十几次都没对,最后还是老老实实去扒了完整的列表。

2.2 为什么不能用城市名直接请求

这个问题我被问过很多次,答案其实很简单:城市名有歧义,代码没有。中国有那么多同名的地方,光是“城关镇”这种名字就能找出几十个,更别说还有“朝阳”这种既是区又是市的情况。如果接口用城市名来匹配,服务器根本不知道你要的是哪个朝阳。代码就不一样了,每个代码对应唯一的一个行政单位,不会产生歧义。

另外从技术角度看,用代码查询的效率更高。字符串匹配需要做模糊比对,而数字匹配可以直接走索引,响应速度差了一个量级。我实测过同一个接口,用城市名请求平均响应时间在800毫秒左右,换成城市代码之后降到了200毫秒以内。对于需要批量拉取数据的场景,这个差距会被放大到非常可观的程度。

还有一个容易被忽略的点:城市名的写法不统一。有的地方写“北京”,有的写“北京市”,有的写“帝都”,接口如果要做兼容处理,维护成本会很高。代码就没有这个问题,101010100就是101010100,不管你怎么描述这个城市,代码是不变的。

2.3 代码表的覆盖范围与更新机制

中国天气网的城市代码表覆盖了全国所有地级市、市辖区、县和县级市,总数在两千多个。这个数量是动态变化的,因为行政区划本身会调整,比如某个县升级成区,或者新设立一个开发区,代码表就会跟着更新。我对比过不同时间点获取的代码表,发现每年都会有几十个条目的增减。

更新机制方面,官方并没有提供一个专门的变更日志,所以如果你在做长期运行的项目,建议定期重新拉取一次完整的代码表,然后跟本地缓存做比对,把新增的、删除的、修改的条目找出来。我一般是一个季度更新一次,这个频率对于大多数应用来说足够了。如果是做气象相关的专业服务,可能需要更频繁地检查。

注意:不要假设代码是永久不变的。我遇到过两次代码变更导致线上服务报错的情况,一次是某个区被合并,原代码失效;另一次是新设了一个区,旧代码被复用到了新区域。虽然概率不高,但一旦碰上就是线上事故。

3. 获取完整城市代码表的实操路径

3.1 从公开页面提取代码数据

最直接的办法是从中国天气网的公开页面上把代码提取出来。打开网站首页,你会看到全国各地城市的列表,每个城市链接的URL里都带着它的代码。比如北京的链接里就包含101010100这个数字。手动一个个点肯定不现实,这时候就需要用脚本批量抓取。

我的做法是先用浏览器的开发者工具观察页面结构,找到城市列表所在的HTML节点,然后用Python的requests库拉取页面,再用BeautifulSoup或者lxml解析出所有链接,最后用正则表达式把URL里的九位数字提取出来。整个过程不到五十行代码就能搞定。下面是我常用的一个提取脚本的核心逻辑:

import re import requests from bs4 import BeautifulSoup def fetch_city_codes(url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/120.0.0.0 Safari/537.36' } resp = requests.get(url, headers=headers, timeout=10) resp.encoding = 'utf-8' soup = BeautifulSoup(resp.text, 'lxml') codes = {} for link in soup.find_all('a', href=True): match = re.search(r'/(\d{9})\.shtml', link['href']) if match: code = match.group(1) name = link.get_text(strip=True) if name and code not in codes: codes[code] = name return codes

这个脚本的关键在于正则表达式/(\d{9})\.shtml,它能精准匹配到URL里的九位数字。实际跑下来,一次能提取到两千多个城市代码,覆盖了绝大部分常用城市。不过要注意,有些页面是分省展示的,可能需要遍历多个入口页面才能拿全。

3.2 处理分页与层级嵌套

网站的城市列表并不是平铺的,而是按省、市、区县层层嵌套。如果你只抓首页,拿到的可能只是省级或者地级市的信息,区县级别的代码会漏掉。我的处理方式是先抓取省级页面的链接,然后逐个进入省级页面抓取地级市,再进入地级市页面抓取区县。这是一个典型的三层遍历结构。

具体操作的时候,我会维护一个待抓取队列,从顶层URL开始,每解析出一个新链接就加入队列,同时记录已经访问过的URL避免重复。为了防止请求过于频繁被限制,每个请求之间加0.5到1秒的延迟。整个遍历下来大概需要十几分钟,取决于网络状况和页面数量。

这里有个细节值得注意:有些区县的页面URL格式跟地级市不太一样,可能不带.shtml后缀,或者数字位数有变化。我在正则里做了兼容处理,同时匹配/(\d{9})和/(\d{9})\.shtml两种形式,确保不漏掉任何一条。

3.3 数据清洗与去重

抓下来的原始数据往往有重复和噪音。重复的来源主要有两个:一是同一个城市在不同页面里出现了多次,二是URL里可能包含非城市代码的九位数字,比如某些文章ID恰好也是九位。去重的时候不能简单地按代码去重,还要结合城市名来判断。

我的清洗流程是这样的:先按代码分组,如果同一个代码对应多个不同的城市名,就人工核查一下哪个是正确的;然后按城市名分组,如果同一个城市名对应多个代码,保留层级最低的那个(也就是区县级优先于地级市)。最后把结果存成CSV或者JSON格式,方便后续使用。

清洗完之后,我一般会做一个简单的校验:统计一下每个省份下面有多少个城市代码,跟公开的行政区划数量做个对比。如果某个省的数量明显偏少,就说明抓取过程中可能漏了页面,需要回去补抓。这个校验步骤帮我发现过好几次遗漏,特别是那些页面结构比较特殊的省份。

3.4 本地缓存与增量更新策略

代码表抓下来之后,不要每次都重新抓,那样既慢又容易触发限制。合理的做法是存到本地,用的时候直接读本地文件。我一般会存两份:一份是完整的代码表,包含代码、城市名、所属省份、层级等信息;另一份是精简的映射表,只包含代码和城市名,用于快速查询。

增量更新的时候,我会先拉取最新的完整列表,然后跟本地缓存做diff。新增的代码直接追加,删除的代码标记为失效,修改的代码更新对应字段。diff的结果会生成一个变更报告,方便我快速了解哪些城市发生了变化。这个机制在长期运行的项目里特别有用,能避免因为代码变更导致的静默失败。

实操心得:本地缓存建议用SQLite而不是纯文本文件。SQLite支持索引和查询,两千多条数据在里面查起来是毫秒级的,而且方便做diff和版本管理。我用纯文本文件的时候,每次更新都要全量重写,后来换成SQLite之后效率提升了很多。

4. 城市代码在实际项目中的使用方式

4.1 构建代码与城市名的双向映射

在实际项目里,你需要的往往不只是代码本身,而是代码和城市名之间的灵活转换。用户输入的是城市名,接口需要的是代码,返回的数据里可能又带着城市名,这中间就需要一个双向映射的桥梁。我的做法是构建两个字典:一个从代码映射到城市名,一个从城市名映射到代码列表。

为什么城市名到代码是列表而不是单个值?因为同名城市确实存在。比如“城关区”在好几个省都有,如果用户只输入“城关区”,你需要根据上下文或者让用户进一步选择来确定具体是哪一个。我的处理方式是优先返回层级最高的那个,同时在结果里附带所有匹配项,让调用方自己决定。

code_to_name = { '101010100': '北京', '101020100': '上海', '101280101': '广州', # ... 更多映射 } name_to_codes = {} for code, name in code_to_name.items(): name_to_codes.setdefault(name, []).append(code) def resolve_city(input_str): if input_str in code_to_name: return input_str if input_str in name_to_codes: return name_to_codes[input_str][0] for name, codes in name_to_codes.items(): if input_str in name: return codes[0] return None

这个resolve函数做了三层匹配:先看输入是不是代码本身,再看是不是完整的城市名,最后做模糊匹配。实测下来,对于绝大多数常见城市,第一层和第二层就能命中,只有少数生僻地名需要走到第三层。

4.2 批量请求时的代码分组策略

如果你需要一次性拉取多个城市的天气数据,直接循环请求是最笨的办法。更好的做法是按代码的中间三位分组,把同一个区域的城市放在一起请求。这样做的好处是可以复用连接,减少握手开销。我实测过,分组之后批量请求的总体耗时能降低百分之三十左右。

具体实现的时候,我会先把待请求的代码列表按中间三位排序,然后依次发送请求。同一个分组内的请求可以并发,不同分组之间保持串行,这样既能利用并发提升速度,又不会因为请求过于集中而触发限制。并发的数量控制在五到十个比较合适,太多了反而会因为资源竞争导致整体变慢。

还有一个细节:有些接口支持一次传入多个城市代码,返回一个数组。如果你的接口有这个能力,那就更省事了,直接把分组后的代码拼成逗号分隔的字符串传进去就行。不过要注意接口对单次请求数量的限制,一般不超过五十个。

4.3 缓存策略与失效时间设计

天气数据是实时变化的,但城市代码是相对固定的。这两者的缓存策略要分开设计。城市代码可以长期缓存,几个月更新一次都没问题;天气数据则需要根据更新频率来设定失效时间,一般十五分钟到一小时比较合理。

我在项目里用的是两级缓存:内存缓存存最近使用的城市代码映射,响应时间在微秒级;磁盘缓存存完整的代码表,响应时间在毫秒级。天气数据则单独走一套缓存,键是城市代码加日期,值是当天的天气信息。这样设计的好处是,即使天气缓存失效了,城市代码的查询依然很快,不会成为瓶颈。

注意:缓存失效时间不要设得太短,否则频繁的磁盘IO会成为性能瓶颈;也不要设得太长,否则代码表更新之后本地还是旧数据。我的经验是城市代码表一周检查一次更新,天气数据十五分钟刷新一次,这个组合在大多数场景下都够用。

4.4 异常处理与降级方案

再稳定的接口也有出问题的时候。我遇到过好几次请求超时或者返回空数据的情况,如果没有做好异常处理,整个应用就会卡死。我的做法是在请求层加超时和重试机制,超时时间设五秒,重试两次,两次都失败就返回缓存里的旧数据,同时记录一条告警日志。

降级方案也很重要。如果城市代码表加载失败,应用应该能退化到使用内置的少量常用城市代码,保证核心功能可用。我一般会在代码里硬编码二十个左右的一线城市代码作为兜底,这样即使外部数据源完全不可用,至少北京上海的天气还能查到。

还有一个容易被忽略的异常:代码表里存在但实际已经失效的代码。这种代码请求接口会返回错误,但错误信息往往不明确。我的处理方式是在本地维护一个失效代码黑名单,一旦某个代码连续多次请求失败,就把它加入黑名单,后续请求直接跳过。这个机制帮我省了很多无效请求。

5. 常见问题排查与避坑经验

5.1 代码请求返回空数据怎么办

这是最常见的问题,原因可能有三种:代码本身不对、代码已失效、接口临时故障。排查的时候我会按这个顺序来:先用一个已知正确的代码(比如北京的101010100)测试接口是否正常,如果正常说明接口没问题;然后用出问题的代码去代码表里查,看是否存在;如果存在但请求还是空,就换个时间段再试,可能是接口那边的临时问题。

我遇到过一种比较隐蔽的情况:代码在代码表里存在,但对应的城市已经改名或者合并了,接口那边已经不再返回数据。这种时候只能通过对比新旧代码表来发现,或者观察请求失败的频率,如果某个代码持续失败,基本可以判定是失效了。

5.2 城市名匹配不上的处理思路

用户输入的城市名千奇百怪,有带“市”的,有不带的,有写简称的,还有写错别字的。我的处理策略是分级匹配:第一级精确匹配,第二级去掉“市”“县”“区”等后缀再匹配,第三级用编辑距离做模糊匹配。编辑距离的阈值设在2以内,超过2的基本就是另一个城市了。

对于特别常见的简称,比如“北上广深”,我会在映射表里额外加一条别名记录,直接指向对应的代码。这样用户输入“北上广深”也能正确解析。别名表不需要很大,覆盖几十个常用简称就够了,维护成本很低,但用户体验提升很明显。

5.3 批量抓取时的频率控制

抓取代码表的时候,如果不控制频率,很容易被限制。我的经验是每个请求之间至少间隔0.5秒,如果目标页面响应慢,就延长到1秒。同时要设置合理的超时时间,避免因为某个页面卡住导致整个抓取流程停滞。另外,User-Agent要设置成常见的浏览器标识,不要用默认的Python标识,否则很容易被识别出来。

如果抓取量比较大,建议分批次进行,比如每次抓一百个页面就休息几分钟。我试过一次性抓两千个页面,结果中间被中断了好几次,后来改成每批两百个,中间休息三十秒,就顺利多了。这个策略虽然总耗时更长,但成功率更高,综合下来反而更省时间。

5.4 代码表版本管理的经验

代码表是会变的,所以版本管理很重要。我一般会在代码表文件里加一个版本号字段,格式是日期加序号,比如20250101-01。每次更新的时候,版本号递增,同时保留旧版本的文件。这样如果新版本出了问题,可以快速回滚到旧版本。

版本对比的时候,我会生成一个变更报告,列出新增、删除、修改的条目。这个报告不仅用于排查问题,还能帮我了解行政区划的变化趋势。比如某段时间新增了很多区县代码,可能意味着那边有新的开发区设立,对于做区域分析的项目来说,这个信息本身就很有价值。

问题类型典型表现排查方法解决方案
代码错误请求返回空或报错用已知正确代码对比测试从代码表中重新获取正确代码
代码失效持续请求失败对比新旧代码表更新本地代码表,加入黑名单
接口故障所有代码都失败测试多个不同代码等待恢复,使用缓存数据降级
频率限制请求被拒绝或超时检查请求间隔和频率降低频率,增加延迟,分批抓取
名称歧义匹配到错误城市检查同名城市列表使用代码而非名称,增加别名表

5.5 几个容易踩的坑

第一个坑是假设代码是连续的。我一开始以为同一个省下面的代码是连号的,结果发现中间有很多空缺,有些号码被分配给了其他省份。所以千万不要用循环递增的方式去猜代码,一定要用完整的代码表。

第二个坑是忽略层级关系。同一个城市名可能对应多个代码,分别代表市、区、县不同层级。如果你需要的是区县级的数据,但匹配到了市级代码,返回的可能是整个市的平均数据,而不是你想要的那个区的数据。我的做法是在代码表里明确标注层级,匹配的时候根据需求选择对应层级。

第三个坑是忘记处理编码问题。抓取下来的页面可能是GBK编码,也可能是UTF-8,如果不做处理,城市名会出现乱码。我的做法是先用chardet检测编码,然后用检测到的编码来解码,这样基本不会出错。

第四个坑是过度依赖单一数据源。虽然中国天气网是最常用的来源,但它的页面结构偶尔会调整,导致抓取脚本失效。我的建议是至少准备两套抓取方案,一套基于页面解析,一套基于接口调用,互为备份。这样即使一套失效了,另一套还能顶上。

6. 代码表维护与扩展的一些思路

代码表拿到手之后,怎么维护和扩展也是个值得聊的话题。我自己的做法是把它当成一个独立的数据模块来管理,跟业务代码解耦。具体来说,就是单独建一个目录存放代码表文件和更新脚本,业务代码只通过一个统一的接口来查询代码,不直接读文件。这样以后换数据源或者改存储格式,只需要改这个模块,业务代码不用动。

扩展方面,我建议在基础的城市代码之上,额外维护几个衍生字段。比如省份名称、经纬度、时区、是否支持区县级查询等。这些字段在基础代码表里可能没有,但可以通过其他公开数据源补充进来。有了这些字段,你在做天气展示的时候就能直接显示省份信息,做地图标注的时候就能直接拿到经纬度,省去了二次查询的麻烦。

还有一个思路是把代码表跟其他数据做关联。比如跟空气质量数据关联,跟历史天气数据关联,跟旅游景点数据关联。这样你的应用就不只是查天气,还能提供更丰富的服务。我做过一个小的出行助手,就是把城市代码跟景点数据关联起来,用户输入城市名,不仅能查到天气,还能看到当地景点的推荐,效果还不错。

最后说一个我个人的习惯:每次更新代码表之后,我会跑一遍全量测试,用所有代码去请求一遍接口,统计成功率和响应时间。这个测试大概需要十几分钟,但能帮我提前发现失效代码和接口异常。跑完之后的报告我会存档,方便以后对比。这个习惯坚持了两年多,帮我避免了好几次线上故障。

代码表这个东西,看起来简单,但真正用好需要不少细节上的打磨。希望上面这些经验能帮你少走一些弯路。如果你也在做类似的项目,欢迎交流踩坑心得。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 20:39:21

街头摄影长期项目实战:从告别瞬间到下一帧的完整指南

年初我给自己定了一个拍摄计划,名字就叫"See you: Next Moment"。说起来很直白,就是每次举起相机,先跟眼前这个画面说一声再见,然后等下一个瞬间。这个项目持续了大半年,拍了差不多两万张照片,最…

作者头像 李华
网站建设 2026/10/19 23:28:23

MIMO波束成形MATLAB仿真:从导向矢量到MVDR权重计算的完整实战

简介:阵列信号处理是无线通信物理层仿真的核心方向之一,MIMO波束成形通过调整阵列天线权重,在空间上增强期望信号并抑制干扰,其原理可从均匀线阵的导向矢量讲起。实际工程中,MVDR、LCMV与MMSE等自适应算法各有适用场景…

作者头像 李华
网站建设 2026/10/14 17:19:46

智能病虫害防治系统落地指南:从数据标注到模型训练与田间部署

简介:这是一份基于图像识别与深度学习的智能病虫害防治系统项目资源,面向农业物联网开发者、计算机视觉学习者及高校相关专业学生,用于解决作物病虫害自动识别与实时监测问题。资源共13个文件,约10.83MB,以6个Python脚…

作者头像 李华
网站建设 2026/10/20 14:47:16

Cosplay垂直网站搭建:静态站点+图片优化+社区运营实战

1. 项目概述:一个聚焦Cosplay内容的垂直动漫网站定位解析“652SJBH动漫网站Cosplay”这个标题乍看像一串随机字符组合,但结合当前国内ACG内容生态与社区运营规律,它极大概率指向一个以Cosplay为核心内容载体、面向二次元爱好者群体的垂直型动…

作者头像 李华
网站建设 2026/10/17 17:11:48

纯前端 localStorage 实现配送地址跨页面同步方案

简介:一套面向毕业设计的民航售票管理系统完整资料包,基于ASP与SQL Server开发,采用B/S结构,适合计算机相关专业学生用来完成课程设计或毕业论文参考。系统涵盖会员注册管理、管理员后台、留言管理、航班查询、网上订票与网上退票…

作者头像 李华
网站建设 2026/10/20 15:24:15

主动配电网SOCR最优潮流:从论文公式到可运行Python代码

简介:本资源是一套面向电力系统方向研究生与高年级本科生的毕业设计级科研实践材料,聚焦双碳目标下综合能源系统(IES)低碳经济运行优化问题,提供从建模、求解到结果分析的完整技术方案。压缩包共25个文件,含…

作者头像 李华