讲师中心 微信公众号
AI工具推荐 视频效率加速

谷歌地图评论数据抓取:Playwright 问题解析与Selenium方案优化

风浩小哥_5926

风浩小哥_5926

发布时间:2025-07-10 09:22:23

|

925人浏览过

|

来源于php中文网

原创

谷歌地图评论数据抓取:playwright 问题解析与selenium方案优化

本文深入探讨了使用Playwright抓取谷歌地图评论数据时遇到的常见问题,特别是评论数量和平均星级无法完整获取的挑战。通过分析现有代码的潜在缺陷,文章提出并详细阐述了如何利用Selenium WebDriver作为更健壮的替代方案,并提供了关键的实现策略,包括元素定位、等待机制、动态内容处理及XPath优化,旨在帮助开发者构建更稳定、高效的谷歌地图数据抓取系统。

1. 问题背景与Playwright实现分析

在尝试从谷歌地图抓取商家信息时,一个常见的问题是无法可靠地获取所有商家的评论数量和平均星级。原始代码片段展示了使用Playwright进行抓取的过程,其中遍历了商家列表,点击每个商家以打开其详细信息面板,然后尝试从中提取数据。

1.1 现有代码的挑战

尽管原始代码成功地获取了部分商家的评论数据(例如,8个商家中只获取了4个),但未能实现完全抓取。这通常源于以下几个核心问题:

  1. 动态内容加载与等待机制不足: 谷歌地图是一个高度动态的单页应用(SPA)。点击列表项后,详细信息面板需要时间来加载其内容。虽然代码中使用了 page.wait_for_timeout(8000),但这种固定时间的等待并不总是可靠,可能导致在元素尚未完全加载时就尝试查找它们,从而失败。
  2. XPath定位策略的误用: 在原始代码中,reviews_span_xpath = f'//div[{index + 1}]//span[@role="img"]' 这行代码在构建XPath时使用了循环变量 index + 1。更关键的是,随后的 reviews_elements = listings[index].locator(reviews_span_xpath) 尝试在 listings[index] 这个 原始列表元素 的上下文中查找评论元素。 然而,当 listings[index].click() 被调用后,谷歌地图会打开一个 新的详细信息面板(通常在屏幕右侧或左侧)。此时,评论信息以及其他详细数据是显示在这个 新面板 中的,而不是在原始的 listings[index] 元素内部。因此,试图在已点击的列表元素内部查找新面板中的元素是无效的。正确的做法应该是在整个 page 或新打开的 详细信息面板的根元素 中查找评论信息。
  3. XPath的脆弱性: 谷歌地图的HTML结构是动态且经常变化的。依赖于 div[{index + 1}] 这种基于索引的XPath或者过于具体的类名可能导致代码在网站结构更新后迅速失效。

2. 推荐方案:利用Selenium实现健壮的抓取

鉴于谷歌地图的动态特性和Playwright在处理特定上下文定位时的潜在挑战,推荐使用Selenium WebDriver。Selenium通过模拟真实浏览器行为,结合其强大的等待机制和灵活的元素定位方法,可以更有效地处理这类动态网站。

2.1 Selenium的优势

  • 直观的浏览器交互: Selenium直接控制浏览器,您可以直观地看到每个操作,便于调试。
  • 强大的等待机制: WebDriverWait 和 ExpectedConditions 允许您根据元素的状态(如可见、可点击、存在)进行智能等待,而非固定时间等待。
  • 灵活的元素定位: 支持多种定位策略,包括XPath、CSS选择器、ID、类名等。
  • 上下文明确: 元素查找可以在整个页面范围内进行,也可以限定在特定父元素内。

2.2 实现策略与示例代码

以下是使用Selenium实现谷歌地图评论抓取的核心步骤和代码示例:

page-agent 浏览器控制(CDP)
page-agent 浏览器控制(CDP)

使用 page-agent CLI(CDP)控制浏览器;CLI只能通过 GitHub Release 的 .tgz 安装,npm 官方包暂未发布。

下载

首先,确保您已安装Selenium和对应的浏览器驱动(例如ChromeDriver)。

pip install selenium

