2026年分省分市POI数据处理全攻略:清洗、聚合与Apache POI实战
2026/9/16 4:10:49 网站建设 项目流程

2026年的中国POI点位数据(分省/分市),我拿到手的第一反应不是看总量,而是先把省、市两级目录翻了一遍。POI点位数据说白了就是地图上那些“感兴趣的点”:饭店、医院、学校、加油站、写字楼、仓库、公交站……每一个点都带经纬度和属性信息,单独看是一个坐标,叠加上时间和属性,就成了判断线下商业布局、物流网络覆盖或城市规划进度的重要素材。这套数据最核心的卖点是按省、按市切好了,你不用自己再做行政区划匹配,拿到某个区域就能直接开工。

这篇东西不打算给你堆概念,重点讲三件事:这类数据从哪来、怎么洗、怎么用,以及用Java或Python处理时最容易踩的坑。无论你是做商业分析、GIS开发、地产选址,还是单纯在搞数据可视化练手,把这条链路走通,后面很多活儿都能直接复用。

1. POI数据到底是个啥:从省市级维度看价值

1.1 分省分市的数据为什么这么“香”

单纯给你一份全国POI数据,很多人第一反应是“这有什么可稀罕的”。但分省分市切好之后,价值完全不一样。先说最直接的场景:连锁品牌做门店选址。你拿全国数据硬筛,数据量动辄几千万条,光加载就把内存吃满;但如果你只需要判断成都市场,直接取四川省或者成都市子集,几万条到几十万条数据,普通笔记本就能跑得动。

再说一个我实际遇到过的需求:某物流公司要做“次日达”覆盖测算。他们关心的不是全国有多少个网点,而是每个地级市辖区内快递柜、驿站、转运中心的密度分布。这种需求如果拿全国数据临时按城市筛选,至少要写一堆正则去匹配地址字段;但数据源已经按省、市切好,直接按文件夹或者表分区读取,代码量能砍掉一半还多。

分省分市还有一个隐藏优势:方便做横向对比。同一个维度在全国范围看是平均值,按省、市拆开看就是一张竞争地图。比如餐饮POI密度,广东和西藏完全没有可比性;但同是长三角的城市,苏州和无锡之间就能比出商业中心的辐射差异。这种对比如果不拆到地级市,几乎没法落地。

1.2 一条POI记录里都装了哪些字段

不要以为POI数据就是一个坐标加一个名字。实际上一份能用的POI数据,字段设计是有固定套路的。我拆解一下常见的2026年版分省分市POI数据,通常包含这么几组字段:

  • 基础标识:POI唯一编号、名称、地址、电话。唯一编号是最容易被忽略的,很多人拿到数据先删掉,等后面要去重或关联业务数据时才发现麻烦。
  • 地理坐标:经纬度(WGS84或GCJ-02坐标系)、所在省、所在市、区县、乡镇/街道。
  • 类别信息:大类(餐饮、购物、医疗、教育等)、中类、小类,有的还会带品牌标签。
  • 运营属性:营业状态、营业时间、评分、评论数。这个不是所有数据源都给,但给到这个层级的基本都是商业级数据。

重点提醒一下坐标系统。国内来源的POI数据经常用GCJ-02(火星坐标),而GPS设备或国际数据源用WGS84。两者之间有几百米的偏移,直接混用会导致点落到错误的路口或建筑上。后文我会专门讲怎么处理。

1.3 2026年这版数据跟往年有什么不同

从趋势上看,2026年的分省分市POI数据有几个变化值得关注。第一是覆盖范围更全,乡镇级POI明显增加。前几年很多数据商只覆盖到县城,乡镇只有基础政府机构;现在随着县域经济被重视,乡镇一级的餐饮、零售、物流点位补得很快。第二是动态属性更多,营业状态、暂时关闭标记这类字段比例大幅提高,这和消费行业经营波动直接相关。第三是数据更新频率从季度更新往月度更新走,一些头部门店连锁品牌的数据已经能做到按月同步。

这些变化对使用者意味着什么?如果你做的是长期趋势研究,光看静态点位数量是远远不够的,必须结合时间维度看新增和关闭数量。2026年的数据结构里,很多厂商已经开始提供“首次出现时间”和“最后确认时间”这类时间戳字段,做生命周期分析就方便多了。

2. 数据来源怎么选:公开、商用还是自采

2.1 公开渠道能拿到什么

网上能看到不少免费的POI数据,大多是爬虫爬出来的快照,质量参差不齐。这类数据适合做技术验证或Demo原型,不适合直接上生产。免费数据的通病是字段不全,很多只有名称、坐标和一级分类,拿它做精细分析会发现维度不够用;另一个问题是时效性差,点位新增和关闭根本更新不到。

