简而言之:要从谷歌地图抓取评论,主要有三种方法:使用轮换代理后端自建的 Selenium 抓取工具、带渲染指令的抓取 API,或者返回已解析 JSON 数据的结构化地图评论 API。本指南将通过 Python 详细介绍这三种方法,提供可直接复制粘贴的代码、分页模式、防封策略,以及最后的数据清洗步骤,将原始评论转化为企业实际可用的数据。
简介
如果您曾尝试过以实际规模从谷歌地图抓取评论,想必已经深知其中的痛点:官方的 Places API 每个地点最多只返回寥寥数条评论,列表内容主要由 JavaScript 渲染,而 CSS 类钩子频繁变更,甚至可能导致抓取工具在周五到周一期间就失效。好消息是,这些数据是可以获取的,您只需选择合适的工具即可。
本指南面向希望从 Google 地图获取商业评论的 Python 开发者、数据分析师和增长团队,这些评论可用于情感分析、竞争对手研究、位置情报或潜在客户开发。我们将并行介绍三种方法:结合 Selenium 和代理以实现完全控制;使用带渲染指令的抓取 API 以减少编码工作量;以及当您需要像 review_id, iso_date_of_last_edit以及发布者回复等干净、结构化的字段时,可采用的按评论获取的 JSON API。
您将获得可运行的代码、用于论证所选方案的对比表,以及用于清理和分析所收集数据的简短处理流程。对于已知会发生漂移的底层选择器或配额,本文会特别指出,以便您提前规划,而非在凌晨两点进行调试。
如何从 Google 地图抓取评论:商业价值及可提取的字段
评论是公开网络上信号强度最高的数据集之一。一份干净的地图评论数据流,可让您衡量竞争对手的客户体验、监控各分店的声誉、构建基于位置的潜在客户列表、计算区域情绪指数,或利用真实的人类评论训练推荐模型。若仅拥有星级平均分,上述功能均无法实现——而这恰恰是大多数通用商家信息抓取工具的局限所在。
实际上,从谷歌地图中提取的数据字段可分为两层。在商家信息层,您可以获取地点名称、地址、总体评分、评论数量、类别、营业时间、电话号码以及堂食或外卖等服务选项。 在单条评论层,您可以获取评论正文(摘要)、评论者姓名、个人资料链接、星级评分、评论日期或最后编辑日期、图片链接、店主回复(如有)以及“服务”或“氛围”等主题标签。提前了解这一划分,有助于您判断仅进行商家信息层面的抓取是否足够,还是需要深入挖掘单条评论。市场调研和评论监测等应用场景几乎总是需要第二层数据。
提取地图评论数据的三大途径:官方 API vs 自建爬虫 vs 爬虫 API
在研究如何从 Google 地图抓取评论时,通常会出现三种路径。选择错误的路径是团队半途而废的最常见原因,因此请在编写代码前花几分钟仔细斟酌。
官方的 Google Places API 在法律层面最为合规,但它专为应用集成设计,而非分析用途。根据 Google 的文档,位置详情请求仅返回地图界面中显示的评论的一小部分(且有上限),这使得它不适合任何规模的情感分析。 自建的 Selenium 爬虫配合住宅代理,能让你完全掌控数据,并获得与未登录用户完全相同的视图,但代价是需要运行浏览器并追踪 CSS 变更。中间层的爬取 API 可处理代理轮换、验证码识别和无头渲染,而其上层的每条评论 JSON API 还能承担解析工作。
请将下表作为决策依据。
|
方法 |
部署时间 |
扩展上限 |
反机器人处理 |
维护 |
何时使用 |
|---|---|---|---|---|---|
|
官方 Google Places API |
低 |
低(按地点的评论上限) |
内置,但受配额限制 |
低 |
将少量评论嵌入您自己的产品界面 |
|
自行搭建 Selenium 并配合代理 |
中等 |
中等 |
手册:代理、等待、重试 |
高(CSS漂移) |
完全控制、自定义流程、中等流量 |
|
SERP API |
低 |
高 |
由提供商处理 |
低 |
可重复任务、定时爬取 |
|
按次审查的 JSON API |
最低 |
高 |
由服务商处理 |
最低 |
字段干净,无需解析,获取数据的最快途径 |
Google 地图特有的反机器人验证
与普通电商网站相比,谷歌地图更难抓取,原因却出乎意料。首先你会遇到 Cookie 同意墙,除非将其关闭,否则搜索结果面板无法显示。其次,谷歌地图完全基于 JavaScript 运行,因此简单的 requests.get 返回的页面几乎是空的。第三点,也是最常导致爬虫失败的原因,是列表面板采用滚动加载而非分页 URL。不存在 ?page=2 可抓取的资源。
此外,Google 还会混淆并轮换 CSS 类名。诸如 hfpxzc, MW4etd, UY7F9和 Nv2PK 在撰写本文时有效,但在发布或运行前应重新验证,因为它们经常变更。来自单一 IP 的激进请求模式还会触发速率限制和偶尔的 reCAPTCHA 验证,因此一旦查询量超过少量,使用住宅代理池和随机化等待就不可或缺。
先决条件:Python、Chrome、相关库及 API 密钥
您需要 Python 3.10 或更高版本,以及最新安装的 Chrome 浏览器。Selenium 路径还需安装几个包。使用 pip 安装:
pip install selenium selenium-wire webdriver-manager beautifulsoup4 lxml requests每个组件都各司其职。Selenium 负责驱动浏览器。Selenium Wire 是一个轻量级扩展,它将底层的 HTTP 请求暴露出来,让你无需调整 Chrome 标志即可连接经过身份验证的代理。Webdriver Manager 管理 ChromeDriver 二进制文件,因此你无需在每台笔记本电脑上固定特定版本。使用 lxml 后端的 BeautifulSoup 解析渲染后的 HTML,而 requests 库足以满足 API 相关操作的需求。
对于方法 2 和 3,请在开始前注册一个 SERP API 账户。通常可以领取免费额度用于测试,然后将密钥填入下方的代码块中。
方法 1 - Selenium 配合代理的实操指南
关于如何从 Google 地图抓取评论的教科书式答案是:使用真实浏览器配合轮换的住宅代理。这是覆盖面最广且控制力最强的方法。 您需驱动一个 Chrome 实例,将其流量通过代理转发,滚动页面直至加载足够多的列表,并使用 BeautifulSoup 解析页面源代码。当您需要灵活性(自定义点击流程、截图捕获、需登录的商家资料)或项目规模较小、足以手动调试选择器时,这是最佳选择。下文的五个子章节将详细演示每个步骤,并提供可直接粘贴到脚本中的代码。
配置 Selenium Wire 使 Chrome 通过代理
使用 Selenium 抓取 Google 地图评论的关键在于将每个请求通过轮转的住宅代理发送。普通的 Selenium 无法干净地传递 HTTP 基本认证凭据,这就是 Selenium Wire 的用武之地。它将代理模块暴露在 seleniumwire_options 中,并在后台处理身份验证握手。大多数提供商期望的代理 URL 格式如下: http://<username>:<password>@<host>:<port>,并包含诸如 render 或 country_code 等附加参数。在生产环境中运行前,请务必对照您所用服务商的最新文档确认确切格式,因为 URL 规范各不相同。
from seleniumwire import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
API_KEY = "YOUR_API_KEY"
PROXY = (
f"http://scrape.render=true.country_code=us:{API_KEY}"
"@proxy-server.example.com:8001"
)
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
sw_options = {"proxy": {"http": PROXY, "https": PROXY, "no_proxy": "localhost,127.0.0.1"}}
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()),
seleniumwire_options=sw_options,
options=options,
)
driver.implicitly_wait(30)无头模式可降低服务器内存占用,而 30 秒的隐式等待时间能确保在触发任何选择器查找之前,地图有足够时间完成渲染。若需更深入的 Selenium 入门指南,请查阅语言绑定文档,其中详细介绍了所有选项。
加载 Google 地图并关闭 Cookie 同意对话框
驱动程序配置完成后,请将其指向搜索 URL(例如 https://www.google.com/maps/search/pizza+in+new+york)。地图通常会在会话首次加载时弹出 Cookie 同意对话框,且在关闭该对话框前,页面布局不允许您滚动浏览搜索结果。请将点击操作包裹在 try/except 块中,因为某些地区的代理渲染流量会完全跳过该对话框。
from selenium.webdriver.common.by import By
driver.get("https://www.google.com/maps/search/pizza+in+new+york")
try:
accept_btn = driver.find_element(
By.XPATH, "//button[.//span[contains(text(), 'Accept all')]]"
)
accept_btn.click()
except Exception:
print("Cookie consent not shown for this session.")请务必记录此异常,而非让异常向上传播。当对话框缺失时,您希望测试继续运行,而非中止。如果您的地区渲染了不同的按钮标签,请替换 XPath 文本或改用 aria-label 匹配同意横幅。
滚动结果面板以触发延迟加载的列表
Google 地图不支持分页。当你滚动左侧面板时,系统会将更多列表流式加载到 DOM 中,直至遇到结束标记。若要在合理范围内从 Google 地图提取评论,必须手动驱动滚动操作。有两种行之有效的方法:第一种通过 ActionChains,这种方式模拟真实用户行为。第二种使用 JavaScript 执行器,速度更快但更容易被识别。
import time
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
def scroll_panel_down(driver, panel_xpath, presses=5, pause_time=1):
panel = driver.find_element(By.XPATH, panel_xpath)
actions = ActionChains(driver)
actions.move_to_element(panel).click().perform()
for _ in range(presses):
actions.send_keys(Keys.PAGE_DOWN).perform()
time.sleep(pause_time)
# Cleaner JS-executor alternative
def scroll_panel_js(driver, panel_xpath, rounds=8, pause=1.2):
panel = driver.find_element(By.XPATH, panel_xpath)
for _ in range(rounds):
driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", panel)
time.sleep(pause)调整 presses 并 pause_time 针对目标查询进行调整。五次向下滚动并每次暂停一秒是一个合理的起点,但针对密集城市区域的查询可能需要 15 轮或更多轮操作,面板才会报告 'You've reached the end of the list'。请观察渲染出的 HTML,当新列表停止出现时停止操作。
使用 BeautifulSoup 解析商家名称、评分和评论数量
一旦面板加载了足够的列表,将页面源代码交给 BeautifulSoup 并提取相关字段。下文中的类钩子在撰写本文时有效,但 Maps 会按其自身时间表进行轮换,因此在正式运行前务必执行最新的 DOM 检查。
from bs4 import BeautifulSoup
soup = BeautifulSoup(driver.page_source, "lxml")
# These class names rotate. Re-verify before each run.
titles = soup.find_all("a", class_="hfpxzc") # listing anchors
ratings = soup.find_all("span", class_="MW4etd") # numeric rating
counts = soup.find_all("span", class_="UY7F9") # review count
places = []
for i, t in enumerate(titles):
place = {
"name": t.get("aria-label") or t.get_text(strip=True),
"url": t.get("href"),
"rating": ratings[i].get_text(strip=True) if i < len(ratings) else "N/A",
"review_count": counts[i].get_text(strip=True) if i < len(counts) else "N/A",
}
places.append(place)此处有两个关键的容错习惯。首先,建议优先使用 aria-label 属性,而非可见文本,因为标签在 UI 调整中更稳定。其次,使用索引安全的查找,以免评分缺失导致循环崩溃。若需更深入的 BeautifulSoup 入门指南,关于解析模式的内部教程是了解高级选择器策略的绝佳参考。
将结果保存为 CSV 并设置 N/A 备选值
方法 1 的最后一步是将解析后的列表写入磁盘。对于一次性任务,CSV 格式已足够,而后续只需一行代码即可切换至 Parquet 或数据库。
import csv
with open("maps_pizza_places.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["name", "rating", "review_count", "url"])
for p in places:
writer.writerow([
p.get("name") or "N/A",
f"{p['rating']}/5" if p["rating"] != "N/A" else "N/A",
p.get("review_count") or "N/A",
p.get("url") or "N/A",
])
driver.quit()缺失字段时,请始终将其默认设置为 N/A ,而非留空。空单元格在下游管道中容易引起歧义(是字段缺失,还是抓取失败?),且会增加数据质量检查的难度。在结尾调用 driver.quit() 可释放 Chrome 进程及其持有的所有代理连接。
方法 2 - SERP API(低代码路径)
方法 1 适用于原型开发,但实现较为繁琐。若要大规模抓取 Google 地图上的评论,更简洁的生产级解决方案是将渲染和代理轮换委托给抓取 API,并发送一组 JSON 指令,告知托管的无头浏览器在页面上执行哪些操作。您将获得相同的最终 HTML 结果,同时避免了本地浏览器的开销,且服务商将承担原本需要您自行处理的重试和代理轮换工作。
大多数服务商(包括我们的 WebScrapingAPI SERP API)都支持在请求头中通过 JSON 指令集描述滚动、点击和等待操作。下面的示例通过单次请求关闭 Cookie 提示框、滚动结果面板数次,并返回最终的 HTML 内容。请务必对照服务商当前的文档确认确切的请求头名称和指令模式,因为这些细节可能有所不同。
import json, requests
from bs4 import BeautifulSoup
API_KEY = "YOUR_API_KEY"
TARGET_URL = "https://www.google.com/maps/search/pizza+in+new+york"
instructions = [
{"type": "click", "selector": "button[aria-label='Accept all']", "optional": True},
{"type": "scroll", "selector": "div[role='feed']", "repeat": 8, "delay": 1500},
{"type": "wait", "value": 2000},
]
headers = {
"x-api-key": API_KEY,
"x-render-js": "true",
"x-instruction-set": json.dumps(instructions),
}
resp = requests.get("https://api.example-scraper.com/v1",
params={"url": TARGET_URL}, headers=headers, timeout=120)
soup = BeautifulSoup(resp.text, "lxml")
listings = soup.find_all("div", class_="Nv2PK") # re-verify class before each run
print(f"Got {len(listings)} listings.")有三点理由使这种模式值得采用。首先,您无需在每个 worker 中运行并更新 Chrome。其次,服务商会为您轮换代理并重试失败的请求,从而消除了大部分日常运维的繁琐工作。第三,整个处理流程可以部署在 cron 任务或无服务器函数中,完全不依赖浏览器。
方法 3 - 提取单条评论字段(文本、日期、排序、主题筛选)
列表级别的抓取能告诉你哪些地点重要,但真正的商业价值在于更深一层——即实际的评论文本。关于如何以单条评论为单位从 Google 地图抓取评论,最简洁的答案是完全跳过解析步骤,直接使用结构化的 Maps Reviews API。你只需查询一次该地点的 data_id ,随后使用所需参数调用评论接口。
其结构直观易懂。 engine 选择 Maps Reviews 引擎。 data_id 标识地点。 sort_by 控制排序: qualityScore (默认,相关性最高), newestFirst, ratingHigh,或 ratingLow. topic_id 筛选特定主题,例如从结构化回复中提取的设施主题 ID(简报中标注了一个用于验证的示例 ID /m/0f3yyn 作为验证示例)。可选字段如 Snippet, Response和 Response.iso_date 采用零索引,即第一条评论位于索引 0。
import requests
params = {
"engine": "google_maps_reviews",
"data_id": "0x80dcd1f0c5cb6f53:0xd2c4f5c30e2b7c5a",
"sort_by": "newestFirst",
"api_key": "YOUR_API_KEY",
}
r = requests.get("https://api.example-reviews.com/search", params=params, timeout=60).json()
for rev in r.get("reviews", []):
print({
"user": rev.get("user", {}).get("name"),
"rating": rev.get("rating"),
"date": rev.get("iso_date_of_last_edit"),
"text": rev.get("snippet", "No review text, rating only"),
"owner_response": rev.get("response", {}).get("snippet", "No response from owner"),
})始终默认省略缺失字段。评论者常仅留下星级评分而无文字说明,且大多数房东从未回复,因此 JSON 数据中会存在缺失部分。
分页及获取首批评论之外的内容
无论是 API 还是 DIY 方案,首批评论的数量都有限制。根据提供商的文档,典型的 Maps Reviews API 每次请求返回约 10 条评论,并在响应中提供一个 next_page_token (或等效光标)。分页循环的逻辑与任何基于令牌的 API 相同:持续调用直至令牌字段缺失。
all_reviews, token = [], None
while True:
p = dict(params, **({"next_page_token": token} if token else {}))
r = requests.get(URL, params=p, timeout=60).json()
all_reviews.extend(r.get("reviews", []))
token = r.get("serpapi_pagination", {}).get("next_page_token")
if not token:
break在 Selenium 端,分页操作即为在地点卡片“评论”标签页内持续滚动。需留意“您已到达列表末尾”提示,并在连续两次滚动后渲染的评论数量不再增加时停止操作。
规避 IP 封禁、验证码及选择器失效
一旦掌握了机械化抓取 Google 地图评论的方法,长远来看关键在于保持访问畅通。抓取量与隐蔽性之间存在权衡,而非固定设置。最重要的控制手段是你的代理池。数据中心 IP 在地图查询中被标记的速度远快于住宅 IP,因此一旦每小时请求量超过几十次,就应计划轮换使用具有国家级定位的住宅 IP。 在操作间加入随机等待时间(300 至 1500 毫秒是合理范围),遇到 429 状态码时采用指数级退避策略,并始终限制重试次数,以免单个故障目标耗尽你的全部配额。
选择器漂移是第二种故障模式。请将钩子视为 hfpxzc, MW4etd,并 Nv2PK 等选择器具有不稳定性。构建一个轻量级的 DOM 监控步骤,当选择器不再匹配任何内容时应立即发出明显警告,尽可能优先使用 aria-label 和结构化选择器,并在每个关键字段保留至少一个备用方案。关于常见阻塞信号及其规避方法的简短提示表值得收藏,而我们团队关于避免IP封禁的详尽指南则深入探讨了头部轮换和指纹识别(当原本稳定的脚本突然开始返回403错误时,这是一份有用的补充阅读)。
清理、存储和分析抓取的评论数据
这一切的最终目标是获得可直接用于决策的数据,而非一个庞大的 CSV 文件。通过简短的后处理流程,即可将原始评论转化为产品或营销团队能够付诸行动的信息。
import pandas as pd
from textblob import TextBlob
df = pd.DataFrame(all_reviews)
df = df.drop_duplicates(subset=["review_id"]) # dedupe
df["rating"] = df["rating"].astype(float).clip(0, 5) # normalize
df["text"] = df["snippet"].fillna("").str.replace(r"[\u200b-\u200f]", "", regex=True)
df["sentiment"] = df["text"].apply(lambda t: TextBlob(t).sentiment.polarity if t else 0.0)四个步骤即可完成大部分工作:通过 review_id,将评分缩减至0-5分范围,从文本中剔除零宽度字符和表情符号噪声,并利用轻量级NLP库为每行数据标注情感极性。此后,只需通过Pandas转SQL或Pandas转数据仓库的简单步骤即可。该输出结果正是驱动评论监控仪表盘或竞争对手情感基准分析的动力,从而将流程闭环回到了开篇提到的应用场景。
关键要点
- 官方的 Google Places API 虽然方便将少量评论嵌入产品中,但其按地点设定的限制使其不适合用于分析或竞争对手研究。
- Selenium 配合住宅代理的组合能提供最大的控制权。需预先规划应对 Cookie 提示栏、滚动加载以及轮换的 CSS 类钩子(如
hfpxzc以及Nv2PK. - 一个带有 JSON 渲染指令集的抓取 API 完全绕过了本地浏览器,是执行定时、可重复任务的最简洁路径。
- 结构化的 Maps Reviews API 是获取干净的单条评论字段的最快途径。请使用
sort_by,topic_id和next_page_token来控制数据深度和切片。 - 将数据处理流程视为一个整体。通过
review_id进行去重、规范化评分、过滤噪音并标记情感倾向,确保输出结果不仅被存储,更能直接用于决策。
常见问题
抓取谷歌地图评论是否合法?
在许多司法管辖区,用于分析或研究的公开评论数据通常被视为可抓取的,但谷歌的服务条款限制了对其产品的自动化访问,且美国关于公共数据抓取的判例法仍在不断演变。请务必查阅地图服务条款,遵守 robots.txt,避免抓取超出公开可见范围的个人数据,并在商业使用前咨询法律顾问。
为什么不直接使用官方的 Google Places API 而不是爬虫?
Places API 存在调用频率限制,需要付费的 Google Cloud 项目,且返回的评论仅是地图界面显示内容的一小部分。这使其适合将少量评论嵌入产品,但无法用于情感分析、竞争对手基准测试或任何需要具有代表性样本的用例。
不使用 Selenium 或无头浏览器能抓取 Google 地图评论吗?
仅使用原始 requests无法实现,因为地图是一个高度依赖 JavaScript 的单页应用(SPA)。切实可行的替代方案是使用代管无头浏览器的爬取 API,或者使用结构化评论 API,该 API 会返回已解析的 JSON 数据,而无需在客户端进行任何渲染。
Google 地图的 CSS 类名变更频率如何?我该如何确保爬虫持续有效?
变更频率足够高,因此不应假设任何类钩子是永久的。可通过 aria-label 选择器、结构化XPath备用方案、一个在选择器返回零匹配时会发出明显报错的小型DOM监控任务,以及在下次计划运行前向您发送提醒的警报。
每次运行能为每家企业抓取多少条评论?分页机制如何运作?
典型的 Maps Reviews API 每次请求会返回约 10 条评论,并提供 next_page_token 指向下一批次的游标。虽然没有固定的总数上限,但非常旧的评论会越来越难获取。请循环处理令牌直到不再返回,并在 Selenium 端持续滚动评论标签页,直到计数停止增长。
总结
关于如何从 Google 地图抓取评论,并没有唯一的标准答案。正确答案取决于您需要多少数据、需要多频繁地获取,以及您能承受多少运维工作量。若仅需对竞争对手的门店进行一次性审核,Selenium 配合住宅代理就已足够。 但若要构建一个需要持续运行的定期情感分析管道——尤其当 Google 不断轮换类名并加强反机器人防护时——采用托管式抓取 API 或结构化的 Maps Reviews API 才是让你高枕无忧的选择。
无论选择哪种方法,请将输出结果视为数据而非普通 HTML。通过评论 ID 进行去重、标准化评分、过滤噪音、计算情感评分,并将其加载到可查询的数据库中,这样您的工作成果才能在仪表盘或模型中发挥价值,而非沦为过时的 CSV 文件。
如果您不愿仅为跟上地图界面的变化而运行和维护一整支 Chrome 浏览器集群,WebScrapingAPI 提供的 Scraper SERP API 可在单一接口后端自动处理代理轮换、无头渲染和验证码破解,这样您只需保留本指南中的 Python 代码,并替换数据抓取层即可。生成一个 API 密钥,将其指向地图 URL,半天之内即可开始收集干净的评论数据。