核心逻辑流程:

  1. 初始化WebDriver: 启动浏览器实例。
  2. 导航到谷歌地图: 打开目标URL。
  3. 处理Cookie同意(如果出现): 模拟点击同意按钮。
  4. 遍历商家列表: 找到所有商家列表项。
  5. 点击单个商家: 模拟点击,打开其详细信息面板。
  6. 智能等待详细信息加载: 使用 WebDriverWait 确保详细信息面板中的关键元素(如评论星级)可见。
  7. 提取详细信息: 从已加载的详细信息面板中提取商家名称、地址、评论等数据。
  8. 返回列表视图: 如果需要继续遍历,可能需要点击返回按钮或通过其他方式回到列表。
  9. 数据处理与存储: 清洗和保存抓取到的数据。

示例代码结构:

from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import re
import time

# 定义一个类来存储商家信息
class Business:
    def __init__(self):
        self.name = "N/A"
        self.address = "N/A"
        self.website = "N/A"
        self.phone_number = "N/A"
        self.category = "N/A"
        self.reviews_average = None
        self.reviews_count = None

# 配置WebDriver
# 假设ChromeDriver在系统PATH中,或者指定路径
# service = Service(executable_path='/path/to/chromedriver')
driver = webdriver.Chrome() # service=service

try:
    # 1. 导航到谷歌地图搜索结果页
    driver.get("https://www.google.com/maps/search/restaurants+near+me") # 示例URL

    # 2. 智能等待页面加载并处理可能的Cookie同意弹窗
    # 尝试查找并点击同意按钮 (根据实际页面结构调整XPath/Selector)
    try:
        WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.XPATH, "//button[contains(@aria-label, 'Accept all')]"))
        ).click()
        print("Accepted cookies.")
        time.sleep(2) # 给页面一些时间来处理
    except:
        print("No cookie consent popup found or already handled.")

    # 3. 滚动加载更多商家列表(如果需要)
    # 谷歌地图通常是无限滚动,需要模拟滚动操作
    # 示例:滚动几次以加载更多列表项
    scrollable_div_xpath = '//*[@id="QA0Szd"]/div/div/div[1]/div[2]/div/div[1]/div/div/div[1]/div[1]' # 示例滚动容器XPath
    try:
        scrollable_div = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, scrollable_div_xpath))
        )
        for _ in range(3): # 滚动3次,每次滚动到底部
            driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", scrollable_div)
            time.sleep(3) # 等待新内容加载
            print(f"Scrolled {_ + 1} times.")
    except Exception as e:
        print(f"Could not find scrollable div or error during scroll: {e}")


    # 4. 获取所有商家列表项
    # 使用更稳定的CSS选择器或XPath,例如查找所有带有特定数据属性的列表项
    # 注意:这里的XPath/CSS选择器需要根据实际的谷歌地图HTML结构来确定
    # 示例:查找所有列表中的商家卡片
    listing_elements_xpath = '//div[@role="article" and @aria-label]'
    listings = WebDriverWait(driver, 15).until(
        EC.presence_of_all_elements_located((By.XPATH, listing_elements_xpath))
    )
    print(f"Found {len(listings)} listings.")

    scraped_businesses = []
    total_scraped_reviews = 0

    for index, listing in enumerate(listings):
        try:
            print(f"\n--- Processing listing {index + 1} ---")
            # 5. 点击单个商家列表项
            # 确保元素是可点击的
            WebDriverWait(driver, 10).until(EC.element_to_be_clickable(listing)).click()
            print(f"Clicked listing {index + 1}.")

            # 6. 智能等待详细信息面板加载
            # 找到详细信息面板中的一个稳定元素,例如商家名称或评论区
            # 注意:这里的XPath需要针对详细信息面板的结构进行调整
            # 商家名称在详细信息面板中的XPath
            detail_name_xpath = '//div[contains(@class, "qBF1Pd fontHeadlineSmall ")]'
            WebDriverWait(driver, 15).until(
                EC.presence_of_element_located((By.XPATH, detail_name_xpath))
            )
            print("Detail panel loaded.")

            business = Business()

            # 7. 从详细信息面板中提取数据
            # 这里的XPath不再依赖于原始列表的索引,而是针对当前打开的详细信息面板
            try:
                business.name = driver.find_element(By.XPATH, detail_name_xpath).text
            except:
                business.name = "N/A"

            try:
                # 地址:data-item-id="address"
                business.address = driver.find_element(By.XPATH, '//button[@data-item-id="address"]//div[contains(@class, "fontBodyMedium")]').text
            except:
                business.address = "N/A"

            try:
                # 网站:data-item-id="authority"
                business.website = driver.find_element(By.XPATH, '//a[@data-item-id="authority"]//div[contains(@class, "fontBodyMedium")]').text
            except:
                business.website = "N/A"

            try:
                # 电话:data-item-id="phone:tel:"
                business.phone_number = driver.find_element(By.XPATH, '//button[contains(@data-item-id, "phone:tel:")]//div[contains(@class, "fontBodyMedium")]').text
            except:
                business.phone_number = "N/A"

            try:
                # 类别:通常在名称下方,可能有特定的类或父元素
                # 这是一个示例XPath,可能需要根据实际页面结构调整
                business.category = driver.find_element(By.XPATH, '//*[@id="QA0Szd"]/div/div/div[1]/div[3]/div/div[1]/div/div/div[2]/div[2]/div/div[1]/div[2]/div/div[2]/span/span/button').text
            except:
                business.category = "N/A"

            # 提取评论数据
            # 评论元素通常有一个role="img"和aria-label属性包含星级和数量
            # 这个XPath是针对详细信息面板中的评论元素
            reviews_span_xpath = '//span[@role="img" and contains(@aria-label, "stars")]'
            try:
                # 确保评论元素可见
                reviews_element = WebDriverWait(driver, 5).until(
                    EC.visibility_of_element_located((By.XPATH, reviews_span_xpath))
                )
                reviews_label = reviews_element.get_attribute("aria-label")
                print(f"Reviews Label: {reviews_label}")

                match = re.match(r'([\d.]+) stars ([\d,]+) Reviews', reviews_label)
                if match:
                    business.reviews_average = float(match.group(1))
                    business.reviews_count = int(re.sub(',', '', match.group(2)))
                    total_scraped_reviews += 1
                else:
                    business.reviews_average = None
                    business.reviews_count = None
            except Exception as e:
                print(f"Could not find reviews for this listing or error processing label: {e}")
                business.reviews_average = None
                business.reviews_count = None

            scraped_businesses.append(business)

            # 8. 返回列表视图
            # 查找并点击返回按钮,通常是一个左箭头图标
            # 这个XPath也需要根据实际页面结构调整
            back_button_xpath = '//button[@aria-label="Back"]'
            try:
                WebDriverWait(driver, 5).until(
                    EC.element_to_be_clickable((By.XPATH, back_button_xpath))
                ).click()
                print("Clicked back button.")
                time.sleep(2) # 给页面一些时间返回列表
            except Exception as e:
                print(f"Could not click back button or no back button found: {e}")
                # 如果没有返回按钮,可能需要刷新页面或重新导航,但会影响效率
                # 更好的做法是确保找到并点击返回按钮

        except Exception as e:
            print(f"Error processing listing {index + 1}: {e}")
            # 如果某个商家处理失败,尝试继续下一个
            # 确保返回列表视图,以防卡在详情页
            try:
                back_button_xpath = '//button[@aria-label="Back"]'
                WebDriverWait(driver, 3).until(
                    EC.element_to_be_clickable((By.XPATH, back_button_xpath))
                ).click()
                time.sleep(1)
            except:
                pass # 无法返回,跳过此商家

    print(f"\nTotal businesses scraped: {len(scraped_businesses)}")
    print(f"Total reviews labels processed: {total_scraped_reviews}")

    # 打印抓取结果
    for b in scraped_businesses:
        print(f"Name: {b.name}, Reviews Average: {b.reviews_average}, Reviews Count: {b.reviews_count}")

