0

0

Google 地图评论数据抓取:提升稳定性和准确性

DDD

DDD

发布时间:2025-07-09 22:22:12

|

973人浏览过

|

来源于php中文网

原创

google 地图评论数据抓取:提升稳定性和准确性

本文旨在解决使用自动化工具抓取 Google 地图评论数据时遇到的不完整或不准确问题,特别是评论平均分和评论数量的抓取遗漏。我们将分析常见原因,并重点介绍如何利用 Selenium 结合动态定位策略和显式等待机制,构建更健壮、更可靠的爬虫,确保数据抓取的完整性和准确性。

1. 问题背景与常见挑战

在进行网页数据抓取时,尤其面对像 Google 地图这样高度动态和交互性强的网站,常常会遇到数据抓取不完整的问题。例如,当尝试抓取商家列表中的评论平均分和评论数量时,可能只成功抓取了部分数据,而其他数据则遗漏或显示为“N/A”。这通常是由于以下几个原因造成的:

  • 页面加载时序问题: 网页内容并非一次性加载完成,而是通过 JavaScript 动态渲染。如果抓取逻辑在元素尚未完全加载或渲染之前就尝试定位,就会导致失败。
  • 元素定位策略的脆弱性: 使用硬编码的 XPath 或基于索引的定位方式,在页面结构稍有变化时就可能失效。特别是当点击一个列表项后,页面内容发生变化,原有的全局索引可能不再适用。
  • 缺乏有效的等待机制: 简单的 time.sleep() 或 wait_for_timeout() 无法保证特定元素已加载完毕,可能导致过早或过晚的尝试定位。

原始代码示例中,reviews_span_xpath = f'//div[{index + 1}]//span[@role="img"]' 这类基于列表 index 的 XPath,在点击某个列表项并进入其详情页后,很可能不再指向当前详情页内的评论元素。详情页内的元素应该使用相对其自身布局的 XPath 或 CSS 选择器来定位。

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

为了克服上述挑战,我们推荐使用 Selenium 结合其强大的浏览器自动化能力和灵活的等待机制。Selenium 能够模拟用户行为,并提供更精细的元素交互和等待控制,从而提高抓取的稳定性。

2.1 Selenium 简介与优势

Selenium 是一个用于 Web 应用程序测试的工具,但它也被广泛用于网页抓取。其主要优势包括:

  • 真实浏览器模拟: Selenium 启动真实的浏览器实例(如 Chrome, Firefox),能够完全模拟用户操作,包括 JavaScript 渲染、AJAX 请求等。
  • 可视化调试: 在开发过程中,可以直接看到浏览器中的操作,便于定位问题。
  • 强大的元素定位: 支持多种定位策略(ID, Name, Class Name, Tag Name, Link Text, Partial Link Text, XPath, CSS Selector)。
  • 显式等待机制: 提供 WebDriverWait,可以等待特定条件满足后再进行操作,避免因加载延迟导致的问题。

2.2 核心抓取策略

为了准确抓取 Google 地图的评论数据,我们需要遵循以下策略:

DeepL Write
DeepL Write

DeepL推出的AI驱动的写作助手,在几秒钟内完善你的写作

下载
  1. 遍历列表项并点击: 首先定位到所有商家列表项,然后逐一点击,进入每个商家的详情页。
  2. 显式等待详情页加载: 在点击后,不要立即尝试抓取,而是等待详情页的关键元素(如商家名称、地址或评论区域)出现。
  3. 在详情页内定位元素: 一旦详情页加载完成,使用相对于详情页布局的 XPath 或 CSS 选择器来定位评论相关的元素,而不是依赖于列表的原始索引。
  4. 提取并解析数据: 从评论元素的 aria-label 属性中提取评论平均分和评论数量。

2.3 示例代码:使用 Selenium 抓取 Google 地图评论

以下是一个使用 Python 和 Selenium 实现 Google 地图评论抓取的示例框架。

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