常用的免费来源主要是几个地图平台的开放接口。它们的优势是覆盖面广、更新快,缺点是接口配额有限,单次查询有数量上限,想拉全一个省的POI得用关键词加网格分页跑很长时间。而且平台对高频抓取有风控,账号容易被限制,实际操作时要控制请求频率,加随机延时,最好配多账号轮换。这一块合规性也要注意,不要拿接口做超出平台允许范围的商业化使用。

2.2 商用数据源的取舍

如果你的工作对数据质量有硬要求,比如商业选址、投资分析、政府咨询,商用数据源基本是绕不开的。市面上主流的数据商提供的2026年分省分市数据,单省几十万条、全国几千万条是常态,年费从几千到几十万都有,差异主要在更新频率、字段维度、售后服务上。

选商用源我比较看三点:

  • 更新频率是否真的按月执行。很多数据商宣传月更但实际是三个月才动一次。
  • 坐标系是否标注清楚。有些数据商给的是GCJ-02,文档里却全篇不提,拿去做空间计算很容易栽跟头。
  • 去重和清洗是否做过。大厂的原始数据能干净到什么程度,你永远猜不到。

建议采购前要求对方拿一个中等城市的完整样例数据,自己跑一遍空间校验,看看有没有点落在河流、公园或荒地中央。这种检测很简单,拿行政边界做空间匹配,看落点误差分布就行。

2.3 自己采集的边界与成本

自采POI数据最常见的形式是派团队到线下扫街,或者用移动采集车。这个成本极高,不是一般企业能长期玩的。自采数据的优势在于独一无二,能拿到竞品永远拿不到的信息,比如某个商场每个铺位的真实营业状态,某条街每个商户的外摆面积。对做连锁零售的人来说,这种差异化数据价值巨大。

但自采的坑也很明显。首先是成本,一辆采集车一个月的运维费用可以请两个全职数据分析师;其次是数据治理,即便是线下踩点回来的数据,同样面临地址标准化、坐标纠偏、去重合并的一整套流程;最后是合规风险,连续采集个人信息相关场所的详细数据需要注意边界,不能踩到隐私红线。

我的判断是:普通团队没必要自建采集体系,公开数据做原型验证,商用数据做业务主数据,自采数据只关注核心竞争区域,三者结合才是效率最高的方案。

3. 数据清洗与标准化:拿到手的第一件事

3.1 首次洗数据的必修动作

不管数据来源是哪家,到手的第一件事永远是做基础体检。我通常分四步走。

第一步是检查坐标系和范围。导入数据后,先对经纬度做一个快速范围判断,中国的经纬度大致在东经73度到135度、北纬18度到54度之间,超出这个范围的记录直接标记为异常,交给后续人工复核。

第二步是字段缺失率统计。针对关键字段,比如名称、经纬度、省份、城市、类别,逐列统计缺失率。缺失率超过5%的字段,在分析阶段就要特别小心,不能直接拿来做聚合统计。

第三步是行政区划匹配。即使数据里已经带了省市区字段,也建议用空间边界重新做一遍匹配。因为数据商给行政归属偶尔会出错,一条记录标注成A市但坐标实际落在B市,如果直接按字段聚合,结果会偏差很大。用边界匹配可以纠正大部分这类错误。

第四步是时间戳标注。如果原始数据里有数据快照时间,要保留下来;如果没有,也要在内部表里加上“入库时间”,方便后续做增量更新和版本回溯。这步很多人不做,等三个月后数据更新了才发现新旧混淆,到时候哭都来不及。

3.2 去重算法:GeoHash加距离判断

POI数据最头疼的问题就是重复。同一家店,在地图平台上可能被录入两三条记录,一条叫“老王牛肉面”,一条叫“老王牛肉面(总店)”,还有一条电话和地址都一样但名称略有差别。单纯的名称去重不可靠,坐标去重也不可靠,因为不同采集批次坐标本来就会有偏移。

业界通用的做法是GeoHash加距离阈值双重判断。GeoHash是把经纬度编码成一个字符串,精度可以通过字符串长度控制。比如使用7位GeoHash,覆盖范围大约是几十米到上百米,适合判断相距很远的点;再用精确距离进一步确认。

import pandas as pd import numpy as np from math import radians, sin, cos, asin, sqrt def haversine(lon1, lat1, lon2, lat2): R = 6371000 dlon = radians(lon2 - lon1) dlat = radians(lat2 - lat1) a = sin(dlat/2)**2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(dlon/2)**2 return 2 * R * asin(sqrt(a)) # 示例:找出距离在100米内的重复点 # 先按GeoHash粗分组,再在组内做距离计算 df['geohash'] = df.apply(lambda row: encode_geohash(row['lng'], row['lat'], precision=7), axis=1) grouped = df.groupby('geohash')

