in

如何爬取谷歌地图数据

如何爬取谷歌地图数据

在此网络抓取教程中,我们将抓取 Google 地图 ( google.com/maps ) – 一种地图服务,也是业务详细信息和评论的主要目录。

谷歌地图是一款复杂的网络软件,因此我们将使用Selenium、Playwright 等浏览器自动化工具包在 Python 中为我们呈现页面。我们将涵盖所有这三个选项,因此请随时按照您最熟悉的选项进行操作 – 让我们开始吧!

为什么要抓取谷歌地图?

谷歌地图包含大量商业资料数据,如地址、评级、电话号码和网站地址。它不仅是用于商业智能和市场分析的强大数据目录,还可以用于潜在客户生成,因为它包含业务联系详细信息。

项目设置

在本教程中,我们将主要使用浏览器自动化库(如SeleniumPlaywright)的 Javascript 执行功能来检索完全呈现的 HTML 页面。

那么,哪个浏览器自动化库最适合抓取 Google 地图?
我们只需要渲染和 javascript 执行功能,所以无论您最喜欢哪个!

为了解析这些页面,我们将使用parsel——一个支持通过 CSS 或 XPath 选择器解析 HTML 的社区包。

这些包可以通过终端工具安装pip

# for selenium
$ pip install selenium
# for playwright
$ pip install playwright

这些工具最强大的功能是javascript 执行功能。此功能允许我们向浏览器发送任何 javascript 代码片段,它会在当前页面的上下文中执行它。让我们快速浏览一下如何在我们的工具中使用它:

在 Selenium 中执行 Javascript

from selenium import webdriver

driver = webdriver.Chrome()
driver.get("https://httpbin.org/html")
script = 'return document.querySelector("h1").innerHTML'  # get first header text
title = driver.execute_script(script)
print(title)

Playwright 中的 Javascript 执行

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("http://httpbin.org/html")
    script = 'return document.querySelector("h1").innerHTML'  # get first header text
    title = page.evaluate("() => {" + script + "}")
    print(title)

Javascript 执行是所有浏览器自动化工具的关键特性,所以让我们来看看我们如何使用它来抓取 Google 地图!

查找 Google 地方信息

要在 Google 地图上查找地点,我们将利用搜索系统。作为 Google 产品的地图具有出色的搜索系统,可以理解自然语言查询。

例如,如果我们想查找巴黎卢浮宫博物馆的页面,我们可以使用类似人类的准确搜索查询:Louvre Museum in Paris。可以直接通过 URL 端点/maps/search/louvr+paris提交:

谷歌地图即时搜索结果的屏幕截图
窄查询显示单个结果

另一方面,对于不太准确的搜索查询,例如
/maps/search/mcdonalds+in+paris将提供多个结果供您选择:

谷歌地图多结果搜索查询的屏幕截图
广泛的查询显示多个结果

我们可以看到这个强大的端点可以将我们直接带到地点页面或向我们显示多个结果。首先,让我们看看后者——如何抓取多个搜索结果。


现在我们知道如何生成搜索 URL,我们需要做的就是打开它,等待它加载并提取链接。由于我们将处理高度动态的网页,因此我们需要一个辅助函数来帮助我们确保内容已加载:

// Wait for N amount of selectors to be present in the DOM
function waitCss(selector, n=1, require=false, timeout=5000) {
  console.log(selector, n, require, timeout);
  var start = Date.now();
  while (Date.now() - start < timeout){
  	if (document.querySelectorAll(selector).length >= n){
      return document.querySelectorAll(selector);
    }
  }
  if (require){
      throw new Error(`selector "${selector}" timed out in ${Date.now() - start} ms`);
  } else {
      return document.querySelectorAll(selector);
  }
}

使用这个实用函数,我们将能够确保我们的抓取脚本在开始解析之前等待内容加载。

让我们把它用在我们的搜索抓取工具中:

使用Selenium

from selenium import webdriver