def scrape_google_maps_reviews(search_url):
    # 配置 ChromeDriver 服务
    # 请确保您的 ChromeDriver 版本与 Chrome 浏览器版本兼容
    service = Service(executable_path='/path/to/chromedriver') # 替换为您的 ChromeDriver 路径
    options = webdriver.ChromeOptions()
    # options.add_argument('--headless') # 可选:无头模式,不显示浏览器界面
    options.add_argument('--disable-gpu')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124124 Safari/537.36")

    driver = webdriver.Chrome(service=service, options=options)
    driver.get(search_url)

    # 显式等待搜索结果加载
    WebDriverWait(driver, 20).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, 'div[role="feed"]'))
    )

    businesses_data = []

    # 模拟滚动以加载更多商家
    # 找到包含商家列表的滚动区域,通常是 role="feed" 的 div
    scrollable_div_xpath = '//*[@id="QA0Szd"]/div/div/div[1]/div[2]/div/div[1]/div/div/div[2]' # 示例XPath,可能需要根据实际页面调整
    try:
        scrollable_div = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, scrollable_div_xpath))
        )
        last_height = driver.execute_script("return arguments[0].scrollHeight", scrollable_div)
        while True:
            driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", scrollable_div)
            time.sleep(2) # 等待新内容加载
            new_height = driver.execute_script("return arguments[0].scrollHeight", scrollable_div)
            if new_height == last_height:
                break
            last_height = new_height
    except Exception as e:
        print(f"滚动加载失败或无滚动区域: {e}")

    # 获取所有商家列表项
    # 注意:这里获取的是列表中的元素,后续点击后,详情页的元素需要重新定位
    listing_elements = driver.find_elements(By.CSS_SELECTOR, 'div[role="feed"] > div > a') 
    print(f"找到 {len(listing_elements)} 个商家列表项。")

    for i in range(len(listing_elements)):
        try:
            # 重新获取列表项,因为点击后页面可能刷新或元素引用失效
            # 也可以尝试存储元素的唯一标识符(如 href),然后通过 href 重新定位
            # 但最简单可靠的方式是每次循环重新获取所有可见列表项,然后点击第i个
            # 注意:这里需要确保点击后,浏览器能正确返回列表视图,或者详情页是侧边栏
            # Google Maps 详情页通常是侧边栏,所以可以直接点击

            # 重新定位当前可见的列表项
            current_listing_elements = driver.find_elements(By.CSS_SELECTOR, 'div[role="feed"] > div > a')
            if i >= len(current_listing_elements):
                print(f"列表项 {i+1} 不再可见,跳过。")
                continue

            listing_to_click = current_listing_elements[i]
            # 获取商家的名称或链接,用于日志和验证
            listing_name = listing_to_click.find_element(By.CSS_SELECTOR, '.qBF1Pd').text if listing_to_click.find_elements(By.CSS_SELECTOR, '.qBF1Pd') else "未知名称"
            print(f"\n尝试点击商家: {listing_name}")

            listing_to_click.click()

            # 显式等待详情页加载完成,例如等待商家名称或评论区域出现
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, 'div.qBF1Pd.fontHeadlineSmall')) # 商家名称
            )
            WebDriverWait(driver, 10).until(
                EC.presence_of_element_located((By.CSS_SELECTOR, 'button[data-item-id="reviews"]')) # 评论按钮
            )
            time.sleep(1) # 短暂等待,确保所有动态内容渲染完毕

            business = Business()

            # 在详情页内定位元素并抓取信息
            try:
                business.name = driver.find_element(By.CSS_SELECTOR, 'div.qBF1Pd.fontHeadlineSmall').text
            except:
                pass
            try:
                business.address = driver.find_element(By.CSS_SELECTOR, 'button[data-item-id="address"] div.fontBodyMedium').text
            except:
                pass
            try:
                business.website = driver.find_element(By.CSS_SELECTOR, 'a[data-item-id="authority"] div.fontBodyMedium').text
            except:
                pass
            try:
                business.phone_number = driver.find_element(By.CSS_SELECTOR, 'button[data-item-id^="phone:tel:"] div.fontBodyMedium').text
            except:
                pass
            try:
                # 类别通常在名称下方,可能需要更精确的定位
                category_element = 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')
                business.category = category_element.text
            except:
                pass

            # 尝试滚动详情面板以确保评论元素可见(如果需要)
            # 通常详情面板是可滚动的,评论可能在下方
            detail_panel_xpath = '//*[@id="QA0Szd"]/div/div/div[1]/div[3]/div/div[1]' # 详情面板的示例XPath
            try:
                detail_panel = driver.find_element(By.XPATH, detail_panel_xpath)
                driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight", detail_panel)
                time.sleep(1) # 等待滚动完成
            except Exception as e:
                print(f"详情面板滚动失败: {e}")

            # 定位评论元素 (注意:这里不再使用 index,而是直接定位详情页内的评论区域)
            # Google Maps 评论通常在一个带有 role="img" 的 span 中,且其父元素可能是评论按钮
            reviews_span_xpath_in_detail = '//button[contains(@aria-label, "stars")]/span[@role="img"]'
            reviews_element = driver.find_elements(By.XPATH, reviews_span_xpath_in_detail)

            if reviews_element:
                reviews_label = reviews_element[0].get_attribute("aria-label")
                print(f"Reviews Label for {business.name}: {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)))
                else:
                    print(f"无法解析评论标签: {reviews_label}")
            else:
                print(f"未找到 {business.name} 的评论信息。")

            businesses_data.append(business)

            # 返回到列表视图 (如果详情页是独立页面,则需要 driver.back())
            # 对于 Google Maps 侧边栏详情,通常不需要额外操作,直接点击下一个列表项即可
            # 但为了确保,可以尝试点击一个返回按钮或者等待列表重新可见
            # 如果列表项是动态加载的,每次循环重新获取 listing_elements 是必要的

            # 简单等待,确保页面状态稳定,为下一次点击做准备
            time.sleep(1) 

        except Exception as e:
            print(f"处理第 {i+1} 个商家时发生错误: {e}")
            # 发生错误时,尝试返回列表或刷新页面,然后继续
            # driver.refresh() # 谨慎使用,可能导致当前列表丢失
            time.sleep(2) # 稍作等待,避免连续错误
            continue # 继续下一个商家

    driver.quit()
    return businesses_data

# 示例用法
if __name__ == "__main__":
    search_query = "restaurants in New York"
    # 注意:Google Maps 的 URL 结构可能很复杂,这里只是一个示例
    # 实际应用中,您可能需要先通过搜索框输入查询,然后获取结果页URL
    # 或者直接构建一个包含查询参数的URL
    google_maps_url = f"https://www.google.com/maps/search/{search_query.replace(' ', '+')}"

    scraped_data = scrape_google_maps_reviews(google_maps_url)

    print("\n--- 抓取结果 ---")
    for biz in scraped_data:
        print(f"名称: {biz.name}")
        print(f"地址: {biz.address}")
        print(f"网站: {biz.website}")
        print(f"电话: {biz.phone_number}")
        print(f"类别: {biz.category}")
        print(f"平均评分: {biz.reviews_average}")
        print(f"评论数量: {biz.reviews_count}")
        print("-" * 20)

    print(f"总共抓取了 {len(scraped_data)} 条商家数据。")

2.4 代码解析与注意事项

  1. service = Service(executable_path='/path/to/chromedriver'): 替换为您的 ChromeDriver 可执行文件的实际路径。ChromeDriver 必须与您安装的 Chrome 浏览器版本兼容。
  2. options.add_argument('--headless'): 启用无头模式,浏览器将在后台运行,不显示界面。这在生产环境中很有用,但调试时建议注释掉。
  3. WebDriverWait 和 EC: 这是 Selenium 显式等待的核心。
    • WebDriverWait(driver, 20): 最长等待 20 秒。
    • EC.presence_of_element_located((By.CSS_SELECTOR, 'div[role="feed"]')): 等待指定 CSS 选择器对应的元素出现在 DOM 中。
    • EC.visibility_of_element_located(...): 等待元素不仅在 DOM 中,而且可见。
    • 针对详情页的等待,我们等待商家名称 (div.qBF1Pd.fontHeadlineSmall) 和评论按钮 (button[data-item-id="reviews"]) 出现,以确保页面加载完整。
  4. 动态定位评论元素: reviews_span_xpath_in_detail = '//button[contains(@aria-label, "stars")]/span[@role="img"]'。这个 XPath 不再依赖于列表的 index,而是查找详情页中包含“stars”的 aria-label 属性的按钮,然后在其内部寻找 role="img" 的 span。这种定位方式更具鲁棒性,因为它直接针对评论元素的语义特征。
  5. 模拟滚动: 对于 Google 地图,商家列表通常是无限滚动的。代码中加入了模拟滚动的逻辑,以加载更多商家。请注意 scrollable_div_xpath 可能需要根据实际页面结构调整。
  6. 错误处理: 使用 try-except 块来捕获可能发生的异常,例如元素未找到。这可以防止爬虫因单个元素的失败而完全中断。
  7. 重新获取列表项: 在循环内部,每次点击前重新获取 listing_elements 是一个重要的实践。这是因为在点击一个商家后,Google 地图的 DOM 可能会发生变化(例如,侧边栏详情页打开,列表项可能被重新渲染或隐藏),导致之前获取的元素引用失效(StaleElementReferenceException)。重新获取可以确保我们总是在操作当前有效的 DOM 元素。
  8. time.sleep() 的使用: 尽管我们强调使用显式等待,但在某些复杂交互后,或者在滚动加载内容时,短暂的 time.sleep() 仍然可以作为补充,给浏览器留出足够的渲染时间。但应尽量减少其使用,并优先考虑显式等待。

3. 总结

抓取动态网页数据,特别是像 Google 地图这样复杂的应用,需要更精细的控制和更健壮的策略。通过从 Playwright 转向 Selenium,并结合以下关键实践,可以显著提高抓取任务的成功率和数据准确性:

  • 选择合适的工具: Selenium 提供完整的浏览器自动化能力,便于处理动态内容和复杂的交互。
  • 采用动态定位策略: 避免使用脆弱的基于索引的 XPath,转而使用更具语义化和稳定性的 CSS 选择器或相对 XPath。
  • 利用显式等待机制: 告别不精确的 time.sleep(),使用 WebDriverWait 等待特定条件满足,确保元素已加载并可交互。
  • 细致的错误处理: 编写健壮的代码,处理可能出现的各种异常,确保爬虫的韧性。
  • 模拟用户行为: 模拟滚动、点击等真实用户行为,以触发内容的动态加载。

遵循这些原则,您将能够构建出更稳定、更高效的 Google 地图评论数据抓取解决方案。请务必遵守目标网站的 robots.txt 协议和服务条款,进行负责任的抓取。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

778

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

686

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

769

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

740

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1445

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

571

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

581

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

752

2023.08.11

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

31

2026.01.26

热门下载

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

精品课程

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

共14课时 | 0.8万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

CSS教程
CSS教程

共754课时 | 24万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号