SerpBase 地图两阶段采集:place_id、data_id 别混,成本先算
做本地商家分析,第一反应是调 /google/maps/search。但列表里只有名称、地址、评分、坐标——要拿营业时间、照片、评论,必须再调详情和评论端点。问题是:三个端点用的是三种不同的 id,混用一个就查不到,或者查到别家。
这篇把两阶段采集的 id 映射和成本算清楚。
三个 id 从哪来、用在哪
Maps 搜索(POST /google/maps/search,2 credits/次,按 query + location.location_query 查)返回的每条 places 里有四个标识:
| 字段 | 形态 | 用途 |
|---|---|---|
place_id |
ChIJ... 开头的字符串 |
详情端点用这个(参数名是 id) |
data_id |
0x...:0x... 形态 |
评论端点用这个(参数名是 data_id) |
feature_id |
0x...:0x... |
与 data_id 同源,做内部关联 |
cid |
数字串 | 拼 Google Maps 链接 |
详情:POST /google/maps/detail,3 credits/次,参数 {"id": place_id}。
评论:POST /google/maps/reviews,2 credits/次,参数 {"data_id": data_id}。
最容易犯的错,是把 data_id 传给详情端点——两个都是 0x... 开头,看着一模一样,但语义不同。
两阶段采集的代码骨架
import os
import requests
HEADERS = {"Content-Type": "application/json",
"X-API-Key": os.environ["SERP_BASE_API_KEY"]}
BASE = "https://api.serpbase.dev"
def places(query: str, location: str, page: int = 1) -> list[dict]:
data = requests.post(f"{BASE}/google/maps/search", headers=HEADERS,
json={"query": query,
"location": {"location_query": location},
"page": page},
timeout=(5, 30)).json()
return data.get("places", []) if data.get("status") == 0 else []
def detail(place_id: str) -> dict:
data = requests.post(f"{BASE}/google/maps/detail", headers=HEADERS,
json={"id": place_id}, timeout=(5, 30)).json()
return data.get("place", {}) if data.get("status") == 0 else {}
def reviews(data_id: str) -> list[dict]:
data = requests.post(f"{BASE}/google/maps/reviews", headers=HEADERS,
json={"data_id": data_id}, timeout=(5, 30)).json()
return data.get("reviews", []) if data.get("status") == 0 else []
for p in places("咖啡", "Seattle, WA")[:20]: # 只对前 20 家做二次调用
d = detail(p["place_id"])
r = reviews(p["data_id"])
print(p["name"], d.get("open_status"), len(r))
成本:先算,再决定拉多少
地图搜索每页返回 20 家(每页一次请求,2 credits)。三段价格:搜索 2c、详情 3c、评论 2c。
- 全量两阶段:100 家 = 搜索 5 页(10c)+ 详情 100 次(300c)+ 评论 100 次(200c)= 510 credits
- 分层采集:只对排名前 20 家做详情和评论 = 10 + 60 + 40 = 110 credits
两者差近五倍,而分析价值通常集中在头部商家。先搜全量,再对 Top N 做深挖,是这类采集的默认姿势。
存储:主表 + 快照表
places主表以place_id为唯一键,存data_id、feature_id、cid、名称、坐标place_details存详情(含open_status、照片),按采集时间保留版本place_reviews用「快照 + 追加」:评论数会变,覆盖会让历史消失
评论是非结构化的长文本,单独存表,别塞进主表的一个 JSON 字段——后面做关键词提取、情感分析,都要逐条处理。
五个坑
坑 1:把 data_id 传给详情端点。 查不到或报参数错误,参数错误不计费,但任务失败。记住:详情用 place_id(参数 id),评论用 data_id。
坑 2:对搜索结果全量做两阶段。 510 credits 一次跑完,免费额度当场清零。先分层。
坑 3:以为评论能翻页拿全。 当前评论端点返回的是「第一页」,没有完整历史。做趋势分析时要知道这是抽样。
坑 4:把 open_status 当静态字段。 它是采集时刻的快照,「营业中」明天可能变成「已打烊」,别写成固定值。
坑 5:同名商家不去重。 连锁店同名不同址,唯一键必须用 place_id,不能用名称。
小结
地图数据的关键不是调用本身,而是id 映射和分层成本:搜索拿列表(2c)→ 记下 place_id 与 data_id → 只对 Top N 调详情(3c)和评论(2c)→ 主表用 place_id 去重。三件事做对,本地商家数据集就稳了。
三个端点的参数与字段以 SerpBase 文档 为准,产品页上有每个端点的请求示例。你们做本地数据采集时,二次调用的比例是多少?评论区聊聊。

浙公网安备 33010602011771号