script = """
function waitCss(selector, n=1, require=false, timeout=5000) {
  console.log(selector, n, require, timeout);
  var start = Date.now();
  while (Date.now() - start < timeout){
  	if (document.querySelectorAll(selector).length >= n){
      return document.querySelectorAll(selector);
    }
  }
  if (require){
      throw new Error(`selector "${selector}" timed out in ${Date.now() - start} ms`);
  } else {
      return document.querySelectorAll(selector);
  }
}

var results = waitCss("div[role*=article]>a", n=10, require=false);
return Array.from(results).map((el) => el.getAttribute("href"))
"""

driver = webdriver.Chrome()


def search(query):
    url = f"https://www.google.com/maps/search/{query.replace(' ', '+')}/?hl=en"
    driver.get(url)
    urls = driver.execute_script(script)
    return urls or [url]


print(f'single search: {search("louvre museum in paris")}')
print(f'multi search: {search("mcdonalds in paris")}')

使用Playwright

from playwright.sync_api import sync_playwright

script = """
function waitCss(selector, n=1, require=false, timeout=5000) {
  console.log(selector, n, require, timeout);
  var start = Date.now();
  while (Date.now() - start < timeout){
  	if (document.querySelectorAll(selector).length >= n){
      return document.querySelectorAll(selector);
    }
  }
  if (require){
      throw new Error(`selector "${selector}" timed out in ${Date.now() - start} ms`);
  } else {
      return document.querySelectorAll(selector);
  }
}

var results = waitCss("div[role*=article]>a", n=10, require=false);
return Array.from(results).map((el) => el.getAttribute("href"))
"""


def search(query, page):
    url = f"https://www.google.com/maps/search/{query.replace(' ', '+')}/?hl=en"
    page.goto(url)
    urls = page.evaluate("() => {" + script + "}")
    return urls or [url]


with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    print(f'single search: {search("louvre museum in paris", page=page)}')
    print(f'multi search: {search("mcdonalds in paris", page=page)}')

结果

single search: ['https://www.google.com/maps/search/louvre+museum+in+paris/?hl=en']
multi search: [
    "https://www.google.com/maps/place/McDonald's/data=!4m7!3m6!1s0x47e66fea26bafdc7:0x21ea7aaf1fb2b3e3!8m2!3d48.8729997!4d2.2991604!16s%2Fg%2F1hd_88rdh!19sChIJx_26Jupv5kcR47OyH6966iE?authuser=0&hl=en&rclk=1",
    ...
]

现在我们可以成功地在谷歌地图上找到地点,让我们来看看我们如何抓取他们的数据。

抓取 Google Places

为了抓取地点数据,我们将使用使用浏览器自动化呈现 javascript 内容的相同方法。为此,我们将获取之前发现的公司 URL 并抓取每家公司的概览数据。

Google 地图地点资料的屏幕截图
有关业务的大量有价值数据

为了解析呈现的 HTML 数据,我们将使用parsel一些简单的 CSS 选择器:

def parse_place(selector):
    """parse Google Maps place"""
    
    def aria_with_label(label):
        """gets aria element as is"""
        return selector.css(f"*[aria-label*='{label}']::attr(aria-label)")

    def aria_no_label(label):
        """gets aria element as text with label stripped off"""
        text = aria_with_label(label)
        return text.split(label, 1)[1].strip()

    result = {
        "name": "".join(selector.css("h1 ::text").getall()).strip(),
        "category": selector.css("button[jsaction='pane.rating.category']::text").get(),
        # most of the data can be extracted through accessibility labels:
        "address": aria_no_label("Address: "),
        "website": aria_no_label("Website: "),
        "phone": aria_no_label("Phone: "),
        "review_count": aria_with_label(" reviews").get(),
        "work_hours": aria_with_label("Monday, ").get().split(". Hide")[0],
        # to extract star numbers from text we can use regex pattern for numbers: "\d+"
        "stars": aria_with_label(" stars").re("\d+.*\d+")[0],
        "5_stars": aria_with_label("5 stars").re(r"(\d+) review")[0],
        "4_stars": aria_with_label("4 stars").re(r"(\d+) review")[0],
        "3_stars": aria_with_label("3 stars").re(r"(\d+) review")[0],
        "2_stars": aria_with_label("2 stars").re(r"(\d+) review")[0],
        "1_stars": aria_with_label("1 stars").re(r"(\d+) review")[0],
    }
    return result