finally:
    # 关闭浏览器
    driver.quit()
    print("Browser closed.")

3. 关键注意事项与最佳实践

  • XPath的健壮性:
    • 避免绝对XPath: 它们过于脆弱,页面微小变动就会导致失效。
    • 利用属性定位: 优先使用 id、name、class、data-* 属性,以及 aria-label、role 等无障碍属性。例如,//button[@data-item-id="address"] 比 //div[1]/div[2]/button[3] 更稳定。
    • 相对XPath: 在特定父元素内查找子元素,例如 parent_element.find_element(By.XPATH, ".//span[contains(@class, 'some-review-class')]")。
  • 智能等待机制:
    • WebDriverWait + ExpectedConditions: 这是处理动态加载内容的关键。例如,EC.presence_of_element_located(元素存在于DOM中)、EC.visibility_of_element_located(元素可见)、EC.element_to_be_clickable(元素可点击)。
    • 避免 time.sleep(): 除非是调试或特定场景(如等待动画完成),否则尽量避免固定时间等待,因为它效率低下且不可靠。
  • 错误处理: 使用 try-except 块捕获 NoSuchElementException、TimeoutException 等,确保脚本在遇到问题时不会崩溃,并能记录错误信息。
  • 滚动加载: 谷歌地图列表是无限滚动的。需要模拟 execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", element) 来触发加载更多内容。
  • IP封锁与反爬: 大规模抓取可能触发谷歌地图的反爬机制,导致IP被暂时封锁。考虑以下策略:
    • 适当的延迟: 在每次操作之间加入随机的 time.sleep()。
    • 使用代理IP: 定期更换IP地址。
    • 模拟真实用户行为: 随机化点击、滚动、输入等操作。
  • 浏览器 Headless 模式: 在生产环境中,可以启用无头模式(不显示浏览器UI),以节省资源。
    from selenium.webdriver.chrome.options import Options
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    driver = webdriver.Chrome(options=chrome_options)
  • 资源管理: 确保在抓取完成后调用 driver.quit() 关闭浏览器实例,释放资源。