实际业务里,我建议把阈值设在50到100米之间。太小的阈值会导致同店不同门头的记录漏掉,太大又容易把同一条街上紧挨着的两家店误判成重复。这个阈值没有固定值,取决于你业务对误判和漏判的容忍度。餐饮行业我通常用60米,商场内部品牌店除外,因为同楼层不同店铺距离可能就十米甚至更近,需要结合类别和名称综合判断。

3.3 地址与类别字段的标准化

地址字段是最让人头大的,同一个“北京市朝阳区建国路88号”,不同批次可能写成“建国路88号”“朝阳区建国路88号”“北京朝阳区建国路88号院”。做标准化时,我会先拆分省市县区字段,再拆分详细地址,然后按“行政区划+道路+门牌号”的结构做归一化。这一步用现成的地址解析工具能省不少事,但要注意解析错误的积累,批量处理后一定要抽检。

类别字段的标准化同样重要。不同数据源的分类体系差异很大,有的用“餐饮服务”,有的用“美食”,还有的直接用平台类目标签。建议第一步先映射成统一的“大类-中类-小类”三级体系。大类控制在一级业务维度,比如吃、住、行、游、购、娱;中类控制在10到20个;小类尽可能保留原始标签,方便后续细分分析。

标准化的意义在做跨区对比时体现得最明显。如果你不统一类别,成都市用A分类体系、重庆市用B分类体系,两个城市的“零售POI密度”就完全没有可比性。我见过很多分析报告,数据都是好数据,就死在分类口径不一致上。

4. 实操环节:用Python处理分省分市POI数据

4.1 准备一份趁手的目录结构

我处理这类项目的习惯是,先按“省市-数据版本-日期”搭目录。比如这样:

/data_2026/ /province/广东省/ 深圳_202601.xlsx 广州_202601.xlsx /province/四川省/ 成都_202601.xlsx /merged/ 全国_202601_清洗后.parquet

好处是每个城市的原始文件独立存放,处理脚本只负责读取,不覆盖源文件,防止误操作。中间文件统一放merged目录,最终结果单独放output目录。这样哪怕清洗逻辑出Bug,重新跑一遍也不会污染原始资料。这个习惯帮我挽回了好几次局面。

4.2 数据加载与基础清洗脚本

以最常见的Excel文件为例,用pandas读取后,先做基础字段裁剪和类型转换。不要把全部字段一锅端进来,留业务要的字段就行,内存占用会小很多。

import pandas as pd df = pd.read_excel("data_2026/province/广东省/深圳_202601.xlsx", dtype={"poi_id": str}) cols = ["poi_id", "name", "lng", "lat", "province", "city", "district", "category", "sub_category", "status"] df = df[[c for c in cols if c in df.columns]] df["lng"] = pd.to_numeric(df["lng"], errors="coerce") df["lat"] = pd.to_numeric(df["lat"], errors="coerce") df = df.dropna(subset=["lng", "lat"]) # 用边界框过滤明显异常坐标 china_bbox = (73, 18, 135, 54) df = df[(df["lng"] >= china_bbox[0]) & (df["lng"] <= china_bbox[2]) & (df["lat"] >= china_bbox[1]) & (df["lat"] <= china_bbox[3])]

几点提醒:poi_id一定要读成字符串,不然Excel里超过15位的数字会被科学计数法截断,精度直接丢;经纬度字段用errors="coerce",解析不了的就置空然后统一过滤,比后面计算时报错要舒服得多。再就是读Excel时如果文件很大,建议把参数engine换成"openpyxl",配合read_only模式能减少很多内存压力。

4.3 按省、市维度做聚合统计

清洗完之后,最常见的需求就是统计每个省、每个市的POI数量、类别分布、行业密度。城市级别的聚合用groupby就能搞定。

city_stats = df.groupby(["province", "city"]).size().reset_index(name="poi_count") cat_city = df.groupby(["province", "city", "category"]).size().reset_index(name="cnt") pivot = cat_city.pivot_table(index=["province", "city"], columns="category", values="cnt", fill_value=0)

如果要做空间密度分析,这时就应该引入GeoPandas。按区县聚合并计算每平方公里的POI密度,这是选址和城市分析的标配。