由于谷歌地图使用复杂的 HTML 结构和 CSS 样式,因此解析起来非常困难。幸运的是,谷歌地图还实现了我们可以利用的大量辅助功能!

让我们将其添加到我们的抓取工具中:

使用Playwright

import json

from parsel import Selector
from playwright.sync_api import sync_playwright


def parse_place(selector):
    """parse Google Maps place"""

    def aria_with_label(label):
        """gets aria element as is"""
        return selector.css(f"*[aria-label*='{label}']::attr(aria-label)")

    def aria_no_label(label):
        """gets aria element as text with label stripped off"""
        text = aria_with_label(label).get("")
        return text.split(label, 1)[1].strip()

    result = {
        "name": "".join(selector.css("h1 ::text").getall()).strip(),
        "category": selector.css("button[jsaction='pane.rating.category']::text").get(),
        # most of the data can be extracted through accessibility labels:
        "address": aria_no_label("Address: "),
        "website": aria_no_label("Website: "),
        "phone": aria_no_label("Phone: "),
        "review_count": aria_with_label(" reviews").get(),
        # to extract star numbers from text we can use regex pattern for numbers: "\d+"
        "stars": aria_with_label(" stars").re("\d+.*\d+")[0],
        "5_stars": aria_with_label("5 stars").re(r"(\d+) review")[0],
        "4_stars": aria_with_label("4 stars").re(r"(\d+) review")[0],
        "3_stars": aria_with_label("3 stars").re(r"(\d+) review")[0],
        "2_stars": aria_with_label("2 stars").re(r"(\d+) review")[0],
        "1_stars": aria_with_label("1 stars").re(r"(\d+) review")[0],
    }
    return result


urls = ["https://goo.gl/maps/Zqzfq43hrRPmWGVB7?hl=en"]
places = []
with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    for url in urls:
        page.goto(url)
        page.wait_for_selector("button[jsaction='pane.rating.category']")
        places.append(parse_place(Selector(text=page.content())))

print(json.dumps(places, indent=2, ensure_ascii=False))

结果

[
  {
    "name": "Louvre Museum",
    "category": "Art museum",
    "address": "Rue de Rivoli, 75001 Paris, France",
    "website": "louvre.fr",
    "phone": "+33 1 40 20 50 50",
    "review_count": "240,040 reviews",
    "stars": "4.7",
    "5_stars": "513",
    "4_stars": "211",
    "3_stars": "561",
    "2_stars": "984",
    "1_stars": "771"
  }
]

我们可以看到,只需几行巧妙的代码,我们就可以从谷歌地图中抓取业务数据。我们专注于一些可见的细节,但 HTML 正文中提供了更多信息,如评论、新闻文章和各种分类标签。

常问问题

为了总结本指南,让我们看一下有关网页抓取Google 地图的一些常见问题:

抓取谷歌地图合法吗?

是的。Google 地图数据是公开的,我们不会提取任何私人信息。以缓慢、尊重的速度抓取谷歌地图属于道德抓取定义。话虽如此,在抓取个人数据(例如评论所附的详细信息(图像或名称))时,应注意欧盟的 GDRP 合规性。

如何更改谷歌地图显示语言?

要更改 Google 地图上显示的内容的语言,我们可以使用 URL 参数hl(代表“人类语言”)。例如,对于英语,我们会?hl=en在 URL 的末尾添加,例如google.com/maps/search/big+ben+londong+uk/?hl=en

谷歌地图爬取工具摘要

在本教程中,我们构建了一个可在 Selenium、Playwright 中使用的Google 地图爬取工具。为此,我们启动了一个浏览器实例并通过 Python 代码控制它,以通过 Google 地图搜索查找企业,并抓取每个企业的网站、电话号码和元信息等详细信息。

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。