☰
开店选址调研:用地图 API 做竞对密度盘点
2026/9/28 19:20:48 网站建设 项目流程

开店选址调研:用地图 API 做竞对密度盘点

开奶茶店、咖啡店、健身房之前,谁都想知道一个问题:这条街上已经有多少家同行了?传统做法是踩点、数铺、问中介,一天能看两条街。用地图 API 可以一晚扫完半个城区,而且结论是可复算的——同一个坐标、同一个缩放级别,下个月再跑一遍还能对比。

这篇给一个"竞对密度盘点"脚本:划定候选区域网格 → 逐格采集同行 → 按网格统计密度 → 输出一张能直接贴进选址报告的 CSV。参数与字段以 SerpBase 官方文档的 maps/search 端点说明 为准;每次成功请求 2 credits,动手前先把成本算清楚。

为什么用网格而不是一个城市中心点

选址调研最容易犯的错:拿城市中心点 + 一个大范围搜一把,看到"全城 300 家咖啡店",然后呢?这个数字对选址毫无意义——你要知道的是候选铺位周边 500 米有几家。

所以做法是网格化:把候选区域切成若干格,每格用"中心点 + zoom"圈定范围单独采集,统计落在每格里的同行数量。哪格密度低、哪格密度高,一张表看清楚。文档里lat/lng是可选参数但必须成对发送,zoom取值 1-21、传坐标时默认 14——这两条决定了你的网格设计:

  • zoom 决定单格覆盖范围。zoom 14 约是区县尺度,适合初筛;锁定候选商圈后用 zoom 15/16 细看。
  • 步长(相邻格中心点的距离)和 zoom 要配套。网格重叠会重复计门店(浪费 credits),网格有缝会漏门店。宁重叠勿留缝——反正按feature_id去重。

密度盘点脚本

importcsv,json,pathlib,requests API="https://api.serpbase.dev/google/maps/search"KEY="你的 API Key"HEADERS={"X-API-Key":KEY,"Content-Type":"application/json"}defgrid_centers(center_lat:float,center_lng:float,rows:int,cols:int,step_deg:float):"""以中心点为中心生成 rows×cols 的网格中心点。"""r0,c0=(rows-1)/2,(cols-1)/2foriinrange(rows):forjinrange(cols):yield(round(center_lat+(i-r0)*step_deg,6),round(center_lng+(j-c0)*step_deg,6))deffetch_cell(q:str,lat:float,lng:float,zoom:int)->list:resp=requests.post(API,headers=HEADERS,json={"q":q,"lat":lat,"lng":lng,"zoom":zoom},timeout=30)data=resp.json()ifdata.get("status")!=0:raiseRuntimeError(f"{lat},{lng}:{data.get('status')}{data.get('error')}")returndata.get("places",[])defdensity_survey(q:str,center_lat:float,center_lng:float,rows:int=3,cols:int=3,step_deg:float=0.015,zoom:int=15)->list:"""逐格采集竞对,按 feature_id 去重后统计每格密度。"""grid_dir=pathlib.Path("scout_grid")grid_dir.mkdir(exist_ok=True)cells,seen=[],set()foridx,(lat,lng)inenumerate(grid_centers(center_lat,center_lng,rows,cols,step_deg)):cache=grid_dir/f"cell_{idx}_{lat}_{lng}.json"ifcache.exists():places=json.loads(cache.read_text("utf-8"))else:places=fetch_cell(q,lat,lng,zoom)cache.write_text(json.dumps(places,ensure_ascii=False),encoding="utf-8")fresh=[]forpinplaces:fid=p.get("feature_id")iffidandfidnotinseen:seen.add(fid)fresh.append(p)cells.append({"格号":idx,"格心纬度":lat,"格心经度":lng,"竞对数量":len(fresh),"平均评分":(round(sum(p["rating"]forpinfreshifp.get("rating"))/max(1,len([pforpinfreshifp.get("rating")])),2)),"代表商户":"、".join(p["name"]forpinfresh[:3]),})returncellsif__name__=="__main__":# 示例:以某商圈为中心扫 3×3 格,找奶茶店cells=density_survey("奶茶店",31.2304,121.4737,rows=3,cols=3,step_deg=0.015,zoom=15)total=sum(c["竞对数量"]forcincells)withopen("scout_density.csv","w",newline="",encoding="utf-8-sig")asf:w=csv.DictWriter(f,fieldnames=list(cells[0].keys()))w.writeheader()w.writerows(cells)print(f"9 格共{total}家竞对 → scout_density.csv")forcincells:bar="█"*c["竞对数量"]print(f"格{c['格号']}:{c['竞对数量']:>3}家{bar}(均分{c['平均评分']})")

三条选址判读经验

  1. 密度要分类目看,别只看总数。"300 家咖啡店"里 250 家是连锁(星巴克、瑞幸),50 家独立店——对独立店选址者来说,真正的竞争信号是后者。脚本采回来之后,按名称把连锁品牌摘出来单独统计:连锁密度高的区域说明市场被验证过、但也说明点位被占得狠;独立店密度低 + 连锁也少的格,才可能是空白市场。
  2. 评分分布比数量更早暴露问题。某格竞对数量中等,但平均评分只有 3.6,而其他格普遍 4.3——这通常意味着这个格的服务/产品供给有问题(或者数据里混进了加油站便利店)。反过来,高密度 + 高评分并存,说明这里是成熟商圈,拼的是差异化不是空白。
  3. 先小网格试跑,再放大。第一晚先跑 1×1 或 3×3,看看你选的城市层级、品类词返回的密度。咖啡店在上海核心区一格可能 40 家,在二线城市一格 5 家——密度差一个量级,你的步长和 zoom 就得重新配。不要第一晚就上 5×5,2 credits 一格,25 格就是 50 credits,配错了参数等于白烧。

成本账

每格一次请求 = 2 credits:3×3 试跑 18 credits,5×5 放大 50 credits。想覆盖全城按 10×10 算就是 200 credits——$10/20k 的标准包够跑好几个城区,但请先算"行×列×品类数×轮次"再动手。如果还要对入围竞对拉详情(营业时间、电话、地址),每店再 2 credits,10 家入围就是 20,采集前先按名单筛一轮,别对网格里所有商户无脑拉详情。

FAQ

为什么返回里找不到评价数字段?地图结果的字段里只有rating(评分),没有评价数(没有 review_count 字段)。所以口碑分析用评分分布做,别承诺"按评价量排序"——文档化载荷里没这个数。想要评价量得换数据源。

候选点位怎么从网格落到具体铺位?网格给的是区域级密度结论(哪几格值得实地看),不是铺位推荐。把密度最低的 2-3 格连同"代表商户"列一起交给线下踩点,线上排查 + 线下验证,比纯线上或纯线下都快。

多久复跑一次对比?建议按月。每次复跑用完全相同的中心点、步长、zoom 和查询词,结果另存为带月份的文件,然后对比"竞对数量"和"平均评分"两列的变化。数量持续上涨说明商圈在升温(租金也会),评分下滑说明供给质量在稀释——这两个信号比任何中介的话都实在。

中文品类词要处理编码吗?不用,JSON body 直接传 UTF-8,请求头带Content-Type: application/json即可。

先跑 3×3 拿一张密度热力图,再决定去实地看哪几格——这就是"用地图 API 做选址初筛"最省钱的打开方式。

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

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

立即咨询