import geopandas as gpd gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df["lng"], df["lat"]), crs="EPSG:4326") # 叠加到区县边界,做空间连接 county = gpd.read_file("data_2026/boundary/区县边界.shp") joined = gpd.sjoin(gdf, county, how="left", predicate="within") density = joined.groupby("区县名").size() / joined.groupby("区县名")["面积_km2"].first()

空间连接这一步会让数据量大的时候计算变慢,建议先把全国数据拆到省份,按省跑完再concat。我是吃过这个亏的,几百万个点直接空间连接,跑了快俩小时没出结果,拆分后十分钟就完事。

4.4 结果导出与后续可视化

分析完建议直接把结果落到parquet文件和Excel两种格式。parquet保留全字段,给你下次做更复杂分析用;Excel给业务同事和领导看。写Excel时用to_excel加sheet_name划分模块,一个文件里放多个Sheet,比一个Sheet塞一堆列直观得多。

with pd.ExcelWriter("output/省市POI统计_202601.xlsx", engine="openpyxl") as writer: city_stats.to_excel(writer, sheet_name="省市总量", index=False) pivot.to_excel(writer, sheet_name="类别分布", index=True) density.to_excel(writer, sheet_name="区县密度", index=True)

可视化层面,稍微提一句:不要一上来就画全国地图热力图,那种图信息量太大,根本看不出细节。先按省圈一批重点城市,逐个画城市内部的POI核密度分布,有对比、有层次,才真正对业务有指导作用。

5. 用Excel处理POI数据时绕不开的Apache POI问题

5.1 为什么专门聊Apache POI

看到这里你可能疑惑:不是聊POI点位数据吗,怎么扯到Apache POI去了?其实搜索引擎里“apache poi”这个词和“poi数据集”经常一起出现,原因很简单:大量POI数据分发和交换都依赖Excel文件,而后端Java系统读写Excel最常用的库就是Apache POI。尤其在企业环境里,Java服务接收数据商交付的.xlsx文件、写统计报表、生成带嵌入样式的结果文档,全是Apache POI的活。

这中间有两个问题出现频率最高:一个是老版本Apache POI存在安全漏洞,另一个是用POI写Word表格时单元格宽度怎么设置都不生效。这俩坑我都踩过,写出来希望对你有用。

5.2 XSSFExportToXML的XXE漏洞原理与修复

先说漏洞。Apache POI 4.1.0及更早版本的XSSFExportToXML接口存在XML外部实体注入风险。XSSFExportToXML的作用是把工作表内容导出成XML格式,但在处理XML外部实体时不够严谨,攻击者可以构造一个包含外部实体引用的XML文件,解析时读取服务器上的本地文件,或者发起内网请求,造成信息泄露。

听起来有点抽象,我解释得直白一点:如果你有一个Java服务,接收用户上传的.xlsx文件,内部用XSSFExportToXML把数据转成XML再往下游处理,那攻击者可以精心构造一个Excel文件,里面藏一段恶意XML,服务端一解析,服务器里的某个配置文件内容就可能被当成本地实体展开,写到导出结果里。更严重的还能做SSRF探测内网服务。

修复方案很简单:升级到Apache POI 4.1.1或更高版本。如果项目暂时不能升级,要马上在代码里禁止解析外部实体。以DocumentBuilderFactory为例,最稳妥的方式是禁掉DOCTYPE声明:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false);

这三个Features组合起来,能基本把XXE路径堵死。顺便说一下,线上如果存在大量历史文件需要解析又没法升级框架,可以在网关或文件接收层加一道过滤,检查上传文件里是否包含<!DOCTYPE<!ENTITY字符串,直接拦截会更省事。

5.3 用POI设置Word表格单元格宽度

另一个高频坑是用Apache POI生成Word报告时,表格单元格宽度设了没反应。典型场景是:你把各省市POI统计表导出成Word文档发给业务方,想控制某列宽度让表格排版整齐,结果调用XWPFTableCell.setWidth()后打开Word发现宽度根本没变。

原因是纯用高层API设置宽度常常无效,真正控制单元格宽度的是底层XML里的tcW元素,需要用底层CT类直接操作。

import org.apache.poi.xwpf.usermodel.*; import org.openxmlformats.schemas.wordprocessingml.x2006.main.*; public class TableCellWidthUtil { public static void setCellWidth(XWPFTableCell cell, int widthTwips) { CTTcPr tcPr = cell.getCTTc().isSetTcPr() ? cell.getCTTc().getTcPr() : cell.getCTTc().addNewTcPr(); CTTcW tcW = tcPr.isSetTcW() ? tcPr.getTcW() : tcPr.addNewTcW(); tcW.setType(STTblWidth.DXA); tcW.setW(java.math.BigInteger.valueOf(widthTwips)); } }

这里单位是twip,1英寸等于1440 twips,1厘米约等于567 twips。比如你想把第一列设为4厘米,直接传2270左右即可。

还有一点,设置单元格宽度之前,建议先把整张表格的表格级宽度布局方式改成STTblLayoutType.FIXED,不然Word会按内容自动调整列宽,你的手工设置照样被覆盖。

CTTblPr tblPr = table.getCTTbl().getTblPr(); if (tblPr == null) { tblPr = table.getCTTbl().addNewTblPr(); } tblPr.addNewTblLayout().setType(STTblLayoutType.FIXED);

5.4 用POI处理超大Excel数据时的内存优化

最后补一个处理大文件时的老规矩:POI的XSSFWorkbook会一次性把整个工作簿读进内存,几万条POI数据的统计报表可能没事,但如果工作簿里有几十万行,内存很容易被撑爆。

读取模式用:

try (InputStream is = new FileInputStream("poi_data_2026.xlsx")) { Workbook wb = StreamingReader.builder() .rowCacheSize(100) .bufferSize(4096) .open(is); }

这里其实用的不是原生POI,是com.monitorjbl:poi-streaming这个库,底层基于SAX事件模型,逐行读取,内存占用能降一个数量级。如果是写大量数据,直接用原生POI的SXSSFWorkbook,开启窗口模式,及时把旧数据刷到磁盘。这是我在处理全国范围POI数据生成Excel时最常用的方案。

6. 常见问题快查表与避坑心得

6.1 常见问题速查表

下面这张表是我这几年在处理省市POI数据过程中,遇到频率最高的几个问题,直接抄作业就行。

现象可能原因解决办法
聚合后的城市数量与官方数量不符坐标落在边界,行政区划字段被标错用省市区边界做空间匹配,覆盖原字段
同一街区POI数量翻倍数据源重复率高,未做去重使用GeoHash加60米阈值去重
POI点落偏到隔壁道路坐标系混用GCJ-02与WGS84先确认坐标系,再统一做坐标转换
Java解析xlsx报Zip bomb文件实际压缩比异常高或确实过大调大ZipSecureFile.setMinInflateRatio或直接换流式读取
Word表格单元格宽度设了没生效表格布局是自动模式设置表格布局为FIXED,并用CTTcW写宽度
解析Excel时服务器内存飙高XSSFWorkbook全量加载改用流式读入或SXSSFWorkbook写出
导出的Excel中文乱码字符集设置不对明确使用UTF-8,避免平台默认编码

6.2 我踩过的几个坑和现在的处理习惯

第一个坑是坐标系统一。早期我拿过一份四川的数据,文件名写着WGS84,实际用的是GCJ-02,我拿它跟GPS轨迹去做匹配,结果每个点都偏移了四五百米,整整调了半天才发现是坐标基准问题。现在我的习惯是任何数据入库前,先随机抽几十个点叠加到在线地图上肉眼检查,这一招比信任何文档都靠谱。

第二个坑是GeoHash去重的误杀。有几家商场紧挨着,中间距离就几十米,用7位GeoHash粗排后距离计算,结果把两家店当成一家删掉。后来处理连锁品牌数据时,我先按名称做一次精确匹配,再做GeoHash距离去重,并且把类别字段纳入去重条件,只在同大类内部判断,误杀率一下就降下来了。

第三个坑是直接把Excel当成数据库用。前期有些中间结果我图省事,一直用Excel存,每次清洗重新跑一次全链路,直到有一次把原始数据和中间结果混在一个目录下,脚本读取时串了文件,浪费了两天才补回数据。从那以后,我坚持把原始数据、中间数据、结果数据分目录管理,原始文件改成只读属性,脚本里也写了绝对路径白名单,防止手滑覆盖。

做省市级POI数据分析,真正难的不是算法有多复杂,而是数据治理习惯有多规范。拿到的数据先做体检,坐标统一、去重、字段标准化这三个动作做到位,后面80%的分析需求都能顺畅跑通。如果你正准备拿一套2026年的分省分市POI数据做项目,把这篇文章提到的检查流程过一遍,至少能帮你少走一个月弯路。

最后再分享一个小技巧:无论你用的是Python还是Java,处理POI数据时都建议把坐标精度保留到六位小数以上,不要因为嫌数字长就四舍五入。经度纬度小数点后第六位对应约0.11米的精度,一旦四舍五入到四位,点位可能就漂移了十几米,这对判断一个点到底在哪栋建筑、哪个商铺,影响是非常大的。数据精度这个东西,越是到省市细分之后,越能拉开差距。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询