4. 总结

抓取谷歌地图这类动态网站需要对Web抓取原理和目标网站结构有深入理解。Playwright和Selenium都是强大的工具,但对于复杂交互和动态内容,Selenium凭借其成熟的WebDriver API和灵活的等待机制,往往能提供更稳定和易于调试的解决方案。通过采用智能等待、健壮的XPath策略和完善的错误处理,可以显著提高谷歌地图评论数据抓取的成功率和可靠性。请记住,网站结构可能随时变化,因此定期维护和更新您的抓取代码是

相关文章

谷歌浏览器
谷歌浏览器

谷歌浏览器Google Chrome是一款可让您更快速、轻松且安全地使用网络的浏览器。Google Chrome的设计超级简洁,使用起来得心应手。这里提供了谷歌浏览器纯净安装包,有需要的小伙伴快来保存下载体验吧!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

PixTV
PixTV Hot

PixTV是一款面向AIGC内容创作的AI视频生成工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

相关专题

更多
cookie
cookie

Cookie 是一种在用户计算机上存储小型文本文件的技术,用于在用户与网站进行交互时收集和存储有关用户的信息。当用户访问一个网站时,网站会将一个包含特定信息的 Cookie 文件发送到用户的浏览器,浏览器会将该 Cookie 存储在用户的计算机上。之后,当用户再次访问该网站时,浏览器会向服务器发送 Cookie,服务器可以根据 Cookie 中的信息来识别用户、跟踪用户行为等。

12279

2023.06.30

document.cookie获取不到怎么解决
document.cookie获取不到怎么解决

document.cookie获取不到的解决办法:1、浏览器的隐私设置;2、Same-origin policy;3、HTTPOnly Cookie;4、JavaScript代码错误;5、Cookie不存在或过期等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1226

2023.11.23

阻止所有cookie什么意思
阻止所有cookie什么意思

阻止所有cookie意味着在浏览器中禁止接受和存储网站发送的cookie。阻止所有cookie可能会影响许多网站的使用体验,因为许多网站使用cookie来提供个性化服务、存储用户信息或跟踪用户行为。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

844

2024.02.23

cookie与session的区别
cookie与session的区别

本专题整合了cookie与session的区别和使用方法等相关内容,阅读专题下面的文章了解更详细的内容。

1926

2025.08.19

class在c语言中的意思
class在c语言中的意思

在C语言中,"class" 是一个关键字,用于定义一个类。想了解更多class的相关内容,可以阅读本专题下面的文章。

4176

2024.01.03

python中class的含义
python中class的含义

本专题整合了python中class的相关内容,阅读专题下面的文章了解更多详细内容。

306

2025.12.06

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

6541

2024.08.14

Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

40

2026.10.28

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

20

2026.10.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 1.5万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 6.1万人学习

CSS教程
CSS教程

共754课时 | 99.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn